← Back to Feed CACHED · 2026-10-06 23:41:51 · CACHE_KEY CVE-2026-54523
CVE-2026-54523 · CWE-862 · Disclosed 2026-08-26

Kyverno NamespacedMutatingPolicy generator.apply() cross-namespace resource generation bypass

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

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.

"Namespace jail-break in Kyverno 1.18.x lets any tenant escalate to cluster-admin."
02 · The Attack Path

5 steps from start to impact.

STEP 01

Authenticate as a namespace tenant

The attacker must hold a valid Kubernetes identity (ServiceAccount token, OIDC token, or client certificate) with RBAC granting create on namespacedmutatingpolicies.kyverno.io in at least one namespace. This is the entry point — no unauthenticated exploitation is possible.
Conditions required:
  • Valid Kubernetes credentials with NamespacedMutatingPolicy create RBAC
  • Cluster running Kyverno v1.18.0 or v1.18.1
Where this breaks in practice:
  • 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
Detection/coverage: Kubernetes audit logs capture create events on namespacedmutatingpolicies.kyverno.io
STEP 02

Craft a malicious NamespacedMutatingPolicy

The attacker writes a policy YAML that embeds 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.
Conditions required:
  • Knowledge of the generator.apply() CEL function signature
  • Ability to craft YAML and apply it via kubectl
Where this breaks in practice:
  • 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
Detection/coverage: OPA/Gatekeeper or a ValidatingAdmissionPolicy can reject NamespacedMutatingPolicy objects referencing namespaces outside the creator's scope
STEP 03

Apply the policy to the attacker's namespace

The attacker runs 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.
Conditions required:
  • kubectl or equivalent API access to the attacker's namespace
Where this breaks in practice:
  • 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
Detection/coverage: Falco or Kubernetes audit-log alerting on NamespacedMutatingPolicy creation events
STEP 04

Trigger the webhook to execute generator.apply()

The attacker creates a resource that matches the policy's 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.
Conditions required:
  • Any matching Kubernetes API event (ConfigMap create, Pod create, etc.)
  • Kyverno background controller ServiceAccount retains default cluster-wide permissions
Where this breaks in practice:
  • 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
Detection/coverage: Kubernetes audit logs show the Kyverno ServiceAccount creating RoleBindings in kube-system — a highly anomalous signal
STEP 05

Escalate to cluster-admin

The generated RoleBinding grants the attacker's identity 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.
Conditions required:
  • RoleBinding successfully created in target namespace
Where this breaks in practice:
  • 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
Detection/coverage: SIEM alerting on cluster-admin RoleBinding creation outside change-management windows; CWPP agents flag unusual ServiceAccount token usage
03 · Compensating Control

1
HIGH 8.2→LOW 2.5
SEVERITY REDUCED
Revoke NamespacedMutatingPolicy create RBAC from non-admin tenants — Remove or restrict create 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.
2
HIGH 8.2→MEDIUM 5.0
SEVERITY REDUCED
Deploy a ValidatingAdmissionPolicy blocking cross-namespace generator.apply() — Write a Kubernetes ValidatingAdmissionPolicy (or OPA/Gatekeeper constraint) that rejects any NamespacedMutatingPolicy whose CEL body references a namespace string different from 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.
3
HIGH 8.2→IGNORE 0.0
SEVERITY REDUCED
Upgrade Kyverno to v1.18.2+ — The definitive fix. v1.18.2 adds namespace-scope validation to 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.
4
HIGH 8.2→HIGH 7.5
Enable Kubernetes audit logging for policy objects — Ensure audit policy captures 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.
What doesn't work
  • 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.
04 · Intelligence Metadata

The supporting signals.

In-the-wild statusNo 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 conceptNo 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.
EPSS0.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 statusNot listed. No CISA KEV entry as of 2026-10-07.
CVSS vectorCVSS: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 versionsKyverno 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 versionKyverno 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.
ResearcherReported by @0xVijay. Reproduced 2026-05-20; advisory published 2026-07-13; clarified 2026-07-22.
Exposure dataKubernetes 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 adoptionCNCF-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.

  1. GitHub Security Advisory GHSA-79gf-7frw-68m9
  2. Nirmata blog — Critical Kyverno Vulnerability CVE-2026-54523
  3. Kyverno v1.18.2 release notes
  4. OSV — GHSA-79gf-7frw-68m9
  5. GitLab Advisory Database — CVE-2026-54523
  6. CNCF — Kyverno Graduation Announcement
  7. Go Vulnerability Database — GO-2026-6296
  8. The Hacker Wire — Kyverno Critical Cross-Namespace Resource Creation
05 · The Call

Final Verdict
↓ DOWNGRADED to HIGH (8.2/10)

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 create permissions on namespacedmutatingpolicies.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.

06 · Verification

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

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/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))