On April 15, a new Kyverno security advisory was published: GHSA-cvq5-hhx3-f99p. A namespace admin can author a Kyverno Policy whose configMap.namespace field points at any namespace in the cluster, and Kyverno's admission controller will happily fetch that ConfigMap using its cluster-privileged ServiceAccount and pipe the contents back to the caller through a mutation annotation. Cross-namespace read, RBAC bypassed, CVSS 7.7.
This is the eighth advisory in 2026 of the same shape.
The reporter, @jrey8343, found this one by noticing that the January fix for CVE-2026-22039 had been applied to the apiCall context loader but not to the configMap context loader in the next subdirectory over. Same class of bug, different file, still unpatched until two days ago. Jim Bugwadia shipped the fix in PR #15850 on April 15, with cherry-picks to release-1.17 and release-1.16 merged within minutes. The response was fast. The pattern it sits inside is harder to fix.
The pattern
Ten security advisories have been published against Kyverno in 2026 so far. Eight are variants of the same architectural trap:
- A user creates a namespaced Policy — something their tenant admin role lets them do in a multi-tenant cluster.
- That Policy contains a field the user controls: a URL path, a ConfigMap namespace, a bearer-token header, a service address.
- Kyverno's admission controller, running with a cluster-privileged ServiceAccount, resolves that field at admission time.
- Data crosses a tenant boundary. From kube-system. From another namespace. From the cloud metadata endpoint. From the admission controller's own bearer token.
Here's the list:
| Date | Advisory | What got weaponized |
|---|---|---|
| Jan 27 | GHSA-8p9x-46gm-qfx2 | apiCall.urlPath — cross-namespace privilege escalation (CVE-2026-22039) |
| Apr 13 | GHSA-qr4g-8hrp-c4rw | apiCall.service.url — SSRF |
| Apr 13 | GHSA-rggm-jjmc-3394 | CEL http.Get/http.Post — SSRF (CVE-2026-4789) |
| Apr 13 | GHSA-fmqp-4wfc-w3v7 | apiCall.service.url — independent report, same shape as qr4g |
| Apr 13 | GHSA-q93q-v844-jrqp | apiCall auto-injects the controller's SA bearer token (CVE-2026-40868) |
| Apr 15 | GHSA-8wfp-579w-6r25 | SA token injection — independent report, same shape as q93q |
| Apr 15 | GHSA-f9g8-6ppc-pqq4 | Unvalidated apiCall.service.url + auto-injected SA token — combines qr4g and q93q |
| Apr 15 | GHSA-cvq5-hhx3-f99p | configMap.namespace — cross-namespace read |
Eight advisories. At the root-cause level they collapse to five distinct bug classes: the January apiCall.urlPath fix, apiCall.service.url SSRF, CEL HTTP library SSRF, SA bearer-token auto-injection in the apiCall executor, and this week's configMap.namespace read. Several of the April advisories are independent reports of the same underlying issue — three separate reporters turned up the apiCall.service.url SSRF; two independently found the SA token injection. Independent convergence on the same design flaws is a signal, not a duplicate-counting problem. Every class is a variant of "the admission controller has broad cluster access, we let policy authors feed it strings, we didn't scope the strings."
It's the same shape as the SSRF class in web apps twenty years ago. Web apps kept shipping functions that took a URL and made a server-side request, and every one had to independently relearn that the caller and the server see different networks. Swap "caller" for "namespace admin" and "server" for "admission controller pod" and you get Kyverno's 2026.
Why it keeps happening
Admission controllers live in an uncomfortable trust position. They are policy engines, which means users are supposed to be able to write useful policies that reference real cluster state. They also run with cluster-wide read permissions, because that's the only way to enforce a policy that says "deny this Pod if it references a ConfigMap the submitter can't actually read."
The design knob is how far into the cluster the policy engine will reach on the policy author's behalf. Every knob on the "reach further" side is a knob that has to independently know about tenant boundaries.
- Can a policy fetch a ConfigMap? Then
configMap.namespaceneeds a namespace allowlist. Or you getGHSA-cvq5-hhx3-f99p. - Can a policy make an HTTP call? Then the URL needs a destination allowlist. Or you get
GHSA-qr4g-8hrp-c4rw. - Does the HTTP client get an Authorization header by default? Then the token source has to be scoped. Or you get
GHSA-q93q-v844-jrqp.
Every one of these loaders is defensible in isolation — sometimes a policy legitimately needs to pull external data. The breakdown shows up when you have five of them (configMap, apiCall, globalReference, imageRegistry, variable), each living in its own file, each having to independently learn about tenant boundaries. They don't, reliably. That's exactly what this week's advisory is: the January patch hardened apiCall.urlPath but the equivalent check for configMap.namespace never got written in the sibling subdirectory.
The Kyverno team keeps shipping individual fixes fast. The class of bug won't stop appearing until every new loader is gated through a single checkpoint that knows who the policy author is and what they're allowed to reach — or until the trust model around the admission controller SA is rethought entirely.
What to do in your clusters right now
Watch for v1.17.2. v1.17.2-rc.1 was tagged this morning. The final should follow in days. Upgrade as soon as it lands. Until then, you're on the rc or on the workaround below.
Audit your existing Policies for cross-namespace context references. This one-liner flags any namespaced Policy whose configMap reference targets a different namespace than the Policy itself:
kubectl get policies.kyverno.io --all-namespaces -o json \
| jq -r '.items[] | .metadata as $m
| .spec.rules[]?.context[]?
| select(.configMap.namespace and .configMap.namespace != $m.namespace)
| "\($m.namespace)/\($m.name) → \(.configMap.namespace)/\(.configMap.name)"'
Anything this prints is a live instance of the bug if the two namespaces belong to different tenants. It's also worth running the same check for apiCall.urlPath and apiCall.service.url — the earlier advisories have the same shape.
Restrict who can create namespaced Policies. In a multi-tenant cluster, policies.kyverno.io/create should belong to cluster admins. Tenant namespace admins don't need it for most common use cases, and giving it to them hands them every advisory on this list as an exploit primitive.
kubectl get rolebindings --all-namespaces -o json \
| jq -r '.items[]
| select(.roleRef.name | test("admin|edit"; "i"))
| .metadata as $m
| .subjects[]?
| "\($m.namespace)/\($m.name) → \(.kind):\(.name)"'
Review what comes back and confirm that every principal with admin/edit in a tenant namespace genuinely needs the ability to create Kyverno Policies there.
Treat ConfigMaps like Secrets in sensitive namespaces. Anything that ends up in a ConfigMap in kube-system, in a control-plane namespace, or in a security-sensitive tenant namespace should be RBAC-audited the same way a Secret would be. That's not a Kyverno-specific lesson, but this class of bug keeps making it load-bearing.
One note on detection
If you run Juliet or any other CNAPP, vulnerable Kyverno versions will surface in your image inventory once the feed catches the advisory. That part is easy and boring — every scanner does the same thing. The class of bug is what's actually worth chewing on.
Credit to @jrey8343 for the bug, the Kyverno maintainers (@JimBugwadia and the security team) for shipping the fix and backports the same day the advisory went public, and the Orca Security research pod for several of the earlier advisories in the same cluster.
If you want to see every vulnerable Kyverno version across every cluster you run — with one query instead of per-cluster scans — start with the free tier. Questions about multi-tenant exposure or help with the audit commands above: contact@juliet.sh.
Get notified when we publish
No spam, no cadence — just an email when we have something worth reading.
Or subscribe via RSS