An admission webhook is one of the more privileged things running in a Kubernetes cluster. It sits in the path of every object that gets created, and the secret-injection webhook from the bank-vaults project holds a cluster-wide right to mint ServiceAccount tokens. CVE-2026-54725, published on July 31, turns that privilege against the people who deployed it: a user who can do nothing more than create a ConfigMap or a Secret in a watched namespace can make the webhook fetch a ServiceAccount token and mail it to an attacker.
The flaw carries a CVSS score of 9.6 and is fixed in version 1.23.1. Everything up to and including 1.22.2 is affected. No public exploit is reported at the time of writing, which is the good news and the reason to move now: the window before someone weaponizes a one-annotation trick is short.
What the webhook was tricked into doing
vault-secrets-webhook is a mutating admission webhook that injects secrets from HashiCorp Vault straight into pods, so applications never read a secret off disk. To do that it reads annotations off the objects it admits, including one that names the Vault server address (vault.security.banzaicloud.io/vault-addr).
According to the project's security advisory, versions through 1.22.2 trust that annotation off any admitted ConfigMap or Secret without validating it: no scheme check, no hostname allowlist, no filter for private or link-local addresses. When the object also carries a value prefixed with vault:, the webhook makes a synchronous call to the annotated address as it admits the object. That is a textbook server-side request forgery: the attacker decides where a privileged process reaches out.
The credential theft is what makes it a 9.6 rather than a nuisance. A second annotation, vault-serviceaccount, selects which ServiceAccount the webhook authenticates as, and the webhook holds serviceaccounts/token:create across the whole cluster. So the attacker names a ServiceAccount, the webhook mints that account's JSON Web Token through the Kubernetes TokenRequest API, and sends it to the attacker's address. That token can then be replayed against the real Vault to read whatever secrets the account's role can reach. The MITRE record is CVE-2026-54725, classified as CWE-918.
| Stage | What happens | Where you catch it |
|---|---|---|
| Bait | Attacker creates a ConfigMap or Secret in a watched namespace, adding a vault-addr annotation pointing at their own server plus one vault: value | Kubernetes audit log: create or update on configmaps and secrets carrying an off-cluster vault-addr annotation |
| Call-out | The webhook reads the annotation and calls that address during admission, with no allowlist check | Unexpected egress from the webhook pod to a host that is not your Vault |
| Token mint | The vault-serviceaccount annotation selects a ServiceAccount; the webhook uses its cluster-wide token:create right to mint that account's JWT | Kubernetes audit: a TokenRequest for serviceaccounts/token driven by the webhook identity |
| Steal and replay | The JWT is sent to the attacker, who replays it against the real Vault to read that role's secrets | Vault audit log: a login from a source you do not recognise |
Why 'low privilege' undersells this
The CVSS vector marks privileges required as low, and on paper that reads as reassuring. In a real multi-tenant cluster it is close to no barrier at all. The right to create a ConfigMap or a Secret in a namespace is one of the most commonly granted permissions there is: continuous-integration runners hold it, application operators hold it, and plenty of developer roles hold it by default. None of those actors is supposed to be able to reach into your secret store. This flaw lets any of them try.
This is the confused deputy pattern in a Kubernetes costume. The webhook is powerful and trusted; the attacker is neither, but the webhook acts on data the attacker fully controls, so its power becomes the attacker's. We have seen the same shape when a single secret hands over a cluster and when a service integration is steered into calling an attacker. The common thread is trusting input that crosses a privilege boundary.
How you would know
Patching closes the hole. It does not tell you whether someone already walked through it. Because the theft is an outbound call from the webhook pod itself, not from the attacker's workload, ordinary endpoint tooling on your application pods will not see it. The evidence lives in three places, and they are worth checking whether or not you have patched yet:
- Kubernetes audit logs. Look for create or update events on ConfigMaps and Secrets that carry a
vault-addrannotation pointing at a host that is not your Vault, and for TokenRequest calls (serviceaccounts/token) attributed to the webhook that do not line up with a normal pod launch. - Egress from the webhook pod. The webhook should only ever talk to your Vault. Any other outbound destination from that pod is a strong signal.
- Vault audit logs. A stolen token still has to be used. Authentication events from a source address you do not recognise, against a role tied to an injected ServiceAccount, are the tell that a token was replayed.
These are the log sources a managed detection setup watches for this class of Kubernetes credential abuse, mapped to techniques like stealing an application access token and using alternate authentication material.
Patch to 1.23.1, then check who can create secrets
Upgrade vault-secrets-webhook to 1.23.1 or later. The fix validates the object-supplied Vault address against an allowlist the operator controls, so an annotation can no longer redirect the webhook to an arbitrary host. Confirm your running version first; anything at or below 1.22.2 is exposed.
Patching is step one, not the finish line. Two hardening moves outlast this specific CVE:
- Scope down who can create ConfigMaps and Secrets in namespaces the webhook watches. If a role does not need that permission, remove it. The precondition for this attack is exactly that grant.
- Constrain the webhook's egress with a network policy so it can reach your Vault and nothing else. An allowlist on the annotation and an allowlist on the network are two locks on the same door.
Admission webhooks are becoming the quiet privilege surface of Kubernetes. They run early, they run trusted, and they increasingly act on attacker-reachable input. This one held a path to a secret store; the next will hold something else. Treat every webhook that reads object annotations as code that runs on untrusted input, because that is what it is.