mirror of
https://github.com/lukaszraczylo/traefik-llmgateway.git
synced 2026-10-15 05:29:28 +00:00
feat(passthrough): add model, path, and header authorization hardening
handlePassthrough checked only the provider allowlist, never the model or the path. A tenant scoped to one cheap model could reach a provider's entire native API (files, batches, fine-tuning) on the operator's key, and client identity headers reached keyless in-cluster MCP/A2A targets unfiltered. This change closes both gaps, additively: every new field defaults to reproducing prior behavior exactly. Model enforcement: when a passthrough JSON body carries a top-level "model" field, handlePassthrough checks it against the group's model glob, in both the bare and provider-prefixed form (the same dual-candidate matcher the unified route's resolveAgainst already applies). A body with no inspectable model (Gemini's URL-embedded model, a multipart upload, a non-JSON body) falls back to provider-only authorization, unchanged from before this round. Per-provider toggle: ProviderConfig.Passthrough, nil or true by default, lets an operator disable a provider's native passthrough route. A disabled provider now reports the ordinary unknown-route 404, checked in ServeHTTP's own route gate, before auth even runs. Per-group path allowlist: GroupConfig.PassthroughPaths, empty by default (matching every other allow-list field's empty-means-all semantics), restricts which native rest path a group may address. Dangerous-header stripping: proxyUpstream now additionally strips Forwarded, X-Forwarded-*, X-Auth-*, X-Remote-User, X-Remote-Groups, and Cookie on both the native-passthrough and MCP/A2A target-proxy paths, plus OpenAI-Organization, OpenAI-Project, and anthropic-beta on native passthrough only. This is an additive denylist expansion, not an allowlist inversion; ordinary headers still pass through. A dedicated test asserts a config with none of the new fields set behaves identically to before this change. The operator's home group (providers/models/mcpServers/agents/passthroughPaths all empty) stays fully unrestricted, and macstudio-rerank/parakeet-mlx/piper-tts stay reachable through native passthrough by default.
This commit is contained in:
@@ -386,6 +386,24 @@ func validateMetadataPath(providerName, metadataPath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// providerPassthroughEnabled reports whether providerName's native
|
||||
// passthrough route is reachable (security+performance audit,
|
||||
// 2026-08-22): true when ProviderConfig.Passthrough is nil (the default)
|
||||
// or explicitly true; false only when an operator set it to false.
|
||||
// cfg.Providers[providerName] is assumed to already be a known key —
|
||||
// ServeHTTP's own passthrough route gate (llmgateway.go) checks
|
||||
// g.adapters[providerName] first, and buildAdapters guarantees adapters
|
||||
// and cfg.Providers share the same key set — but a missing key still
|
||||
// defaults to true here rather than panicking, matching every other
|
||||
// nil-means-default helper in this package.
|
||||
func providerPassthroughEnabled(cfg *Config, providerName string) bool {
|
||||
pc, ok := cfg.Providers[providerName]
|
||||
if !ok || pc.Passthrough == nil {
|
||||
return true
|
||||
}
|
||||
return *pc.Passthrough
|
||||
}
|
||||
|
||||
// buildAdapters resolves cfg.Providers into a map of providerAdapter keyed
|
||||
// by provider name. Every provider name is validated (validateConfigName)
|
||||
// and every ProviderConfig value must be non-nil before anything else runs,
|
||||
|
||||
Reference in New Issue
Block a user