A high-severity flaw in Kyverno — one of the most widely deployed Kubernetes-native policy engines — silently attaches the controller’s own ServiceAccount bearer token to outbound HTTP requests whose URLs are controlled by policy authors. Tracked as CVE-2026-40868 (CVSS 8.1) and published on April 14, 2026, the vulnerability turns any user with permission to create or modify a ClusterPolicy into a potential full-cluster admin via a textbook confused deputy attack. It is the fourth apiCall-related credential exposure advisory against Kyverno in roughly three months, following CVE-2026-22039, CVE-2026-4789, and the related GHSA-8wfp-579w-6r25 and GHSA-f9g8-6ppc-pqq4 disclosures.
What Happened
Kyverno’s apiCall context entries let a policy fetch external data during admission decisions — for example, to enrich a decision with information from an internal microservice. The servicecall helper inside pkg/engine/apicall/apiCall.go supports an apiCall.service.url field that the policy author controls. When that field is set and the policy does not explicitly set an Authorization header, Kyverno’s helper auto-injects one in the form Authorization: Bearer <kyverno-controller-token>, using the token mounted into the Kyverno admission controller Pod.
Because the URL is policy-controlled, any attacker who can write a ClusterPolicy — including legitimate platform engineers whose credentials are later stolen, or any RBAC subject granted policies.kyverno.io/create at the cluster scope — can point service.url at a server they control. Kyverno faithfully makes the request, dutifully attaches its own bearer token, and hands the token to the attacker. The attacker now holds a credential that authenticates to the Kubernetes API as the Kyverno controller’s ServiceAccount, which typically carries very broad cluster-wide RBAC because Kyverno needs to mutate, validate, and clone resources across all namespaces.
This is the classic confused deputy: the policy engine is a trusted intermediary with privileged access to the Kubernetes API, and it is being tricked into delegating its own identity to a third party that has no right to it.
Technical Details
The root cause is implicit authentication behavior. In Kyverno versions prior to 1.17.0, when a context.apiCall entry uses the service form and does not explicitly set an Authorization header, the helper walks the following path:
- Load the Pod’s ServiceAccount token from
/var/run/secrets/kubernetes.io/serviceaccount/token. - Perform variable substitution on the policy-supplied
urlfield, resolving any{{ request.* }}or{{ serviceAccountName }}placeholders. - Call
http.NewRequestwith the resolved URL and injectAuthorization: Bearer <token>beforeclient.Do.
Steps 1 and 3 were added to simplify policies that call back into the Kubernetes API server or into other in-cluster services protected by TokenReview. The implementation, however, does not verify that the resolved host belongs to a trusted allowlist, and it does not strip the Authorization header on cross-origin redirects. The result is that the token travels wherever the URL points — including http://attacker.example.com/, http://169.254.169.254/latest/api/token/ (cloud metadata in some configurations), or any attacker-hosted endpoint behind an apiCall template.
Namespaced Policy resources are not directly exploitable for this specific bearer-token path. The servicecall usage is gated to cluster-scoped ClusterPolicy and global context entries via the namespaced urlPath check in apiCall.go, so the attack requires cluster-scoped write permission on Kyverno policies. In practice this is a much lower bar than it sounds: many platform teams grant ClusterPolicy edit rights to multiple engineers, pipelines, and GitOps operators, and compromise of any one of those identities is sufficient.
A related issue (CVE-2026-22039, CVSS up to 10.0 depending on rater) covers a different apiCall path that allowed namespaced tenants to escape their namespace entirely by controlling urlPath. CVE-2026-40868 is distinct: it is about the bearer token being attached at all when the destination is a service URL rather than a Kubernetes API URL.
Affected Versions
All Kyverno releases before 1.17.0 are affected. The 1.17.x series, cut on February 2, 2026, contains the fix, which:
- Disables implicit bearer-token injection for
servicecallhelpers unless the URL’s host matches an explicit allowlist. - Strips
Authorizationheaders on any redirect whose target host differs from the original. - Adds a configuration flag to forbid
service.urlvalues pointing at link-local, loopback, or RFC 1918 addresses unless operators opt in.
Kyverno users on 1.15.x and 1.16.x are not receiving a backport; the only sanctioned fix is to upgrade to 1.17.0 or later. As of publication, the current Kyverno release is 1.17.2.
Impact Assessment
The real-world impact depends entirely on what the Kyverno controller’s ServiceAccount is allowed to do — and in almost every deployment, the answer is “a lot.” The default Kyverno Helm chart binds the controller to a ClusterRole with the ability to:
get,list,watch,create,update,patch, anddeleteon a broad set of core and custom resources across all namespaces, so mutation policies can rewrite Pods, Deployments, ConfigMaps, and so on.- Read
Secretscluster-wide (required for policies that validate TLS material or registry pull secrets). - Create
SubjectAccessReviewandTokenReviewobjects. - Generate resources (via the
generaterule type) in arbitrary namespaces.
An attacker holding the controller’s token can therefore read every Secret in the cluster, mint new workloads, and in most deployments escalate to cluster-admin by creating a privileged ClusterRoleBinding or a mutating webhook of their own. On clusters running on cloud providers, a broad enough Secret read often includes IAM access keys, which transforms a Kubernetes compromise into a full cloud account compromise within minutes.
The discovery pattern is especially ugly: the bearer token is leaked inside an outbound HTTPS request body-free GET, which is unlikely to trigger egress-based DLP and which will look like benign Kyverno traffic to most mesh observability tools.
Mitigation — What To Do Right Now
- Upgrade immediately. Move every Kyverno installation to 1.17.0 or later. For Helm deployments:
helm upgrade kyverno kyverno/kyverno --version 3.4.x(chart 3.4.x ships Kyverno 1.17.x). - Audit existing
ClusterPolicyobjects. Grep forapiCall:blocks that includeservice:and review whether theurlfield is parameterized with untrusted input.kubectl get clusterpolicy -o yaml | yq '.items[] | select(.spec.rules[].context[]?.apiCall.service.url)'will surface the suspect policies. - Tighten RBAC on
ClusterPolicy. Restrictclusterpolicies.kyverno.iocreate/update/patch verbs to a small, audited set of identities. GitOps operators like Flux and Argo should assume the controller’s own token, not delegate admin capability to every developer with repo access. - Restrict Kyverno egress. Apply a
NetworkPolicyor service-mesh egress rule that allows the Kyverno controller to reach only the Kubernetes API server and known in-cluster services. Block link-local (169.254.0.0/16), loopback, and public internet egress by default. This blunts the attack even on unpatched versions. - Rotate the ServiceAccount token. If you have any reason to suspect a
ClusterPolicywith an unfamiliarservice.urlwas applied before the upgrade, delete the Kyverno controller Pod so it remounts a fresh projected token, and rotate any Secrets the controller had read access to as a precaution. - Consider Kubewarden or a restricted Kyverno subset. The Kubewarden project published a note explicitly stating it is not affected by this class of vulnerability because it does not support outbound apiCall. For environments where external enrichment is not a hard requirement, disabling the apiCall feature entirely — via the
--enableApiCallContext=falsecontroller flag — removes the attack surface.
Detection
IOCs are limited because the outbound request is shaped like normal Kyverno traffic, but a few signals are worth hunting for in SIEM data:
- Outbound HTTPS from the
kyvernonamespace to unexpected external domains, especially any first-seen domain within the last 30 days. ClusterPolicyresources created or updated with anapiCall.service.urlpointing outside the cluster’s known service CIDR.- Kubernetes audit logs showing the
system:serviceaccount:kyverno:kyvernoidentity performing actions from a source IP that is not the Kyverno controller Pod IP — a strong indicator the token has left the cluster.
References
- Kyverno GitHub security advisories: GHSA-q93q-v844-jrqp, GHSA-8wfp-579w-6r25, GHSA-f9g8-6ppc-pqq4, and GHSA-8p9x-46gm-qfx2 (the related namespace-isolation bypass).
- Kyverno 1.17 release notes: kyverno.io/blog.
- Kubewarden statement on CVE-2026-22039 and the broader apiCall class: kubewarden.io/blog.
If you operate a multi-tenant Kubernetes cluster and you have not audited your Kyverno ClusterPolicy objects in the last week, do it before you close this tab. A single malicious service.url is all it takes.