Monitor vulnerabilities like this one.
Sign up free to get alerted when software you use is affected.
8.1
Kyverno Service Call Leaks Service Account Token
GHSA-q93q-v844-jrqp
Summary
Kyverno's service call feature may send your service account token to an attacker-controlled endpoint if you don't explicitly set the Authorization header in your policy. This can happen if your policy uses a URL that's controlled by an attacker, such as if your policy repository or controller is compromised. To fix this, make sure to set the Authorization header explicitly in your policies, especially if you're using cluster-wide policies or global context entries.
What to do
- Update github.com kyverno to version 1.17.0.
Affected software
| Vendor | Product | Affected versions | Fix available |
|---|---|---|---|
| github.com | kyverno | <= 1.17.0 | 1.17.0 |
Original title
kyverno apicall servicecall implicit bearer token injection leaks kyverno serviceaccount token
Original description
kyverno’s apiCall servicecall helper implicitly injects `Authorization: Bearer ...` using the kyverno controller serviceaccount token when a policy does not explicitly set an Authorization header. because `context.apiCall.service.url` is policy-controlled, this can send the kyverno serviceaccount token to an attacker-controlled endpoint (confused deputy).
namespaced policies are blocked from servicecall usage by the namespaced `urlPath` gate in `pkg/engine/apicall/apiCall.go`, so this report is scoped to ClusterPolicy and global context usage.
## attacker model
the attacker can create or update a ClusterPolicy (or create a GlobalContextEntry) which uses `context.apiCall.service.url` and can choose the request URL and headers. a cross-boundary framing for real deployments is gitops: if the policy repo/controller is compromised, the ClusterPolicy/global context entry becomes untrusted input to kyverno.
## relevant links
- repository: https://github.com/kyverno/kyverno
- commit: 17aeb52337fd66adb0c8126213ba076612a287a7
- callsite (token injection): https://github.com/kyverno/kyverno/blob/17aeb52337fd66adb0c8126213ba076612a287a7/pkg/engine/apicall/executor.go#L150-L173
- namespaced policy gate (servicecall blocked): https://github.com/kyverno/kyverno/blob/17aeb52337fd66adb0c8126213ba076612a287a7/pkg/engine/apicall/apiCall.go#L67-L83
## root cause
in `(*executor).addHTTPHeaders`, kyverno reads the serviceaccount token from `/var/run/secrets/kubernetes.io/serviceaccount/token` and injects it when the outgoing request has no Authorization header:
```go
if req.Header.Get("Authorization") == "" {
token := a.getToken()
if token != "" {
req.Header.Add("Authorization", "Bearer "+token)
}
}
```
## proof of concept
the attached `poc.zip` is a reproducible cluster PoC. it uses an in-cluster HTTP receiver which logs the Authorization header it receives. the PoC does not print token bytes; it only checks that the received header is non-empty and not equal to the negative control.
run (one command):
```bash
unzip poc.zip -d poc
cd poc
make test
```
canonical (expected: implicit token injection):
```bash
unzip poc.zip -d poc
cd poc
make canonical
```
expected output includes:
```
[CALLSITE_HIT]: executor.addHTTPHeaders Authorization=="" -> read_serviceaccount_token=true
[PROOF_MARKER]: authorization_header_injected=true token_nonempty=true
```
control (expected: explicit Authorization header disables auto-injection):
```bash
unzip poc.zip -d poc
cd poc
make control
```
expected output includes:
```
[CALLSITE_HIT]: executor.addHTTPHeaders Authorization!="" -> autoinject_skipped=true
[NC_MARKER]: authorization_header_injected=false
```
optional: the canonical run may also print an `[RBAC]: ...` line using `kubectl auth can-i` with the exfiltrated token, to show concrete privileges without exposing the token.
## impact
token exfiltration: the kyverno controller serviceaccount token is sent to a policy-controlled endpoint. impact depends on the rbac bound to that serviceaccount in the target deployment.
## recommended fix
do not auto-inject the kyverno serviceaccount token into policy-controlled servicecall requests. require explicit Authorization configuration, or enforce a strict allowlist of destinations where credentials may be attached and document the behavior.
## workarounds
- avoid using servicecall to arbitrary urls in policies.
- set an explicit Authorization header in servicecall policies to prevent implicit token injection.
[poc.zip](https://github.com/user-attachments/files/25352288/poc.zip)
[PR_DESCRIPTION.md](https://github.com/user-attachments/files/25352289/PR_DESCRIPTION.md)
oleh
namespaced policies are blocked from servicecall usage by the namespaced `urlPath` gate in `pkg/engine/apicall/apiCall.go`, so this report is scoped to ClusterPolicy and global context usage.
## attacker model
the attacker can create or update a ClusterPolicy (or create a GlobalContextEntry) which uses `context.apiCall.service.url` and can choose the request URL and headers. a cross-boundary framing for real deployments is gitops: if the policy repo/controller is compromised, the ClusterPolicy/global context entry becomes untrusted input to kyverno.
## relevant links
- repository: https://github.com/kyverno/kyverno
- commit: 17aeb52337fd66adb0c8126213ba076612a287a7
- callsite (token injection): https://github.com/kyverno/kyverno/blob/17aeb52337fd66adb0c8126213ba076612a287a7/pkg/engine/apicall/executor.go#L150-L173
- namespaced policy gate (servicecall blocked): https://github.com/kyverno/kyverno/blob/17aeb52337fd66adb0c8126213ba076612a287a7/pkg/engine/apicall/apiCall.go#L67-L83
## root cause
in `(*executor).addHTTPHeaders`, kyverno reads the serviceaccount token from `/var/run/secrets/kubernetes.io/serviceaccount/token` and injects it when the outgoing request has no Authorization header:
```go
if req.Header.Get("Authorization") == "" {
token := a.getToken()
if token != "" {
req.Header.Add("Authorization", "Bearer "+token)
}
}
```
## proof of concept
the attached `poc.zip` is a reproducible cluster PoC. it uses an in-cluster HTTP receiver which logs the Authorization header it receives. the PoC does not print token bytes; it only checks that the received header is non-empty and not equal to the negative control.
run (one command):
```bash
unzip poc.zip -d poc
cd poc
make test
```
canonical (expected: implicit token injection):
```bash
unzip poc.zip -d poc
cd poc
make canonical
```
expected output includes:
```
[CALLSITE_HIT]: executor.addHTTPHeaders Authorization=="" -> read_serviceaccount_token=true
[PROOF_MARKER]: authorization_header_injected=true token_nonempty=true
```
control (expected: explicit Authorization header disables auto-injection):
```bash
unzip poc.zip -d poc
cd poc
make control
```
expected output includes:
```
[CALLSITE_HIT]: executor.addHTTPHeaders Authorization!="" -> autoinject_skipped=true
[NC_MARKER]: authorization_header_injected=false
```
optional: the canonical run may also print an `[RBAC]: ...` line using `kubectl auth can-i` with the exfiltrated token, to show concrete privileges without exposing the token.
## impact
token exfiltration: the kyverno controller serviceaccount token is sent to a policy-controlled endpoint. impact depends on the rbac bound to that serviceaccount in the target deployment.
## recommended fix
do not auto-inject the kyverno serviceaccount token into policy-controlled servicecall requests. require explicit Authorization configuration, or enforce a strict allowlist of destinations where credentials may be attached and document the behavior.
## workarounds
- avoid using servicecall to arbitrary urls in policies.
- set an explicit Authorization header in servicecall policies to prevent implicit token injection.
[poc.zip](https://github.com/user-attachments/files/25352288/poc.zip)
[PR_DESCRIPTION.md](https://github.com/user-attachments/files/25352289/PR_DESCRIPTION.md)
oleh
ghsa CVSS3.1
8.1
Vulnerability type
CWE-441
Published: 14 Apr 2026 · Updated: 14 Apr 2026 · First seen: 14 Apr 2026