fix: config validation hardening and integration polish

- providers.go: validate provider names against ^[a-zA-Z0-9._-]+$ and
  reject reserved names (v1, mcp, a2a) that would shadow gateway routes;
  reject nil ProviderConfig map values at construction instead of
  panicking.
- mcp_a2a.go: apply the same name-pattern and reserved-name validation to
  mcpServers/agents keys, and reject nil TargetConfig/AgentConfig map
  values at construction.
- auth.go: reject nil GroupConfig map values at construction.
- provider_openai_test.go, mcp_a2a_test.go, auth_test.go: table-driven
  constructor-error coverage for the above.
- integration/integration_test.go: point the package doc at
  `make integration-up`/`integration-keep` instead of a bare
  `docker compose up -d --build`, which skips the dynamic.yml.tmpl
  render step and can run against a stale or absent dynamic.yml; add
  max_tokens to the gemini chat request so translate_gemini.go's
  hasMaxTokens=true branch is actually exercised under real Yaegi.
- translate_gemini.go: soften the max_tokens comment's verification claim
  to match what the integration suite actually now exercises.
This commit is contained in:
2026-08-20 13:57:56 +01:00
parent 730583d98e
commit ae4d6bab96
8 changed files with 202 additions and 23 deletions
+21 -5
View File
@@ -183,18 +183,34 @@ func (g *Gateway) handleTargetProxy(w http.ResponseWriter, r *http.Request, u *u
g.proxyUpstream(w, r, upstreamURL, g.targetClient, nil, false, kind+" target (name "+name+")")
}
// validateTargetURLs checks that every configured MCP-server and agent
// target URL parses (url.Parse) and uses an http or https scheme. A
// malformed or non-HTTP target is rejected once, here, at construction —
// a constructor error, not a 502 the first time some caller happens to
// address it.
// validateTargetURLs checks every configured MCP-server and agent entry at
// construction: the map value must be non-nil, the name must pass
// validateConfigName (same character set and reserved-name rules as a
// provider name — an MCP server or agent is routed at "/mcp/{name}/*" or
// "/a2a/{name}/*", the same shadowing risk applies), and the target URL
// must parse (url.Parse) and use an http or https scheme. A nil value,
// invalid name, or malformed/non-HTTP target is rejected once, here, at
// construction — a constructor error, not a nil-pointer panic or a 502 the
// first time some caller happens to address it.
func validateTargetURLs(cfg *Config) error {
for name, tc := range cfg.MCPServers {
if tc == nil {
return fmt.Errorf("llmgateway: mcpServers %q: config must not be nil", name)
}
if err := validateConfigName("mcpServers", name); err != nil {
return err
}
if err := validateTargetURL(tc.URL); err != nil {
return fmt.Errorf("llmgateway: mcpServers %q: %w", name, err)
}
}
for name, ac := range cfg.Agents {
if ac == nil {
return fmt.Errorf("llmgateway: agents %q: config must not be nil", name)
}
if err := validateConfigName("agents", name); err != nil {
return err
}
if err := validateTargetURL(ac.URL); err != nil {
return fmt.Errorf("llmgateway: agents %q: %w", name, err)
}