GHSA-jr6p-8pjj-mfx6

Suggest an improvement
Source
https://github.com/advisories/GHSA-jr6p-8pjj-mfx6
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-jr6p-8pjj-mfx6/GHSA-jr6p-8pjj-mfx6.json
JSON Data
https://api.test.osv.dev/v1/vulns/GHSA-jr6p-8pjj-mfx6
Aliases
Published
2026-07-31T16:53:42Z
Modified
2026-08-18T15:10:45Z
Severity
  • 6.6 (Medium) CVSS_V3 - CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:L CVSS Calculator
Summary
Capsule has an incomplete fix of CVE-2026-22872: TenantResource RawItems and Generators still allow cluster-scoped resource creation (cross-tenant privilege escalation)
Details

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):

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/processor_func.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:

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 (incompletefix_poc_test.go), run against trunk HEAD with go1.26.4, proves the asymmetry:

  • TestRawItemPath_NoClusterScopeGuard: feeds a ClusterRole rawItem to handleRawItem -> object returned unchanged, no rejection (PASS).
  • TestNamespacedItemsPath_HasClusterScopeGuard: 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:
    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.)

Database specific
{
    "cwe_ids": [
        "CWE-269",
        "CWE-863"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-31T16:53:42Z",
    "nvd_published_at": "2026-07-30T20:18:13Z",
    "severity": "MODERATE"
}
References

Affected packages

Go / github.com/projectcapsule/capsule

Package

Name
github.com/projectcapsule/capsule
View open source insights on deps.dev
Purl
pkg:golang/github.com/projectcapsule/capsule

Affected ranges

Type
SEMVER
Events
Introduced
0.13.0
Fixed
0.13.8

Database specific

last_known_affected_version_range
"<= 0.13.7"
source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-jr6p-8pjj-mfx6/GHSA-jr6p-8pjj-mfx6.json"