Kai Ole Hartwig
18 min read
By

KEDA CVE-2026-77524: The operator sends its own token to foreign Vault servers

When the KEDA operator needs a secret from Vault, it logs in via Kubernetes auth and presents a ServiceAccount token as its credential. Up to version 2.20, that was its own token by default, and the server it sent it to was determined solely by the address field of a TriggerAuthentication that a team can create in its own namespace. CVE-2026-77524 exploits exactly this combination. A tenant names a server they run themselves and gets handed a token that can read secrets across the whole cluster. The fix in KEDA 2.21.0 therefore checks the destination and at the same time binds the token to the purpose it was issued for.

TL;DR — 90 seconds

How the operator becomes a messenger

KEDA scales workloads based on external metrics, and the credentials a scaler needs for that are described by a TriggerAuthentication. The resource lives in the namespace of the team that creates it. For HashiCorp Vault it contains the server address in address, the method in authentication and, for Kubernetes auth, also the Vault role in role and the mount path in mount. Optionally, the credential block defines which token the operator presents: a token file via serviceAccount or a freshly minted token for a ServiceAccount in the namespace via serviceAccountName.

Why this becomes dangerous is shown by Vault's login flow. With Kubernetes auth, the client sends its Vault role and the raw ServiceAccount JWT to the login endpoint auth/<mount>/login. Vault does not validate the token itself but asks the Kubernetes API server via the TokenReview API whether it is valid and whom it belongs to. The procedure has no way for the client to prove possession of the token without handing it over. Whoever runs the login endpoint therefore ends up holding a fully usable bearer token.

This is exactly where the bug was. If the credential block was missing, the operator fell back to its own ServiceAccount token according to the advisory, and it did not check the address from the TriggerAuthentication. A service with cluster privileges thus sent its token to any server a tenant named. Security literature calls this pattern a confused deputy: a privileged service carries out a task for someone with few privileges and uses its own privileges to do so. The additional classification as server-side request forgery describes the other side of the same coin, because the operator opens the connection from inside the cluster to a freely chosen destination.

The code path already had a history. Through CVE-2025-68476, credential.serviceAccount could be used to send any file from the operator pod to Vault as a supposed token. The fix in 2.17.3 and 2.18.3 then checked the file's content, which according to the new advisory must look like a ServiceAccount JWT with a subject starting with system:serviceaccount:. The operator's own token meets this condition without any effort. The first fix asked what the secret looks like, not where it goes and what it is good for.

What the fix commit changes

Fix commit 1e21cc7 touches 15 files and works on three points. First, without credential the Vault handler no longer reads the operator's API token but a dedicated projected token, by default at /var/run/secrets/keda-vault/token. The Helm chart projects it into the pod with the audience vault, so the operator needs no TokenRequest permissions for it. Second, the new default mode enforce-audience introduces a list of approved audiences against which every token from a file is checked before it leaves the operator. Tokens that KEDA mints itself for serviceAccountName or boundServiceAccountToken now also need an explicit mapping of namespace, name and audience. Third, an allowlist can check the Vault address, comparing scheme, host and port rather than just the beginning of the string.

The audience change reveals the most about the actual root cause. A ServiceAccount token carries in its aud claim the recipient it is intended for. According to the Kubernetes docs, a pod's default token is bound to the pod and issued for the kube-apiserver, so it is valid wherever the API server accepts it. A token with the audience vault, on the other hand, is rejected by the API server, and the KEDA docs explicitly require audiences that the API server does not accept. If such a token falls into the wrong hands, it no longer opens the Kubernetes API. The core of the bug was therefore less the missing address check than the fact that a universally valid key could leave the operator at all.

From namespace to cluster in a single scaling run

According to the advisory, the attack does not need much: an account that may create TriggerAuthentications and ScaledObjects in its own namespace and also holds the built-in edit role there, plus a KEDA installation with default settings. Such permissions are common in clusters with namespace admins. One detail of the Helm chart deserves attention here. By default it does not aggregate the KEDA permissions into the default roles, because rbac.aggregateToDefaultRoles is set to false. Tenants therefore only get the right to create these resources if someone has flipped that switch or given them dedicated roles.

The sequence itself is unspectacular, and that is exactly what makes it dangerous. The tenant creates a TriggerAuthentication whose Vault address points to their own server and leaves out the credential block. A ScaledObject references this TriggerAuthentication. As soon as the operator evaluates the scaler, it logs in to Vault as intended, just at the wrong server, and hands over its token there. There is no crash, no conspicuous error message and no foreign code in the operator.

What the token is worth depends on the operator's permissions. According to the advisory, it may read secrets cluster-wide, create Jobs and HPAs, scale workloads and modify admission webhooks and APIServices. Read access to all secrets alone is enough to obtain credentials of more privileged services, and Jobs can be used to start workloads in other namespaces as far as their policies allow. The advisory rightly rates this as a path to cluster admin.

Truly affected are therefore clusters in which several teams manage their own namespaces and may create KEDA resources themselves. This applies even if nobody in the entire cluster uses Vault, because the attacker brings the fake Vault server along. There is little risk where only the platform team maintains TriggerAuthentications, since whoever is allowed to do so usually has far-reaching permissions anyway.

CVEAffectedFixPrerequisiteCVSS
CVE-2026-77524 (GHSA-637c-6jxx-4rwm)KEDA v2 before 2.21.02.21.0Create TriggerAuthentication and ScaledObject in own namespace, edit role9.9 (critical)
CVE-2025-68476 (GHSA-c4p6-qg4m-9jmr), predecessorOperator up to 2.17.2 and 2.18.22.17.3 / 2.18.3 / 2.19.0Create or modify TriggerAuthentication8.2 (high)

The sources differ slightly on versions. The GitHub advisory page lists everything up to 2.20.0 as affected and no patched version, while OSV and the release notes name 2.21.0 as the fix. The release came out on 23 September, the CVE number was only published on 14 October.

As of 17 October, none of the sources checked mentions a public exploit. No attacks in the wild are known, and the vulnerability is not in the CISA KEV catalog. EPSS estimates the probability of exploitation within the next 30 days at 0.64 percent. The advisory, however, describes the path in such detail that reproducing it takes little effort.

KEDA 2.21.0 and what the upgrade requires

The only complete fix is KEDA 2.21.0, and the update deliberately changes behaviour in a way that can break existing configurations. Affected are Vault Kubernetes auth and every TriggerAuthentication using boundServiceAccountToken, which according to the release notes includes integrations with the Metrics API, Prometheus, Loki and the Datadog Cluster Agent. Plain Vault tokens, API keys and OAuth remain unaffected. Before you upgrade, the section “Service account token audiences” in the migration guide is worth reading.

In the Helm chart, operator.serviceAccountTokens.mode controls the new mode, the default being enforce-audience. The audience of the projected Vault token is set by hashiCorpVault.kubernetesAuth.audience, default vault, and the mount path by hashiCorpVault.kubernetesAuth.projectedTokenMountPath. If you let KEDA mint tokens for ServiceAccounts in tenant namespaces, you need an entry with name, namespace and audience for each ServiceAccount under permissions.operator.restrict.serviceAccountTokenCreationRoles. An entry without an audience only grants RBAC and does not allow minting tokens in the new mode.

On the Vault side, the role must accept the audience vault, or you configure a dedicated audience in the chart that your role already knows. The chart does not touch Vault itself. The guide recommends creating a new Vault role so existing clients keep working during the migration. In addition, Vault needs its own API-valid credential for its TokenReview requests, such as the token of its own ServiceAccount. The new login token is precisely unsuitable for this, because the API server no longer accepts its audience.

Two pitfalls only show up in operation. An empty credential: {} block is not the same as a missing block, so either leave it out entirely or explicitly set credential.serviceAccount to /var/run/secrets/keda-vault/token. If something does not match, individual ScaledObjects and ScaledJobs also fail to authenticate while the operator itself looks healthy. After the rollout you should therefore check the scalers and not just the pod status. For the Datadog Cluster Agent 7.83.2, the guide says the only option is switching to API or app key authentication, because it cannot configure an audience.

The legacy mode restores the old behaviour. The guide calls it an operator-wide, insecure escape hatch, and that is exactly what it is, because it reopens the vulnerability. It has no place in multi-tenant clusters.

The audience makes a leaked token worthless but does not prevent it from being sent in the first place. That is what the new allowlist for Vault addresses is for, which is disabled by default. In the chart you configure it under operator.outboundFilter.hashiCorpVault with mode and allowedEndpoints, and the chart passes it to the operator as KEDA_OUTBOUND_FILTER. Entries are HTTP(S) origins made up of scheme, host and port; the filter does not accept paths, wildcards or CIDRs. According to the docs it applies to all Vault authentication methods, including plain tokens. It is best to start with warn, look at which destinations show up in the log and then switch to enforce. An empty list blocks every Vault destination in that mode.

Hardening that would have worked before the patch

Before looking at individual measures, some context helps. In this attack the operator does not execute any foreign code and does not call any unusual syscall. It does exactly what it was built for, just on behalf of the wrong party. Anything that limits the destination of the connection, the acceptance of the task or the value of the token is therefore effective. Anything that hardens the container itself, on the other hand, misses the point here.

Egress restriction for the operator

The token leaves the cluster through an outbound connection from the operator pod. A NetworkPolicy with policyTypes: [Egress] that only allows the API server, DNS, the real Vault servers and the destinations of the scalers in use would have prevented the login at the foreign server from happening at all. This is the measure with the greatest effect, and it also helps against future vulnerabilities of the same class.

It does have limits you should know about, though. The standard NetworkPolicy works with IP blocks and label selectors, not with host names, so for external destinations with changing addresses you need FQDN rules from your CNI, such as those offered by Cilium. In addition, the rule must not blanket-allow pods in tenant namespaces, because an attacker can just as well run their fake Vault server as a pod in their own namespace. And it only works if your CNI enforces NetworkPolicies at all. In operation the rule means maintenance effort, since every new scaler needs an allowance. The allowlist from KEDA 2.21 brings the same principle into the application but does not replace the network layer, because it only covers Vault.

Admission rule for TriggerAuthentications

The attack starts with a resource whose content can be checked in advance. A ValidatingAdmissionPolicy, generally available since Kubernetes 1.30, can reject TriggerAuthentications and ClusterTriggerAuthentications on create and update if spec.hashiCorpVault.address does not come from a fixed list. A second rule can forbid authentication: kubernetes without a credential block, i.e. exactly the case in which the operator used its own token before 2.21. Both conditions can be expressed directly in CEL over the fields of the resource. The allowed addresses sensibly come from a parameter resource that only the platform team may change. Kyverno or Gatekeeper do the same if you already run one of them.

RBAC: TriggerAuthentications belong to the platform team

The most effective organisational lever is not to let tenants create TriggerAuthentications in the first place. The platform team then provides credentials as ClusterTriggerAuthentications that ScaledObjects in the namespaces merely reference. This costs some convenience but removes the attack's prerequisite. The advisory for the predecessor CVE-2025-68476 already named exactly this restriction as a workaround, and anyone who implemented it back then was protected this time as well. To do this, check whether rbac.aggregateToDefaultRoles is enabled in the chart and which custom roles give tenants permissions on triggerauthentications.keda.sh.

Fewer permissions for the operator

How much a leaked token is worth is decided by the operator's permissions. If it may read secrets cluster-wide, its token is a master key. With permissions.operator.restrict.secret the chart offers a switch that limits read access to secrets in its own release namespace, and permissions.operator.restrict.namesAllowList additionally narrows down the names. This fits well with the model of ClusterTriggerAuthentications, whose secrets live in the KEDA namespace. TriggerAuthentications that read secrets from tenant namespaces will no longer work then, which I derive from the description of the switch. Test the setting first, because according to issue #641 in the chart repository the operator crashed with it in KEDA 2.14.2. The switch does not prevent the token from being sent, but it considerably reduces the possible damage.

What does not help here

Many standard hardening measures remain right but are ineffective against this mechanism. seccomp with RuntimeDefault or a custom profile does not prevent an HTTPS connection that the operator is allowed to open anyway. Dropped capabilities, runAsNonRoot, allowPrivilegeEscalation: false and readOnlyRootFilesystem target code that wants to do damage inside the container, and no such code is involved here. The same applies to the Pod Security Standard “restricted”, to user namespaces and to a minimal base image without shell and package manager.

A strictly configured Vault role with bound_service_account_names and an audience does not help either, because the token never reaches your Vault but the attacker's server. A CVE gate in the pipeline only fires once the vulnerability has a number, and here three weeks passed between release and CVE. Anyone relying solely on CVE scanners would only have learned about it from the release notes during that time.

You can find more on egress rules, admission policies and tenant separation in the series TYPO3 on Kubernetes under IT-Grundschutz.

Detection and verification

Whether you are affected is shown first by the operator image in the cluster, because everything before 2.21.0 contains the vulnerability. After that it is worth looking at the resources themselves. List all TriggerAuthentications and ClusterTriggerAuthentications with a hashiCorpVault block and compare address with your real Vault servers. Suspicious are unknown addresses, addresses pointing to services or pods in tenant namespaces, and entries with authentication: kubernetes without credential.

Harder is the question of whether a token has already leaked. The Kubernetes audit log helps most here, as it records the authenticated user and the source IP for every request. The chart names the operator's ServiceAccount keda-operator by default. With an installation in the keda namespace it therefore appears in the log as system:serviceaccount:keda:keda-operator. Requests from this user that do not come from the IPs of the operator pods are a strong signal of a stolen token. This search is my own derivation and not a vendor recommendation; the advisory and release notes name no IoCs.

If you suspect something, there is a quick first step. According to the Kubernetes docs, a pod-bound token becomes invalid as soon as the pod is deleted, at the latest when its lifetime expires. Restarting the operator pods therefore invalidates the tokens they held until then. Afterwards you rotate all secrets the operator was allowed to read and check whether Jobs, webhooks or APIServices were created or modified with the token.

Operator recommendation

Act immediately if KEDA before 2.21.0 runs in a cluster in which teams manage their own namespaces and may create KEDA resources themselves. Check the existing TriggerAuthentications right away and either revoke tenants' right to create them or put an admission rule in place until the update is done. Plan the move to 2.21.0 with the migration guide, enable the allowlist for Vault addresses at the same time and then test each scaler individually.

Monitoring is enough if only the platform team maintains TriggerAuthentications and nobody else has write access to KEDA resources. The update still belongs in the next regular maintenance window, not least because the audience binding also defuses future bugs in this path.

Regardless of the individual case, what I say about every vulnerability applies: patches and updates belong automated. If Renovate or Dependabot propose new versions of Helm charts and images as merge requests on their own and the pipeline tests them against a staging environment, a security release like KEDA 2.21.0 is a matter of hours instead of weeks. A cooldown period for routine updates protects against compromised releases, and you exempt security updates from it. A breaking change like this one is exactly where the value of the process shows, because the failing Vault login then surfaces in testing and not only in production. Anyone updating by hand, on the other hand, can easily go four weeks without a patch during the holiday season and then face an update where everything breaks at once.

Frequently asked questions about CVE-2026-77524

What should I do if a token may have leaked?+

Restart the operator pods, which invalidates pod-bound tokens. Then rotate all secrets the operator was allowed to read and check the audit log.

Why doesn't Pod Security protect here?+

No foreign code runs in the operator. It opens a connection it is allowed to open anyway. Only egress rules, admission policies and RBAC work against that.

Is upgrading to 2.21.0 enough?+

It closes the vulnerability as long as you don't switch to legacy mode. Also enable the allowlist for Vault addresses, which is off by default.

I don't use Vault at all. Am I affected?+

Yes, if teams may create TriggerAuthentications. The attacker brings the fake Vault server along. The vulnerability is in the operator, not in your Vault configuration.

Conclusion

CVE-2026-77524 is already the second vulnerability in this code path, and the difference between the two fixes is instructive. The first checked whether a token looks like a token and overlooked that the most dangerous token passes every one of these checks. The second binds the token to its purpose and checks the destination before it is sent. The lesson reaches far beyond KEDA. Every controller with cluster privileges that takes destinations or paths from tenant resources is a potential confused deputy, as the hook bypass in Argo CD shows in the same week. It is worth taking a targeted look at your own operators: where do they open connections on behalf of others, and with which credential.

Sources

I review your Kubernetes clusters for operators with overly broad permissions, implement egress and admission rules and automate the rollout of security updates.

KEDA and operator audits, NetworkPolicies and ValidatingAdmissionPolicies for multi-tenant clusters, update automation with Renovate and tested pipelines.

Platform operations instead of consulting on paper: I continuously review, patch and harden your infrastructure.

About the author