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
| Advisory | Severity | Affected | Issue |
|---|---|---|---|
| GHSA-5qq8-67g6-4h2w | Critical | before 1.19.1 | Namespaced Policy apiCall with method: POST creates objects in any namespace or at cluster scope as Kyverno's service account |
| GHSA-c5qq-7g2q-cpqp | High | before 1.19.1 | Same %2e%2e path trick used to read resources in other namespaces |
| GHSA-59v6-2x73-wfg4 | High | 1.16.0 to 1.19.0 | globalContext.get in a tenant's NamespacedValidatingPolicy reads cluster-wide GlobalContextEntry data |
| GHSA-5cjf-wwfg-pj4c | High | 1.14.0 to 1.19.0 | A PolicyException for an ImageValidatingPolicy ignores its images list and skips signature checks for every image |
| GHSA-q825-p383-r9v5 | High | before 1.19.1 | Legacy 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
- Upgrade to Kyverno 1.19.1. Check the Helm chart version that ships it and confirm the running image afterwards.
- Until then, remove tenant
createandupdaterights onpolicies.kyverno.io. The critical and the cross-namespace read paths both start from a tenant-authored namespaced Policy. - 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.
- 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
imageslist is what you intend, because it now takes effect. - Restrict Kyverno's egress. One advisory describes legacy
apiCall.servicecalls reaching hosts such as169.254.169.254with 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
- GHSA-5qq8-67g6-4h2w: privilege escalation to cluster admin via Policy apiCall urlPath
- GHSA-c5qq-7g2q-cpqp: namespace isolation bypass via percent-encoded path segments
- GHSA-59v6-2x73-wfg4: globalcontext CEL library not namespace-confined
- GHSA-5cjf-wwfg-pj4c: ImageValidatingPolicy exceptions ignore images
- GHSA-q825-p383-r9v5: legacy apiCall service executor bypasses SSRF blocklist
- Kyverno v1.19.1 release