Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions cmd/docker-mcp/secret-management/secret/credstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ func cmd(ctx context.Context, args ...string) *exec.Cmd {
return exec.CommandContext(ctx, "docker", append([]string{"pass"}, args...)...)
}

// ValidateSecretName returns an error if the name contains glob metacharacters
// or path traversal sequences that could be injected into the pattern sent to
// the Desktop secrets resolver (which treats the pattern field as a glob).
func ValidateSecretName(name string) error {
if strings.ContainsAny(name, "*?[]{") {
return fmt.Errorf("secret name %q contains illegal glob metacharacter", name)
}
return nil
}

// GetDefaultSecretKey constructs the full namespaced ID for an MCP secret
// using the default namespace (docker/mcp/).
//
Expand Down
12 changes: 12 additions & 0 deletions cmd/docker-mcp/secret-management/secret/secret_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@ func TestGetDefaultSecretKey(t *testing.T) {
assert.Equal(t, "docker/mcp/mykey", result)
}

func TestValidateSecretName(t *testing.T) {
valid := []string{"mykey", "postgres_password", "GITHUB_TOKEN", "some-key", "key.with.dots"}
for _, name := range valid {
require.NoError(t, ValidateSecretName(name), "expected %q to be valid", name)
}

invalid := []string{"**", "*", "docker/mcp/**", "key*", "key?", "key[0]", "key{a}"}
for _, name := range invalid {
assert.Error(t, ValidateSecretName(name), "expected %q to be invalid", name)
}
}

func TestParseArg(t *testing.T) {
// Test key=value parsing
secret, err := ParseArg("key=value", SetOpts{})
Expand Down
10 changes: 9 additions & 1 deletion pkg/gateway/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,21 @@ func (c *Configuration) Find(serverName string) (*catalog.ServerConfig, *map[str

// Is it an MCP Server?
if server.Image != "" || server.SSEEndpoint != "" || server.Remote.URL != "" {
// Scope secrets to only the keys declared by this server so that a
// compromised or malicious server cannot access another server's secrets.
scopedSecrets := make(map[string]string, len(server.Secrets))
for _, s := range server.Secrets {
if v, ok := c.secrets[s.Name]; ok {
scopedSecrets[s.Name] = v
}
}
return &catalog.ServerConfig{
Name: serverName,
Spec: server,
Config: map[string]any{
oci.CanonicalizeServerName(serverName): c.config[oci.CanonicalizeServerName(serverName)],
},
Secrets: c.secrets, // TODO: we could keep just the secrets for this server
Secrets: scopedSecrets,
}, nil, true
}

Expand Down
8 changes: 8 additions & 0 deletions pkg/gateway/secrets_uri.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ func buildFallbackURIs(configs []ServerSecretConfig) map[string]string {
secretToOAuthID := oauthMapping(cfg)

for _, s := range cfg.Secrets {
if err := secret.ValidateSecretName(s.Name); err != nil {
log.Logf("Warning: skipping secret with invalid name %q: %v", s.Name, err)
continue
}
secretName := cfg.Namespace + s.Name
if oauthSecretID, ok := secretToOAuthID[s.Name]; ok {
secretNameToURI[secretName] = "se://" + oauthSecretID
Expand All @@ -68,6 +72,10 @@ func buildVerifiedURIs(configs []ServerSecretConfig, availableSecrets map[string
secretToOAuthID := oauthMapping(cfg)

for _, s := range cfg.Secrets {
if err := secret.ValidateSecretName(s.Name); err != nil {
log.Logf("Warning: skipping secret with invalid name %q: %v", s.Name, err)
continue
}
secretName := cfg.Namespace + s.Name

// Try OAuth token first
Expand Down
4 changes: 4 additions & 0 deletions pkg/mcp/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ func (c *remoteMCPClient) AddRoots(roots []*mcp.Root) {
}

func getSecretValue(ctx context.Context, secretName string) string {
if err := secret.ValidateSecretName(secretName); err != nil {
log.Logf("Warning: skipping secret with invalid name %q: %v", secretName, err)
return ""
}
fullID := secret.GetDefaultSecretKey(secretName)
env, err := secret.GetSecret(ctx, fullID)
if err != nil {
Expand Down
Loading