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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
* [BUGFIX] Distributor: Release the push worker pool goroutines on shutdown by stopping the async executor during the stopping phase when `-distributor.num-push-workers` is set. #7602
* [BUGFIX] Querier: Fix flake in integration tests TestQuerierWithStoreGatewayDataBytesLimits and TestQuerierWithBlocksStorageLimits by waiting for the querier to see the store-gateway ACTIVE in the ring before querying. #7614
* [BUGFIX] Ruler: Register xfunctions (xincrease, xrate, xdelta) in the global parser before loading rule files. #7621
* [BUGFIX] Security: Reject empty entries in `-distributor.sign-write-requests-keys` caused by stray or trailing commas (e.g. `newkey,`). Previously these were silently accepted and produced an empty signing key, which downgraded HMAC stream-push authentication to a forgeable signature. Misconfigured flags now fail at process startup; audit your configs before upgrading. #7587

## 1.21.0 2026-04-24

Expand Down
20 changes: 18 additions & 2 deletions pkg/util/flagext/secretstringslicecsv.go
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
}

Expand Down
28 changes: 28 additions & 0 deletions pkg/util/flagext/secretstringslicecsv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
})
}
Loading