diff --git a/internal/cli/cli_test.go b/internal/cli/cli_test.go index 20133cc..bcccfbd 100644 --- a/internal/cli/cli_test.go +++ b/internal/cli/cli_test.go @@ -1344,6 +1344,46 @@ another_flag: 123 "agent-card": "https://yaml-extra-agent.example.com", }, }, + { + name: "svc-param Authorization values are redacted", + filename: "creds.yaml", + content: "svc-param:\n - \"Authorization=Bearer SECRET123\"\n - \"X-Trace=abc\"\n", + wantVals: map[string]string{ + "svc-param": "Authorization=,X-Trace=abc", + }, + }, + { + name: "svc-param Authorization value containing a comma is fully redacted", + filename: "creds-comma.yaml", + content: "svc-param:\n - \"Authorization=Basic abc,def\"\n", + wantVals: map[string]string{ + "svc-param": "Authorization=", + }, + }, + { + name: "svc-param Authorization key is matched case-insensitively with colon separator", + filename: "creds-colon.yaml", + content: "svc-param:\n - \"authorization:Bearer SECRET123\"\n", + wantVals: map[string]string{ + "svc-param": "authorization:", + }, + }, + { + name: "svc-param key with Authorization prefix is not redacted", + filename: "creds-prefix.yaml", + content: "svc-param:\n - \"Authorization-Hint=visible\"\n", + wantVals: map[string]string{ + "svc-param": "Authorization-Hint=visible", + }, + }, + { + name: "svc-param scalar Authorization value is redacted", + filename: "creds-scalar.yaml", + content: "svc-param: \"Authorization=Bearer a,b\"\n", + wantVals: map[string]string{ + "svc-param": "Authorization=", + }, + }, { name: "json config with additional properties ignored", filename: "extra.json", diff --git a/internal/clicfg/binder.go b/internal/clicfg/binder.go index c3bab65..608e4da 100644 --- a/internal/clicfg/binder.go +++ b/internal/clicfg/binder.go @@ -50,7 +50,7 @@ func Bind(flags *pflag.FlagSet, store *Store) ([]FlagBinding, error) { sensitive := false switch f.Name { - case "auth", "bearer", "api-key": + case "auth": sensitive = true } @@ -91,6 +91,9 @@ func Bind(flags *pflag.FlagSet, store *Store) ([]FlagBinding, error) { f.Changed = true binding.Source = source.String() binding.Path = source.Path + if f.Name == "svc-param" { + value = redactServiceParams(value) + } binding.Value = formatValue(value) bindings = append(bindings, binding) }) @@ -104,6 +107,33 @@ func Bind(flags *pflag.FlagSet, store *Store) ([]FlagBinding, error) { return bindings, nil } +// redactServiceParams hides the value of each Authorization service parameter. +// Entries are redacted before joining so commas inside values cannot split a +// credential across entries. +func redactServiceParams(value any) any { + entries, ok := toStringSlice(value) + if !ok { + s, ok := value.(string) + if !ok { + return value + } + entries = []string{s} + } + redacted := make([]string, len(entries)) + for i, entry := range entries { + redacted[i] = redactServiceParam(entry) + } + return redacted +} + +func redactServiceParam(entry string) string { + sep := strings.IndexAny(entry, ":=") + if sep < 0 || !strings.EqualFold(strings.TrimSpace(entry[:sep]), "Authorization") { + return entry + } + return entry[:sep+1] + "" +} + func flagValueString(f *pflag.Flag) string { if sv, ok := f.Value.(pflag.SliceValue); ok { return strings.Join(sv.GetSlice(), ",")