Default ServiceAccount Automount Ships Enabled Cluster-Wide
Every namespace ships a default ServiceAccount with automountServiceAccountToken left unset, which the API server treats as true. Pods that never touch kubectl still receive a live, auto-renewing bearer token, and normal secret rotation or restarts do nothing to revoke it on clusters using projected volumes.
At a glance
- Unsafe setting
- The namespace default ServiceAccount leaves automountServiceAccountToken unset, defaulting to true for every pod without an explicit SA.
- Failure trigger
- A pod running under the default ServiceAccount is compromised and its projected API token is read from the mounted volume.
- Blast radius
- Any namespace-scoped RoleBinding referencing the default SA or system:serviceaccounts group becomes reachable from every pod that never asked for API access.
- Recommended control
- Set automountServiceAccountToken to false on every default ServiceAccount and require dedicated, explicitly-mounted ServiceAccounts for workloads that need API calls.
The Trap
The automountServiceAccountToken field on the default ServiceAccount object in each Kubernetes namespace, left as nil rather than explicitly set to false.
The Default State
kubeadm, EKS, GKE and AKS all create a ServiceAccount named default in every namespace with automountServiceAccountToken absent from the object spec. The API server treats a nil value as true. Any pod that omits serviceAccountName inherits default and gets a projected token volume mounted at /var/run/secrets/kubernetes.io/serviceaccount/token. That token is issued via the TokenRequest API with expirationSeconds typically 3607, refreshed by the kubelet roughly every 60 seconds for the life of the pod, and on API servers running with –service-account-extend-token-expiration set to true (the default since 1.19) the actual bound lifetime can be silently stretched to a year regardless of what the manifest requests.
The Blast Radius
An RCE inside any workload running under default now has a live, self-renewing credential against the API server. Helm charts frequently grant the default SA or the system:serviceaccounts:<namespace> group get/list/watch on secrets and configmaps for service discovery, so the attacker doesn’t need privilege escalation, only namespace enumeration. Sidecars, batch jobs and static nginx pods that never call the API still carry the mount, so the exposure spans the entire namespace rather than the handful of pods that actually need it. Because kubelet refreshes the token independently of pod state, deleting or rotating the legacy long-lived Secret does nothing on clusters using projected tokens; the only way to cut access is to remove the mount and restart the pod.
The Lead Mechanic Fix
Patch every namespace’s default SA directly: kubectl patch serviceaccount default -n <namespace> -p ‘{“automountServiceAccountToken”: false}’. For workloads that genuinely call the API, create a dedicated ServiceAccount with automountServiceAccountToken: true set explicitly and bind it to a scoped Role, not a namespace-wide group. Constrain token audience with a TokenRequestSpec audiences field rather than accepting the default kubernetes.default.svc audience, and cap –service-account-max-token-expiration on the API server so the extension flag can’t produce year-long tokens. Enforce the pattern with a Kyverno ClusterPolicy that denies any pod spec where automountServiceAccountToken resolves true unless serviceAccountName is not default.