Safeguard
Cloud Security•

Kyverno 1.19.1: Namespaced Policies That Escalate to Cluster Admin

Kyverno's 10 September advisories: a tenant's namespaced Policy could make Kyverno create cluster objects, and image verification exceptions exempted every image.

Safeguard Security Team
6 min read

On 10 September 2026 Kyverno released v1.19.1 and published a set of advisories, one rated critical. The critical one starts from a common multi-tenant setup: a team can create Kyverno Policy objects in its own namespace. With a percent-encoded path in an apiCall, that team could make Kyverno send a POST as its own admission controller service account, outside the team's namespace. The advisory's first demonstration creates a MutatingWebhookConfiguration, which leads to cluster admin.

None of the advisories had a CVE ID assigned when published.

What's affected

AdvisorySeverityAffectedIssue
GHSA-5qq8-67g6-4h2wCriticalbefore 1.19.1Namespaced Policy apiCall with method: POST creates objects in any namespace or at cluster scope as Kyverno's service account
GHSA-c5qq-7g2q-cpqpHighbefore 1.19.1Same %2e%2e path trick used to read resources in other namespaces
GHSA-59v6-2x73-wfg4High1.16.0 to 1.19.0globalContext.get in a tenant's NamespacedValidatingPolicy reads cluster-wide GlobalContextEntry data
GHSA-5cjf-wwfg-pj4cHigh1.14.0 to 1.19.0A PolicyException for an ImageValidatingPolicy ignores its images list and skips signature checks for every image
GHSA-q825-p383-r9v5Highbefore 1.19.1Legacy apiCall.service and GlobalContextEntry calls skip the SSRF blocklist and send Kyverno's token to any host

The fix for all of them is 1.19.1. There is no patched 1.18 release in this batch, so 1.18 clusters need to move to 1.19.1.

Why the path trick works. For namespaced policies, Kyverno checks that an apiCall.urlPath points into the policy's own namespace. The check cleaned the path lexically and matched it against a /namespaces/NAME/ pattern. The request itself percent-decoded the path. So %2e%2e segments passed the check as harmless text and became .. when the request was sent, walking out of the namespace. Because Kyverno makes the call with its own service account, the tenant borrows that identity.

The advisory describes two attack paths. Path A registers a mutating webhook that rewrites every admitted object, with default chart values. Path B posts a PolicyException into the kyverno namespace, which the tenant can't write to directly, to switch off an enforcing policy for their own namespace. The reporter is Artem Cherezov.

The image verification bug is a quieter problem. An admin writes a PolicyException meant to exempt one trusted image from signature checks. In affected versions, any matching exception skipped verification for the whole resource, so an unsigned image in the same pod, or in a pod with no trusted image at all, got through.

How to check if you're exposed

Kyverno version:

kubectl get deploy -n kyverno -o jsonpath='{range .items[*]}{.metadata.name}{"  "}{.spec.template.spec.containers[0].image}{"\n"}{end}'

Who can create namespaced Kyverno policies? If any non-admin group can, the critical issue applies:

kubectl auth can-i create policies.kyverno.io -n team-a \
  --as=system:serviceaccount:team-a:deployer

Look for namespaced policies with apiCall.urlPath values that contain encoded dots or traversal:

kubectl get policies.kyverno.io -A -o json | jq -r '
  .items[]
  | .metadata.namespace as $ns | .metadata.name as $n
  | .spec.rules[]?.context[]?.apiCall
  | select(. != null)
  | select((.urlPath // "") | test("%2e|\\.\\."; "i"))
  | "\($ns)/\($n)  method=\(.method // "GET")  urlPath=\(.urlPath)"'

Review mutating webhooks and which field manager created them. Kyverno's own are prefixed kyverno-. Anything else you can't account for needs a look:

kubectl get mutatingwebhookconfigurations -o json | jq -r '
  .items[] | "\(.metadata.name)  managers=\([.metadata.managedFields[]?.manager] | unique | join(","))"'

And list PolicyExceptions in the Kyverno namespace, checking each against your change records:

kubectl get policyexceptions -n kyverno

What to do now

  1. Upgrade to Kyverno 1.19.1. Check the Helm chart version that ships it and confirm the running image afterwards.
  2. Until then, remove tenant create and update rights on policies.kyverno.io. The critical and the cross-namespace read paths both start from a tenant-authored namespaced Policy.
  3. Hunt for leftovers. A webhook or PolicyException created through this path stays after you patch. Delete anything from the checks above that has no owner.
  4. Review image verification exceptions. On affected versions, an exception scoped to one image exempted every image on the resource. After upgrading, confirm each exception's images list is what you intend, because it now takes effect.
  5. Restrict Kyverno's egress. One advisory describes legacy apiCall.service calls reaching hosts such as 169.254.169.254 with Kyverno's token attached. A NetworkPolicy limiting the Kyverno namespace's outbound traffic to the API server and known services caps that.

Why it matters

A policy engine is an admission webhook with a powerful service account, and letting tenants author policies hands them a way to make that account act. Every feature that lets a namespaced policy reach outside its namespace, whether an API call, a CEL library or a global context, needs the same confinement. These advisories show the gaps appear where two pieces of code read the same input differently: the check normalized the path one way and the HTTP request decoded it another.

The image exception bug carries a separate lesson. Exceptions are where verified supply chain controls quietly lose their teeth. Treat each one as a change to the control itself, and test that an unrelated unsigned image is still blocked after you add it.

Try it yourself

In a staging cluster on 1.19.1, create a PolicyException scoped to one signed image for an ImageValidatingPolicy. Deploy a pod that carries that image plus an unsigned one, and confirm the admission webhook rejects it. Then run the same test on your production version to see which behavior you've been relying on.

Click through it at your own pace, or open it full screen.

Quick check

What permission does an attacker need for the critical Kyverno advisory?

Create rights on Kyverno Policy objects in their own namespace, plus normal workload rights. The advisory's tenant used the built-in edit role and create on policies.kyverno.io.

Why does %2e%2e get past the namespace check?

The check cleaned the path lexically, where %2e%2e is just text, while the HTTP request percent-decoded it into ... The two disagreed about which namespace the path pointed at.

Is a Kyverno 1.18 cluster covered by a backport?

Not in this batch. The advisories list 1.19.1 as the fixed version, so 1.18 clusters need to upgrade to 1.19.1.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.