Kubernetes Pods Inherit API Tokens Nobody Requested
Kubernetes Pods Inherit API Tokens Nobody Requested sits in the Kubernetes RBAC Hardening risk register because a small configuration shortcut can widen access, weaken control boundaries, or hide failure conditions. The risk usually starts with kubernetes defaults automountServiceAccountToken to true at the API server level. In production, the concern is a single RCE in any container, a vulnerable base image with a dependency confusion payload, an SSRF that reaches the metadata-style endpoint, gives an
The Trap
automountServiceAccountToken left unset on ServiceAccounts and PodSpecs
The Default State
Kubernetes defaults automountServiceAccountToken to true at the API server level. When a namespace is created, its default ServiceAccount inherits this, and every Pod that does not explicitly set automountServiceAccountToken: false gets a projected volume mounted at /var/run/secrets/kubernetes.io/serviceaccount/, containing token, ca.crt, and namespace. Developers writing manifests almost never set the field on either the ServiceAccount or the Pod, because nothing in kubectl apply warns them it happened. The kubelet’s TokenRequest-issued token defaults to a 3607-second expiry and is bound to the pod’s service account, but with no audience restriction unless BoundServiceAccountTokenVolume behaviour has been explicitly configured with specific audiences.
The Blast Radius
A single RCE in any container, a vulnerable base image with a dependency confusion payload, an SSRF that reaches the metadata-style endpoint, gives an attacker a valid bearer token scoped to the cluster’s API server. If that namespace’s default ServiceAccount has been bound, even loosely, to a ClusterRole via a ClusterRoleBinding — common when teams apply a broad ‘view’ or custom operator role cluster-wide for convenience — the attacker can now run kubectl-equivalent calls against https://kubernetes.default.svc: list secrets across namespaces, read other ServiceAccount tokens, enumerate ConfigMaps holding connection strings, or exec into other pods if ‘pods/exec’ verbs are present. This is the exact mechanism behind most container-breakout-to-cluster-takeover incident writeups: the compromised workload never touches the node, it just asks the API server nicely with a token nobody meant to hand it. Audit logs show the requests as legitimate ServiceAccount activity, not an intrusion, because from the API server’s perspective they are.
The Lead Mechanic Fix
Set automountServiceAccountToken: false on every ServiceAccount by default: kubectl patch serviceaccount default -n <namespace> -p ‘{“automountServiceAccountToken”: false}’. For the small number of pods that genuinely need API access, mount a scoped, audience-bound, short-lived token explicitly via a projected volume with serviceAccountToken specifying audience and expirationSeconds (900 or lower), rather than relying on the legacy auto-mounted secret. Enforce this cluster-wide with a Kyverno ClusterPolicy or Gatekeeper constraint that denies any Pod spec lacking an explicit automountServiceAccountToken: false unless the ServiceAccount is on an allow-list tied to a documented RoleBinding, not a ClusterRoleBinding. Never bind default ServiceAccounts to ClusterRoles.