mirror of
https://github.com/lukaszraczylo/traefik-llmgateway.git
synced 2026-10-16 05:37: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:
@@ -6,6 +6,39 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestProviderPassthroughEnabled is the security+performance audit
|
||||
// (2026-08-22) table for ProviderConfig.Passthrough's default: nil (the
|
||||
// zero value — every provider configured before this field existed)
|
||||
// means enabled, matching prior behavior exactly; only an explicit false
|
||||
// disables it.
|
||||
func TestProviderPassthroughEnabled(t *testing.T) {
|
||||
trueVal, falseVal := true, false
|
||||
cases := []struct {
|
||||
pc *ProviderConfig
|
||||
name string
|
||||
want bool
|
||||
}{
|
||||
{name: "nil field defaults to enabled", pc: &ProviderConfig{Type: "openai"}, want: true},
|
||||
{name: "explicit true stays enabled", pc: &ProviderConfig{Type: "openai", Passthrough: &trueVal}, want: true},
|
||||
{name: "explicit false disables", pc: &ProviderConfig{Type: "openai", Passthrough: &falseVal}, want: false},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
cfg := &Config{Providers: map[string]*ProviderConfig{"openai": c.pc}}
|
||||
if got := providerPassthroughEnabled(cfg, "openai"); got != c.want {
|
||||
t.Errorf("providerPassthroughEnabled = %v, want %v", got, c.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("unknown provider name defaults to enabled", func(t *testing.T) {
|
||||
cfg := &Config{Providers: map[string]*ProviderConfig{}}
|
||||
if !providerPassthroughEnabled(cfg, "notconfigured") {
|
||||
t.Error("want true for a provider name absent from cfg.Providers")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestAttemptRecorderFromContext_RoundTrip proves withAttemptRecorder/
|
||||
// attemptRecorderFromContext round-trip a non-nil recorder, and that the
|
||||
// SAME func value comes back out (asserted indirectly here, by observing
|
||||
|
||||
Reference in New Issue
Block a user