diff --git a/CHANGELOG.md b/CHANGELOG.md index f770101674..8e602db57a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ * [BUGFIX] Ring: Fix ring token conflict resolution only applied to updated instance and make constantly token conflict check during instance observe period. * [BUGFIX] Distributor: Fix a panic (`slice bounds out of range`) in the stream push path when the context deadline expires while the worker goroutine is still marshalling a `WriteRequest`. #7541 * [BUGFIX] Query Frontend: Fix native histogram responses not being handled correctly in `minTime()` sort ordering for split_by_interval merge. #7555 +* [BUGFIX] Security: Fix `SecretStringSliceCSV.Set()` accepting empty entries from stray or trailing commas (e.g. `newkey,`). #7587 ## 1.21.0 2026-04-24 diff --git a/pkg/util/flagext/secretstringslicecsv.go b/pkg/util/flagext/secretstringslicecsv.go index 0a1dea7834..e48fc070c4 100644 --- a/pkg/util/flagext/secretstringslicecsv.go +++ b/pkg/util/flagext/secretstringslicecsv.go @@ -1,6 +1,9 @@ package flagext -import "strings" +import ( + "errors" + "strings" +) // SecretStringSliceCSV is a slice of strings that is parsed from a comma-separated string. // It implements flag.Value and yaml Marshalers, but masks the value when marshaled to YAML @@ -15,12 +18,25 @@ func (v SecretStringSliceCSV) String() string { } // Set implements flag.Value +// Each comma-separated entry is trimmed of surrounding whitespace. +// Empty entries (after trimming) are rejected with an error. func (v *SecretStringSliceCSV) Set(s string) error { if s == "" { v.values = nil return nil } - v.values = strings.Split(s, ",") + parts := strings.Split(s, ",") + values := make([]string, 0, len(parts)) + for _, p := range parts { + p = strings.TrimSpace(p) + if p == "" { + // Do not include the original input value in the error message + // to avoid accidentally exposing secret values. + return errors.New("invalid value: empty entry after trimming") + } + values = append(values, p) + } + v.values = values return nil } diff --git a/pkg/util/flagext/secretstringslicecsv_test.go b/pkg/util/flagext/secretstringslicecsv_test.go index 58772e28c1..fad17c3f05 100644 --- a/pkg/util/flagext/secretstringslicecsv_test.go +++ b/pkg/util/flagext/secretstringslicecsv_test.go @@ -44,4 +44,32 @@ func TestSecretStringSliceCSV(t *testing.T) { require.NoError(t, s.Keys.Set("")) assert.Equal(t, []string(nil), s.Keys.Value()) }) + + t.Run("trailing comma is rejected", func(t *testing.T) { + var s TestStruct + err := s.Keys.Set("newkey,") + require.Error(t, err, "trailing comma must produce an error") + assert.Nil(t, s.Keys.Value(), "values must not be updated on error") + }) + + t.Run("leading comma is rejected", func(t *testing.T) { + var s TestStruct + require.Error(t, s.Keys.Set(",newkey")) + }) + + t.Run("double comma is rejected", func(t *testing.T) { + var s TestStruct + require.Error(t, s.Keys.Set("newkey,,oldkey")) + }) + + t.Run("whitespace-only entry is rejected", func(t *testing.T) { + var s TestStruct + require.Error(t, s.Keys.Set("newkey, ,oldkey")) + }) + + t.Run("surrounding whitespace is trimmed from valid entries", func(t *testing.T) { + var s TestStruct + require.NoError(t, s.Keys.Set(" key1 , key2 ")) + assert.Equal(t, []string{"key1", "key2"}, s.Keys.Value()) + }) }