Skip to content

Commit 64f322a

Browse files
committed
backend: k8cache: percent-encode '+' in cache key fields to avoid delimiter collision
GenerateKey joins apiGroup, kind, namespace, and contextID with '+' as a delimiter, and DeleteKeys later splits on '+' to strip the namespace via positional indexing. Kubeconfig context names are not restricted by Kubernetes naming rules and may legitimately contain '+', which produces a key with more than 4 segments and breaks the positional namespace stripping in DeleteKeys. This can cause cache invalidation to silently target the wrong key, leaving stale cached API responses served after a write. Escape literal '+' to '%2B' in each field before joining so the only '+' characters remaining in the key are the delimiters themselves. Fixes #6123 Signed-off-by: Siddhi Khandelwal <siddhi.200727@gmail.com>
1 parent 2796a66 commit 64f322a

2 files changed

Lines changed: 20 additions & 2 deletions

File tree

backend/pkg/k8cache/cacheStore.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,19 @@ func GenerateKey(url *url.URL, contextID string) (string, error) {
163163
Context: contextID,
164164
}
165165

166-
// Create a stable representation
167-
raw := fmt.Sprintf("%s+%s+%s+%s", apiGroup, k.Kind, k.Namespace, k.Context)
166+
// Create a stable representation. Each field has any literal "+" escaped
167+
// to "%2B" so it cannot be misread as the "+" delimiter when the key is
168+
// later split apart (see DeleteKeys). This matters most for the context
169+
// field, since kubeconfig context names are not restricted by Kubernetes
170+
// naming rules and may legitimately contain "+".
171+
escape := strings.NewReplacer("+", "%2B").Replace
172+
raw := fmt.Sprintf(
173+
"%s+%s+%s+%s",
174+
escape(apiGroup),
175+
escape(k.Kind),
176+
escape(k.Namespace),
177+
escape(k.Context),
178+
)
168179

169180
return raw, nil
170181
}

backend/pkg/k8cache/cacheStore_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,13 @@ func TestGenerateKey(t *testing.T) {
410410
expectedKey: "",
411411
expectedErr: errors.New("invalid url format"),
412412
},
413+
{
414+
name: "context key containing a literal plus is escaped, not treated as delimiter",
415+
urlPath: url.URL{Path: "/clusters/kind-kind/apis/apps/v1/namespaces/default/deployments"},
416+
contextKey: "prod+cluster",
417+
expectedKey: "apps+deployments+default+prod%2Bcluster",
418+
expectedErr: nil,
419+
},
413420
}
414421
for _, tc := range tests {
415422
t.Run(tc.name, func(t *testing.T) {

0 commit comments

Comments
 (0)