fix: prevent glob injection and cross-server secret leakage in MCP gateway#467
Conversation
…teway
Validates secret names against glob metacharacters before they reach the
Desktop secrets resolver, which treats the pattern field as a glob. A
catalog entry with name "**" would otherwise cause GetSecret to send
{"pattern": "docker/mcp/**"}, returning all vault secrets and injecting
them into outbound HTTP headers to the remote server (SSH-10 / external
report).
Also scopes the secrets map in configuration.Find() to only the keys
declared by each server, preventing cross-server secret access by name
guessing.
- Add ValidateSecretName() to the secret package as the enforcement point
- Call it in remote.go:getSecretValue() to block HTTP header exfiltration
- Call it in secrets_uri.go buildFallbackURIs/buildVerifiedURIs to block
glob injection into se:// URIs for container-based servers
- Scope Secrets in configuration.Find() to per-server declared secrets
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
87c9b07 to
cf4d4bc
Compare
|
@slimslenderslacks Can you take a look? |
saucow
left a comment
There was a problem hiding this comment.
Reviewed and tested locally.
Glob injection fix (ValidateSecretName): Confirmed the issue — a wildcard in the secret name gets passed through to the secrets resolver pattern query. Blocking * is the correct fix; it's the only character the resolver interprets as a wildcard.
Secret scoping in Find(): Good cleanup. Resolves the pre-existing TODO and applies least-privilege to the secrets map. Both code paths that consume ServerConfig.Secrets (remote.go and clientpool.go) already iterate only the server's declared secrets and look up by declared name, so the full map wasn't directly exploitable, but scoping it down is the right thing to do.
On the ?[]{ blocking: The resolver already rejects these characters before they reach the matching logic, so blocking them in the gateway is harmless defense-in-depth rather than a required fix. No issue including them.
On path traversal: The ValidateSecretName doc comment mentions "path traversal sequences" but the function doesn't block .. or /. This is fine for the scope of this PR — the wildcard fix is what matters here. Noting it for accuracy since the doc comment implies broader coverage than the implementation provides.
Testing:
- Happy path: container servers with API key secrets, container servers with OAuth secrets, and remote servers with OAuth all work correctly. No validation warnings for legitimate secret names. Tool calls succeed across all server types.
- Negative path: a test catalog entry with a wildcard secret name pointing at a local server. Gateway correctly rejects the name with a warning and the remote server receives empty headers. No secret leaked.
Summary
Fixes two secret over-disclosure vulnerabilities in the MCP gateway reported via security@docker.com (related to SSH-10):
name: **causedGetDefaultSecretKeyto producedocker/mcp/**, which was embedded verbatim into the JSON pattern sent to the Desktop secrets resolver. The resolver treats this as a glob and returns all vault secrets, which then get injected into outbound HTTP headers to the attacker's server.configuration.Find()passed the fullc.secretsmap (all servers' secrets) to everyServerConfig. An attacker who guesses a secret name from the public catalog could retrieve another server's secret without touching the secrets engine.Changes
credstore.go— AddValidateSecretName()as a single enforcement point; rejects names containing glob metacharacters (*?[]{)remote.go— CallValidateSecretName()ingetSecretValue()before constructing the secrets engine query, blocking HTTP header exfiltrationsecrets_uri.go— CallValidateSecretName()in bothbuildFallbackURIsandbuildVerifiedURIsto block glob injection intose://URIs for container-based serversconfiguration.go— ScopeSecretsinFind()to only the keys declared by that specific server (resolves the pre-existingTODOcomment)Test plan
TestValidateSecretNamecovers valid and invalid name casesmake lint-darwinpassesname: **no longer leaks secrets into remote server headers🤖 Generated with Claude Code