CVE-2026-65835: Capsule: Incomplete fix of CVE-2026-22872: TenantResource RawItems and Generators still allow cluster-scoped resource creation (cross-tenant privilege escalation)
Summary CVE-2026-22872 (GHSA-qjjm-7j9w-pw72) reported that a Tenant Owner could create cluster-scoped resources (e.g. ClusterRole, ValidatingWebhookConfiguration) through a TenantResource, because the controller applies them with its cluster-admin ServiceAccount and SetNamespace is ineffective for cluster-scoped kinds. The v0.13.0 fix added a cluster-scope rejection guard, but only on the NamespacedItems selection path (ResourceReference.LoadResources -> IsNamespacedGVK, error "cluster-scoped kind ... is not allowed"). The RawItems create path — the exact vector the original advisory named — and the Generators path were not given this guard. The vulnerability therefore persists in all releases v0.13.0 through v0.13.7 and on trunk HEAD (8d89d6865d).
Details TenantResource reconcile flow: - internal/controllers/resources/namespaced.go reconcile() obtains the apply client via loadClient(); by default (impersonation off, no Spec.ServiceAccount) this is the manager client whose SA is bound to cluster-admin (charts/capsule/templates/rbac.yaml:488-501, {fullname}-manager-rolebinding -> roleRef cluster-admin). - Collector.Collect() (collect.go) processes spec.RawItems via handleRawItem and spec.Generators via handleGeneratorItem.
handleRawItem (collect.go:406-425, trunk HEAD — byte-identical to v0.13.0): go tmplString := tpl.FastTemplate(string(item.Raw), opts.Iterator.FastContext) obj := &unstructured.Unstructured{} unstructured.UnstructuredJSONScheme.Decode([]byte(tmplString), nil, obj) if ns != nil { obj.SetNamespace(ns.Name) } // ONLY mitigation return obj, nil // NO IsNamespacedGVK / allowClusterScoped guard handleGeneratorItem (collect.go:382-404) is the same: it only SetNamespaces on rendered objects.
The accumulated objects flow to pkg/api/processor/processorfunc.go Reconcile() -> Apply() -> clt.PatchApply(ctx, c, obj, ...) (line 167/378) with no scope check at any point.
By contrast, CollectNamespacedItems (collect.go:308) calls item.LoadResources(..., allowClusterScoped=false), and pkg/template/reference.go:105-107 enforces: go if !allowClusterScoped && !isNamespaced { return nil, fmt.Errorf("cluster-scoped kind %s/%s is not allowed", ...) } So the guard the fix added is real, but it sits on a different (selection) path than the one the CVE described (RawItems create path). For cluster-scoped kinds, SetNamespace is ignored by the Kubernetes API server, so the object is created cluster-wide by the cluster-admin client.
Parent-fix diff confirmation: in v0.12.4 the RawItems handler in processor.go was the vulnerable code (obj.SetNamespace(ns.Name) then createOrUpdate via r.client). v0.13.0 refactored this into collect.go handleRawItem but left it without the new guard.
Proof of Concept A self-contained in-process Go test (incompletefixpoctest.go), run against trunk HEAD with go1.26.4, proves the asymmetry: - TestRawItemPathNoClusterScopeGuard: feeds a ClusterRole rawItem to handleRawItem -> object returned unchanged, no rejection (PASS). - TestNamespacedItemsPathHasClusterScopeGuard: feeds the same ClusterRole kind to LoadResources(allowClusterScoped=false) -> rejected with "cluster-scoped kind rbac.authorization.k8s.io/v1/ClusterRole is not allowed" (PASS).
VULNERABLE: RawItems path accepted cluster-scoped rbac.authorization.k8s.io/v1/ClusterRole; metadata.namespace="tenant-ns" (ignored by API server for cluster-scoped kinds) GUARDED: NamespacedItems path correctly rejected cluster-scoped kind: cluster-scoped kind rbac.authorization.k8s.io/v1/ClusterRole is not allowed
End-to-end (cluster) reproduction: 1. Deploy capsule (default Helm) with rbac.resources.create=true (the opt-in that exposes TenantResources to tenant owners; the configuration the original CVE applies to). 2. As a Tenant Owner, create in a tenant namespace: yaml apiVersion: capsule.clastix.io/v1beta2 kind: TenantResource metadata: {name: pwn, namespace: <tenant-ns>} spec: resources: - namespaceSelector: {matchLabels: {capsule.clastix.io/tenant: <tenant>}} rawItems: - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: {name: tenant-escalation} rules: [{apiGroups: [""], resources: [""], verbs: [""]}] 3. Observe the cluster-scoped ClusterRole tenant-escalation is created by the cluster-admin controller, despite the tenant owner lacking cluster RBAC to create it. Swap in ValidatingWebhookConfiguration to intercept/exfiltrate cluster-wide Secrets.
Impact A Tenant Owner (namespace-scoped) escalates to cluster-admin-equivalent privileges and can compromise all tenants and the cluster control plane. Identical impact to CVE-2026-22872; the v0.13.0 remediation does not close the RawItems/Generators vector.
Remediation Apply the same IsNamespacedGVK / allowClusterScoped rejection inside handleRawItem and handleGeneratorItem — or centrally in Collector.AddToAccumulation / processor.Apply — so the create path enforces the same cluster-scope policy as the selection path. (GlobalTenantResource shares the path but is not a privesc — cluster-admin-only to create.)
Other sources
Capsule is a multi-tenancy and policy-based framework for Kubernetes. From 0.13.0 until 0.13.8, after the incomplete CVE-2026-22872 fix, TenantResource RawItems and Generators in internal/controllers/resources/collect.go, including handleRawItem and handleGeneratorItem, did not apply the ResourceReference.LoadResources and IsNamespacedGVK cluster-scoped resource rejection guard used by NamespacedItems, allowing a Tenant Owner to create cluster-scoped resources such as ClusterRole or ValidatingWebhookConfiguration through the cluster-admin controller client. This issue is fixed in version 0.13.8.
— MITRE
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
go/github.com/projectcapsule/capsuleto a version that resolves this vulnerability.Fixed in 0.13.8 - Upgrade
Upgrade to a fixed release to a version that resolves this vulnerability.
Fixed in 0.13.8 - Configuration
If using the default Helm deployment, note that the opt-in `rbac.resources.create=true` exposes TenantResources; ensure it is not enabled unless required, because TenantResource RawItems/Generators can create cluster-scoped resources when the fix is not present.
Capsule Helm chart (default) rbac.resources.create = true - Compensating control
Temporarily restrict Tenant Owner capabilities for TenantResource usage (e.g., avoid granting permissions that allow creation of TenantResource RawItems/Generators) until Capsule is upgraded to v0.13.8.
Event History
Frequently Asked Questions
What is the severity of CVE-2026-65835?
The severity of CVE-2026-65835 is classified as medium with a score of 6.6.
What does CVE-2026-65835 affect?
CVE-2026-65835 affects the Capsule framework for Kubernetes, specifically from versions 0.13.0 to 0.13.8.
How do I fix CVE-2026-65835?
To fix CVE-2026-65835, upgrade Capsule to version 0.13.9 or later.
What kind of vulnerability is CVE-2026-65835?
CVE-2026-65835 is a cross-tenant privilege escalation vulnerability due to an incomplete fix of CVE-2026-22872.
What does the incomplete fix in CVE-2026-65835 allow?
The incomplete fix in CVE-2026-65835 allows the creation of cluster-scoped resources through TenantResource RawItems and Generators.