mirror of
https://github.com/lukaszraczylo/traefik-llmgateway.git
synced 2026-10-12 04:54:45 +00:00
fix: reject dot-only config names
configNamePattern (^[a-zA-Z0-9._-]+$) permits "." and ".." as valid
provider/mcpServers/agents names, since a bare dot is in the allowed
character class. Both names read as directory-traversal segments once
embedded as a path segment in the gateway's own routes
(passthroughRoute's "/{provider}/*", targetRoute's "/mcp/{name}/*" and
"/a2a/{name}/*"). Nothing in this package's current path handling
mis-resolves them, but disallowing them at construction is cheap and
removes any future dependence on that staying true.
- providers.go: add dotOnlyConfigNames ({".", ".."}), checked in
validateConfigName between the character-class check and the
reserved-name check.
- provider_openai_test.go: extend TestValidateConfigName with "." and
".." cases (rejected) across provider/mcpServers/agents kinds, plus
"..." (accepted — the rule is an exact match on "." and "..", not "any
dots-only string").
Gates: go test ./... -count=1, go test ./... -race -count=1, gofmt -l .,
go vet ./..., make yaegi-check, golangci-lint run ./..., golangci-lint
run --enable=gosec ./..., fieldalignment ./... — all clean.
This commit is contained in:
+12
-5
@@ -666,11 +666,13 @@ func TestBuildAdapters(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestValidateConfigName covers validateConfigName's character-set and
|
||||
// reserved-name rules, shared by buildAdapters (provider names) and
|
||||
// validateTargetURLs (mcpServers/agents names, mcp_a2a_test.go) — a
|
||||
// provider, MCP server, or agent named "v1", "mcp", or "a2a" would shadow
|
||||
// one of the gateway's own fixed top-level routes.
|
||||
// TestValidateConfigName covers validateConfigName's character-set,
|
||||
// dot-only-name, and reserved-name rules, shared by buildAdapters
|
||||
// (provider names) and validateTargetURLs (mcpServers/agents names,
|
||||
// mcp_a2a_test.go) — a provider, MCP server, or agent named "v1", "mcp",
|
||||
// or "a2a" would shadow one of the gateway's own fixed top-level routes,
|
||||
// and one named "." or ".." reads as a directory-traversal segment once
|
||||
// embedded as a path segment in the gateway's own routes.
|
||||
func TestValidateConfigName(t *testing.T) {
|
||||
tests := []struct {
|
||||
kind string
|
||||
@@ -687,6 +689,11 @@ func TestValidateConfigName(t *testing.T) {
|
||||
{"provider", "a2a", true}, // reserved: A2A target-proxy prefix
|
||||
{"mcpServers", "v1", true}, // same reserved set applies to every kind
|
||||
{"agents", "a2a", true},
|
||||
{"provider", ".", true}, // dot-only: matches configNamePattern's character class but rejected anyway
|
||||
{"provider", "..", true}, // dot-only: same
|
||||
{"mcpServers", ".", true}, // dot-only rule applies to every kind
|
||||
{"agents", "..", true}, // dot-only rule applies to every kind
|
||||
{"provider", "...", false}, // three dots is NOT one of dotOnlyConfigNames — the rule is exact-match on "." and "..", not "any dots-only string"
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.kind+"/"+tt.name, func(t *testing.T) {
|
||||
|
||||
+17
-3
@@ -207,13 +207,27 @@ var configNamePattern = regexp.MustCompile(`^[a-zA-Z0-9._-]+$`)
|
||||
// reachable — reject it here instead of silently shadowing it.
|
||||
var reservedConfigNames = map[string]bool{"v1": true, "mcp": true, "a2a": true}
|
||||
|
||||
// validateConfigName reports an error unless name matches configNamePattern
|
||||
// and is not one of reservedConfigNames. kind labels the config section
|
||||
// (e.g. "provider", "mcpServers", "agents") in the error message.
|
||||
// dotOnlyConfigNames are the two names configNamePattern's character class
|
||||
// permits (it allows ".") but that are rejected anyway: "." and ".." read
|
||||
// as directory-traversal segments once embedded as a path segment in the
|
||||
// gateway's own routes (passthroughRoute's "/{provider}/*", targetRoute's
|
||||
// "/mcp/{name}/*" and "/a2a/{name}/*"), even though nothing in this
|
||||
// package's own path handling currently mis-resolves them — disallowing
|
||||
// them at construction is cheap, and removes any dependence on that
|
||||
// staying true.
|
||||
var dotOnlyConfigNames = map[string]bool{".": true, "..": true}
|
||||
|
||||
// validateConfigName reports an error unless name matches configNamePattern,
|
||||
// is not one of dotOnlyConfigNames, and is not one of reservedConfigNames.
|
||||
// kind labels the config section (e.g. "provider", "mcpServers", "agents")
|
||||
// in the error message.
|
||||
func validateConfigName(kind, name string) error {
|
||||
if !configNamePattern.MatchString(name) {
|
||||
return fmt.Errorf("llmgateway: %s name %q is invalid: must match %s", kind, name, configNamePattern.String())
|
||||
}
|
||||
if dotOnlyConfigNames[name] {
|
||||
return fmt.Errorf("llmgateway: %s name %q is invalid: must not be \".\" or \"..\"", kind, name)
|
||||
}
|
||||
if reservedConfigNames[name] {
|
||||
return fmt.Errorf("llmgateway: %s name %q is reserved and would shadow a gateway route", kind, name)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user