diff --git a/cmd/cmd.go b/cmd/cmd.go index 505b0d29c..df7212156 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -223,13 +223,19 @@ func flagsToLogFields(flags *pflag.FlagSet) []z.Field { return fields } -// redact returns a redacted version of the given flag value. It currently supports redacting -// passwords in valid URLs provided in ".*address.*" flags and redacting auth tokens. +// redact returns a redacted version of the given flag value. It supports: +// - Full redaction of auth token flags (e.g., keymanager-auth-token) +// - Value redaction of header flags (e.g., beacon-node-headers, otlp-headers) keeping keys visible +// - Password redaction in URLs for address flags (e.g., loki-addresses) func redact(flag, val string) string { if strings.Contains(flag, "auth-token") { return "xxxxx" } + if strings.Contains(flag, "header") { + return redactHeaderValue(val) + } + if !strings.Contains(flag, "address") { return val } @@ -241,3 +247,14 @@ func redact(flag, val string) string { return u.Redacted() } + +// redactHeaderValue redacts the value portion of a "key=value" header string, +// preserving the key for debuggability. E.g., "Authorization=Basic abc123" becomes "Authorization=xxxxx". +func redactHeaderValue(val string) string { + key, _, ok := strings.Cut(val, "=") + if !ok { + return val + } + + return key + "=xxxxx" +} diff --git a/cmd/cmd_internal_test.go b/cmd/cmd_internal_test.go index a78412005..40d2fe7ab 100644 --- a/cmd/cmd_internal_test.go +++ b/cmd/cmd_internal_test.go @@ -224,6 +224,25 @@ func TestFlagsToLogFields(t *testing.T) { } } +func TestFlagsToLogFieldsRedactsHeaders(t *testing.T) { + set := pflag.NewFlagSet("test", pflag.PanicOnError) + set.StringSlice("beacon-node-headers", nil, "") + err := set.Parse([]string{ + "--beacon-node-headers=Authorization=Basic b2JvbDpzZWNyZXQ=", + }) + require.NoError(t, err) + + for _, field := range flagsToLogFields(set) { + field(func(f zap.Field) { + if f.Key != "beacon-node-headers" { + return + } + require.NotContains(t, f.String, "b2JvbDpzZWNyZXQ=") + require.Contains(t, f.String, "Authorization=xxxxx") + }) + } +} + func TestRedact(t *testing.T) { tests := []struct { name string @@ -249,6 +268,24 @@ func TestRedact(t *testing.T) { value: "https://obol.obol.tech/dv/0x0f481bbd06a596cb3ba569b9de0cbfcf822b209c2d6877c98173df986dd3c0ec", expected: "https://obol.obol.tech/dv/0x0f481bbd06a596cb3ba569b9de0cbfcf822b209c2d6877c98173df986dd3c0ec", }, + { + name: "redact beacon-node-headers value", + flag: "beacon-node-headers", + value: "Authorization=Basic b2JvbDpzZWNyZXQ=", + expected: "Authorization=xxxxx", + }, + { + name: "redact otlp-headers value", + flag: "otlp-headers", + value: "X-Api-Key=secret-api-key-12345", + expected: "X-Api-Key=xxxxx", + }, + { + name: "header without value passes through", + flag: "beacon-node-headers", + value: "X-No-Value", + expected: "X-No-Value", + }, } for _, tt := range tests { diff --git a/cmd/exit_fetch.go b/cmd/exit_fetch.go index 85becd014..6415de57c 100644 --- a/cmd/exit_fetch.go +++ b/cmd/exit_fetch.go @@ -65,13 +65,11 @@ func newFetchExitCmd(runFunc func(context.Context, exitConfig) error) *cobra.Com valPubkPresent := cmd.Flags().Lookup(validatorPubkey.String()).Changed if !valPubkPresent && !config.All { - //nolint:revive,perfsprint // we use our own version of the errors package; keep consistency with other checks. - return errors.New(fmt.Sprintf("%s must be specified when exiting single validator.", validatorPubkey.String())) + return errors.New(validatorPubkey.String() + " must be specified when exiting single validator.") } if config.All && valPubkPresent { - //nolint:revive // we use our own version of the errors package. - return errors.New(fmt.Sprintf("%s should not be specified when %s is, as it is obsolete and misleading.", validatorPubkey.String(), all.String())) + return errors.New(validatorPubkey.String() + " should not be specified when " + all.String() + " is, as it is obsolete and misleading.") } return nil