Someone left the master key taped to the inside of a suggestion box
CVE-2026-54523 is a missing-authorization flaw (CWE-862) in Kyverno's new NamespacedMutatingPolicy resource type, introduced in v1.18.0. The CEL-exposed generator.apply(namespace, resources) function accepts an arbitrary namespace argument without validating that it matches the policy's own namespace. A tenant who can create a NamespacedMutatingPolicy in *their* namespace can trick the admission controller — which runs with cluster-wide RBAC — into generating RoleBindings, ConfigMaps, NetworkPolicies, or Secrets in any namespace, including kube-system. This is a complete namespace-isolation bypass leading to cluster-admin escalation. Only v1.18.0 and v1.18.1 are affected; the fix ships in v1.18.2.
The vendor CVSS of 9.6 CRITICAL is technically correct for the described attack scenario: low privileges, network-reachable K8s API, no user interaction, scope-change to cluster-admin. But it overstates the real-world risk for most Kyverno users. The feature that enables this — NamespacedMutatingPolicy — shipped for the first time in April 2026. Many production Kyverno deployments are still on 1.16.x or 1.17.x and are entirely unaffected. Even among 1.18.x adopters, the vulnerability only fires when non-admin tenants have been granted create RBAC on NamespacedMutatingPolicy — a deliberate multi-tenant delegation step, not a default. These compounding prerequisites narrow the blast radius substantially, warranting a downgrade to HIGH 8.2.
5 steps from start to impact.
Authenticate as a namespace tenant
create on namespacedmutatingpolicies.kyverno.io in at least one namespace. This is the entry point — no unauthenticated exploitation is possible.- Valid Kubernetes credentials with NamespacedMutatingPolicy create RBAC
- Cluster running Kyverno v1.18.0 or v1.18.1
- Most clusters restrict policy-object creation to platform teams or cluster-admins
- NamespacedMutatingPolicy is a new resource type (v1.18.0+); many tenants have never been granted this RBAC
create events on namespacedmutatingpolicies.kyverno.ioCraft a malicious NamespacedMutatingPolicy
generator.apply("kube-system", [{"apiVersion": "rbac.authorization.k8s.io/v1", "kind": "RoleBinding", ...}]) inside a matchConditions CEL expression. The CEL boolean expression evaluates the generator call as a side effect when any matching resource event is processed. No special tooling is required — kubectl and a text editor suffice.- Knowledge of the
generator.apply()CEL function signature - Ability to craft YAML and apply it via
kubectl
- The exploit is straightforward for anyone who reads the advisory, but requires Kyverno CEL familiarity
- Admission policy linting tools may flag unusual generator.apply targets
Apply the policy to the attacker's namespace
kubectl apply -f malicious-nmpol.yaml -n attacker-ns. The Kyverno admission controller validates CEL compilation but does not enforce namespace scope on generator.apply() arguments (pkg/cel/policies/mpol/validate.go — the guard present in apicall.go:73-82 is absent). The policy is accepted.- kubectl or equivalent API access to the attacker's namespace
- Kyverno webhook must be healthy and processing admission requests
- Clusters with strict admission review (e.g., secondary OPA policies gating Kyverno policy creation) would block this
Trigger the webhook to execute generator.apply()
matchConditions — for example, a ConfigMap in their own namespace. When the Kyverno admission webhook intercepts this event, it evaluates the CEL expression, which invokes generator.apply("kube-system", ...). The admission controller's ServiceAccount — which holds cluster-wide RBAC — creates the attacker-specified resources in the target namespace.- Any matching Kubernetes API event (ConfigMap create, Pod create, etc.)
- Kyverno background controller ServiceAccount retains default cluster-wide permissions
- If the Kyverno ServiceAccount's RBAC has been tightened to exclude RoleBinding creation, the generate call fails
- Namespace-level ResourceQuotas in kube-system may block unexpected object creation
Escalate to cluster-admin
cluster-admin (or any chosen ClusterRole) in the target namespace. If kube-system is targeted, the attacker now has the RBAC foundation to read Secrets, modify Deployments, and pivot to full cluster control. From here, standard post-exploitation applies: dumping cloud-provider IRSA/workload-identity tokens, container escape to nodes, or lateral movement to other clusters.- RoleBinding successfully created in target namespace
- EDR/CWPP agents on nodes may detect post-exploitation activity
- Cloud-provider guardrails (e.g., GKE Workload Identity restrictions) limit blast radius beyond the cluster
cluster-admin RoleBinding creation outside change-management windows; CWPP agents flag unusual ServiceAccount token usagecreate verb on namespacedmutatingpolicies.kyverno.io from all ClusterRoleBindings and RoleBindings granted to non-platform-team identities. This eliminates the prerequisite for exploitation entirely. Deploy within 30 days per noisgate mitigation SLA for HIGH severity. Use kubectl get clusterrolebindings,rolebindings -A -o json | jq to audit current grants.metadata.namespace. This is a defense-in-depth control that catches exploitation attempts even if RBAC is misconfigured. Deploy within 30 days per noisgate mitigation SLA.generator.apply() in the nmpol CEL compiler. Schedule within 180 days per noisgate remediation SLA for HIGH severity. Test in staging first — the patch modifies admission webhook behavior and could reject previously-accepted policies that legitimately used generator.apply() within the same namespace.create, update, and patch events for namespacedmutatingpolicies.kyverno.io at the RequestResponse level. Forward to SIEM. This does not prevent exploitation but enables detection of attempts. Deploy within 30 days.- Kubernetes NetworkPolicies — The attack operates at the API server level via admission webhooks, not over pod-to-pod network traffic. NetworkPolicies are irrelevant.
- Pod Security Standards / Pod Security Admission — PSA controls pod-level security context (runAsNonRoot, capabilities, etc.). It has no authority over policy-object creation or CEL expression content.
- Container image scanning (Trivy, Grype, Snyk) — The vulnerability is in Kyverno's admission controller logic, not in a container image dependency. Image scanners won't flag it unless they specifically scan the Kyverno controller image for the vulnerable Go module version.
- Kubernetes RBAC alone without auditing the specific resource — Generic RBAC reviews that focus on core API resources (pods, secrets, deployments) will miss NamespacedMutatingPolicy grants because it's a Kyverno CRD, not a core resource.
The supporting signals.
| In-the-wild status | No known exploitation. Not listed in CISA KEV. No GreyNoise or Shadowserver tags. The Kubernetes API server is rarely internet-exposed, limiting opportunistic scanning. |
|---|---|
| Proof of concept | No public PoC on pocindex.io, GitHub, ExploitDB, or Nuclei. The advisory contains enough detail to construct a working exploit (malicious NamespacedMutatingPolicy YAML), but no weaponized tooling has been published. Reproduction is straightforward for anyone with Kyverno CEL knowledge. |
| EPSS | 0.00466 (0.466% probability of exploitation in the next 30 days). This is low, reflecting the narrow affected-version window and Kubernetes-specific attack surface. |
| KEV status | Not listed. No CISA KEV entry as of 2026-10-07. |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N — Network vector because the K8s API is the entry point; PR:L because you need an authenticated tenant identity; Scope Changed because the admission controller's cluster-wide RBAC is the execution context, not the attacker's own permissions. |
| Affected versions | Kyverno v1.18.0 and v1.18.1 only. All prior release trains (1.17.x, 1.16.x, etc.) are unaffected because NamespacedMutatingPolicy did not exist. |
| Fixed version | Kyverno v1.18.2 (released July 2026). The patch adds namespace-scope validation to generator.apply() in the nmpol CEL compiler, matching the existing guard in apicall.go:73-82. |
| Researcher | Reported by @0xVijay. Reproduced 2026-05-20; advisory published 2026-07-13; clarified 2026-07-22. |
| Exposure data | Kubernetes API servers are rarely internet-exposed. Shodan shows ~38,000 publicly reachable K8s API endpoints globally, but the vast majority use RBAC and OIDC. The relevant attack surface is internal — authenticated tenants in multi-tenant clusters running Kyverno 1.18.x. |
| Kyverno adoption | CNCF-graduated (March 2026). 50+ listed adopters including LinkedIn (230+ clusters), Bloomberg, Coinbase, Deutsche Telekom, Spotify, US DoD Platform One. Estimated tens of thousands of clusters. However, v1.18.x adoption since its April 2026 release is a fraction of the total installed base. |
Sources.
- GitHub Security Advisory GHSA-79gf-7frw-68m9
- Nirmata blog — Critical Kyverno Vulnerability CVE-2026-54523
- Kyverno v1.18.2 release notes
- OSV — GHSA-79gf-7frw-68m9
- GitLab Advisory Database — CVE-2026-54523
- CNCF — Kyverno Graduation Announcement
- Go Vulnerability Database — GO-2026-6296
- The Hacker Wire — Kyverno Critical Cross-Namespace Resource Creation
Why this verdict
- Narrow affected version window: Only v1.18.0 and v1.18.1 are vulnerable — two releases spanning roughly 3 months (April–July 2026). The majority of production Kyverno deployments are on 1.16.x or 1.17.x and are completely unaffected. This dramatically reduces the exposed population compared to a flaw in a long-lived feature.
- RBAC prerequisite narrows the attacker pool: Exploitation requires
createpermissions onnamespacedmutatingpolicies.kyverno.io, which is a deliberate delegation to namespace owners in multi-tenant setups — not a default grant. Single-tenant or platform-team-only clusters never expose this surface. - Authenticated-only, internal surface: PR:L means a valid Kubernetes identity is required. Kubernetes API servers are overwhelmingly internal-facing (~38K public globally per Shodan, vs. millions of clusters total). The attacker is already inside the cluster boundary — this is a privilege-escalation primitive, not an initial-access vector.
- No exploitation evidence or weaponization: Zero PoCs, zero KEV, EPSS 0.466%, no GreyNoise/Shadowserver tags. The advisory provides a conceptual roadmap but no ready-made tooling exists.
- Role multiplier: Kyverno is an admission controller — a control-plane-adjacent component deployed in production Kubernetes clusters. In multi-tenant clusters where tenants have been granted NamespacedMutatingPolicy creation RBAC (the canonical use case for this feature), the documented chain succeeds and the blast radius is cluster-wide — full namespace-isolation bypass to cluster-admin, with downstream pivot potential to cloud-provider credentials, node access, and lateral movement to other clusters. This is fleet-scale impact within the Kubernetes boundary. However, because the feature is brand new (v1.18.0, April 2026) and the multi-tenant delegation pattern is the specific enabler, the realistic fraction of Kyverno installs in the high-value role is estimated at 5–15% of the total installed base — above the 1% floor threshold but well below 'canonical.' This sets a HIGH floor, not CRITICAL.
Why not higher?
CRITICAL would be appropriate if this affected all Kyverno versions or if the vulnerable feature were enabled by default in a way that exposed every installation. The narrow two-release window (v1.18.0–v1.18.1), the requirement for deliberate RBAC delegation of NamespacedMutatingPolicy creation to tenants, and the absence of any exploitation evidence or public PoC all compound to reduce urgency below the CRITICAL threshold. The attack requires an already-authenticated insider — this is not an unauthenticated remote chain.
Why not lower?
MEDIUM would understate the risk because when the prerequisites are met, the attack is trivially simple (a single kubectl apply) and the impact is cluster-admin — the maximum possible privilege in Kubernetes. Kyverno's role as an admission controller means the blast radius is not bounded to a single workload; it spans the entire cluster including kube-system. The CNCF graduation and adoption by organizations like the US DoD Platform One, LinkedIn, and Bloomberg means the affected population, while narrow, includes high-value targets where cluster compromise has severe consequences.
Crowdsourced verification payload.
Run this script on any host with kubectl configured to access the target cluster and jq installed. It checks the Kyverno deployment version and audits RBAC for NamespacedMutatingPolicy creation grants. Requires get permissions on deployments in the kyverno namespace and cluster-wide read on rolebindings/clusterrolebindings. Example: bash check_cve_2026_54523.sh
#!/usr/bin/env bash
# check_cve_2026_54523.sh — Detect CVE-2026-54523 exposure in Kyverno
# Checks: (1) Kyverno version, (2) NamespacedMutatingPolicy RBAC grants
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
set -euo pipefail
RESULT="UNKNOWN"
VULN_VERSIONS=("v1.18.0" "v1.18.1" "1.18.0" "1.18.1")
# --- Step 1: Detect Kyverno version ---
KYVERNO_NS="${KYVERNO_NS:-kyverno}"
VERSION=$(kubectl get deployment -n "$KYVERNO_NS" -l app.kubernetes.io/component=admission-controller \
-o jsonpath='{.items[0].spec.template.spec.containers[0].image}' 2>/dev/null || true)
if [ -z "$VERSION" ]; then
echo "[!] Could not detect Kyverno admission-controller deployment in namespace '$KYVERNO_NS'."
echo " Set KYVERNO_NS env var if Kyverno is in a different namespace."
echo "UNKNOWN"
exit 2
fi
# Extract version tag from image string (e.g., ghcr.io/kyverno/kyverno:v1.18.1)
VER_TAG=$(echo "$VERSION" | grep -oP '(?<=:)v?[0-9]+\.[0-9]+\.[0-9]+' || echo "")
echo "[*] Kyverno image: $VERSION"
echo "[*] Detected version tag: ${VER_TAG:-unable to parse}"
IS_VULN_VERSION=false
for v in "${VULN_VERSIONS[@]}"; do
if [ "$VER_TAG" = "$v" ]; then
IS_VULN_VERSION=true
break
fi
done
if [ "$IS_VULN_VERSION" = true ]; then
echo "[!] VULNERABLE VERSION DETECTED: $VER_TAG (affected: v1.18.0, v1.18.1)"
RESULT="VULNERABLE"
else
echo "[+] Version $VER_TAG is not in the affected range (v1.18.0, v1.18.1)."
RESULT="PATCHED"
fi
# --- Step 2: Check for NamespacedMutatingPolicy RBAC grants to non-system subjects ---
echo ""
echo "[*] Auditing RBAC for namespacedmutatingpolicies.kyverno.io create grants..."
GRANTS=$(kubectl get clusterrolebindings,rolebindings -A -o json 2>/dev/null | \
jq -r '
[.items[] |
.subjects[]? as $subj |
select(.roleRef.kind == "ClusterRole" or .roleRef.kind == "Role") |
{binding: .metadata.name, ns: .metadata.namespace, subject: $subj.name, kind: $subj.kind, role: .roleRef.name}
] | .[] | "\(.binding) -> \(.subject) (\(.kind)) via \(.role) in ns=\(.ns // "cluster-wide")"' 2>/dev/null || echo "")
NMPOL_ROLES=$(kubectl get clusterroles,roles -A -o json 2>/dev/null | \
jq -r '.items[] | select(.rules[]? | select(
(.apiGroups[]? == "kyverno.io" or .apiGroups[]? == "*") and
(.resources[]? == "namespacedmutatingpolicies" or .resources[]? == "*") and
(.verbs[]? == "create" or .verbs[]? == "*")
)) | .metadata.name' 2>/dev/null || echo "")
if [ -n "$NMPOL_ROLES" ]; then
echo "[!] Roles/ClusterRoles granting create on namespacedmutatingpolicies:"
echo "$NMPOL_ROLES" | while read -r role; do
echo " - $role"
done
if [ "$RESULT" = "VULNERABLE" ]; then
echo "[!!] VULNERABLE: Kyverno $VER_TAG + RBAC grants for NamespacedMutatingPolicy creation exist."
fi
else
echo "[+] No roles granting create on namespacedmutatingpolicies found."
if [ "$RESULT" = "VULNERABLE" ]; then
echo "[*] Version is vulnerable but no RBAC grants detected — exploitation unlikely without RBAC change."
fi
fi
echo ""
echo "$RESULT"
exit $([ "$RESULT" = "PATCHED" ] && echo 0 || ([ "$RESULT" = "VULNERABLE" ] && echo 1 || echo 2))