Kubernetes Service Account Token Security Audit: How to Find Overprivileged Pods and Unused Tokens Before Attackers Use Them for Cluster Escalation

Retool's new app builder is where AI-generated code ships safely
Building apps with AI is easy. Getting them to production safely is another story.
Container escape vulnerabilities are regularly discovered. When an attacker escapes a container, the first thing they do is check what the pod's service account token can access. If the answer is cluster-admin, the cluster is fully compromised from a single pod breakout. The audit described here does not require a container escape to assess this risk. It enumerates all service account RBAC bindings from the control plane and identifies which pods would become a critical escalation path if their container were compromised.
Enumerating All Service Account RBAC Bindings
Start with a complete inventory of what each service account can do:
# List all service accounts across all namespaces
kubectl get serviceaccounts --all-namespaces
# List all ClusterRoleBindings (cluster-wide RBAC)
kubectl get clusterrolebindings -o wide
# Find all ClusterRoleBindings that grant cluster-admin
kubectl get clusterrolebindings -o json | \
jq '.items[] | select(.roleRef.name == "cluster-admin") | \
{name: .metadata.name, subjects: .subjects}'
# List all RoleBindings in all namespaces
kubectl get rolebindings --all-namespaces -o wide
For a comprehensive audit, use kubectl-who-can (a kubectl plugin) to check what each service account can do:
# Install via krew
kubectl krew install who-can
# Find who can create pods (a common privilege escalation vector)
kubectl who-can create pods --all-namespaces
# Find who can exec into pods (another escalation vector)
kubectl who-can create pods/exec --all-namespaces
# Find who can list secrets (secrets may contain credentials)
kubectl who-can list secrets --all-namespaces
Any service account that appears in the output of cluster-admin binding queries or that can create pods, exec into pods, or list secrets is a high-priority finding. Document the workload that uses this service account and evaluate whether the permission is required.
Identifying Pods Running with Overprivileged Service Accounts
After finding overprivileged service accounts, identify which pods are using them:
# Find all pods using a specific service account
kubectl get pods --all-namespaces -o json | \
jq '.items[] | select(.spec.serviceAccountName == "my-service-account") | \
{namespace: .metadata.namespace, pod: .metadata.name, \
sa: .spec.serviceAccountName}'
# Find all pods running with automountServiceAccountToken not disabled
kubectl get pods --all-namespaces -o json | \
jq '.items[] | select(.spec.automountServiceAccountToken != false) | \
{namespace: .metadata.namespace, pod: .metadata.name, \
sa: .spec.serviceAccountName}'
Cross-reference: for each pod with an overprivileged service account, assess the attack surface:
- Is the pod internet-facing (LoadBalancer or Ingress)?
- Does the pod run user-supplied code or untrusted inputs?
- Is the pod running a known-vulnerable image version?
- Does the pod run as root (check
securityContext.runAsNonRoot: falseor absent)?
A pod running as root, facing the internet, running a known-vulnerable web framework, and mounted with a cluster-admin token is the highest-risk combination. Prioritize remediating these first.
Briefings like this, every morning before 9am.
Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.
Disabling Automount Where Tokens Are Not Needed
Most application workloads do not need to communicate with the Kubernetes API. For those pods, disable the automatic token mount.
At the pod spec level:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-web-app
spec:
template:
spec:
automountServiceAccountToken: false # Disable token mount
containers:
- name: app
image: my-app:1.0
At the service account level (applies to all pods using this SA by default):
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-web-app-sa
namespace: production
automountServiceAccountToken: false
The pod-level setting overrides the service account-level setting, so you can disable at the SA level and re-enable for specific pods that do need API access.
For workloads that do need API access: scope the service account permissions to the minimum required using a Role (namespace-scoped) rather than a ClusterRole:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: configmap-reader
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list"]
resourceNames: ["app-config"] # scope to specific named resource
Never use * in resources or verbs for production workload service accounts.
Migrating from Service Account Tokens to Workload Identity
The most common (and dangerous) pattern for Kubernetes workloads that need cloud provider access (AWS, GCP, Azure) is to mount cloud credentials as environment variables or Kubernetes secrets:
env:
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: aws-credentials
key: access_key_id
This pattern stores long-lived cloud credentials in Kubernetes secrets, which are base64-encoded (not encrypted) by default, and mounts them in every pod replica. An attacker who reads the secret from any pod gets long-lived cloud credentials.
Workload identity federation eliminates this pattern by allowing pods to authenticate to cloud APIs using their Kubernetes service account token, exchanged for a short-lived cloud provider credential at authentication time.
AWS: EKS Pod Identity or IRSA (IAM Roles for Service Accounts)
# Create an IAM role for the service account
eksctl create iamserviceaccount \
--name my-app-sa \
--namespace production \
--cluster my-cluster \
--attach-policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \
--approve
The pod then uses the AWS SDK which automatically exchanges the projected service account token for temporary STS credentials. No static credentials required.
GCP: GKE Workload Identity
# Bind a Kubernetes SA to a GCP service account
gcloud iam service-accounts add-iam-policy-binding \
my-gcp-sa@my-project.iam.gserviceaccount.com \
--role roles/iam.workloadIdentityUser \
--member "serviceAccount:my-project.svc.id.goog[production/my-app-sa]"
kubectl annotate serviceaccount my-app-sa \
--namespace production \
iam.gke.io/gcp-service-account=my-gcp-sa@my-project.iam.gserviceaccount.com
Bound Service Account Tokens vs Legacy Tokens
Kubernetes 1.20 introduced bound service account tokens (projected volumes) which expire and are audience-scoped. Legacy service account tokens (the secret-based tokens in older Kubernetes versions) do not expire and can be used to authenticate to any Kubernetes API server.
Check whether your cluster is using legacy long-lived tokens:
# Find all service account secret-based tokens (legacy non-expiring)
kubectl get secrets --all-namespaces -o json | \
jq '.items[] | select(.type == "kubernetes.io/service-account-token") | \
{namespace: .metadata.namespace, name: .metadata.name, \
serviceAccount: .metadata.annotations["kubernetes.io/service-account.name"]}'
In Kubernetes 1.24+, secret-based tokens are no longer automatically created. If your cluster still has them from a pre-1.24 era, they are long-lived and should be audited.
For bound tokens (the current default): check that your admission controller or Pod Security Standards require projected service account tokens with an expiry. The token path in the pod is typically /var/run/secrets/kubernetes.io/serviceaccount/token, and for bound tokens the audience, expiry, and issuer are validated by the API server on each API call.
Use the kubectl auth can-i command to verify what a specific token is permitted to do:
# Test permissions of a specific service account
kubectl auth can-i --list --as=system:serviceaccount:production:my-app-sa
This lists every resource and verb the service account can perform, which is the definitive permission inventory for that account.
The bottom line
Kubernetes service account tokens are the most commonly over-granted credentials in container environments. Audit ClusterRoleBindings for any service account with cluster-admin or broad secret/pod permissions, disable automount on every pod that does not need API access, migrate cloud credentials to workload identity federation, and run kubectl auth can-i --list for each high-risk service account to get the authoritative permission inventory. Schedule this audit quarterly; RBAC accumulates permissions the same way AD does.
Frequently asked questions
What is the difference between a Role and a ClusterRole in Kubernetes RBAC?
A Role is namespace-scoped: it grants permissions on resources within a specific namespace. A ClusterRole is cluster-scoped: it grants permissions across all namespaces or on cluster-scoped resources (nodes, persistent volumes, ClusterRoleBindings). A RoleBinding can bind either a Role or a ClusterRole to subjects within a namespace. Prefer Roles over ClusterRoles for application workloads unless the workload genuinely needs cross-namespace or cluster-scoped resource access. ClusterRoleBindings (which grant ClusterRole permissions cluster-wide) are the highest-risk RBAC object and should be audited most carefully.
How do I find pods that are mounting the default service account token?
The default service account exists in every namespace. Find pods using it: `kubectl get pods --all-namespaces -o json | jq '.items[] | select(.spec.serviceAccountName == "default" or .spec.serviceAccountName == null) | {ns: .metadata.namespace, pod: .metadata.name}'`. In many clusters, the default service account has no RBAC bindings, making it low-risk. But verify: run `kubectl auth can-i --list --as=system:serviceaccount:NAMESPACE:default` for each namespace to confirm the default SA has no permissions before dismissing it.
Can Kubernetes secrets be encrypted at rest?
Yes, but it is not enabled by default. Kubernetes etcd stores secret data base64-encoded (not encrypted) unless encryption at rest is explicitly configured via the kube-apiserver's `--encryption-provider-config` flag. For managed Kubernetes (EKS, GKE, AKS), enable the provider-managed encryption: in EKS, this is AWS KMS envelope encryption for secrets; in GKE, this is CMEK for etcd. Verify encryption is active by checking the encryption config: any secret retrieved with `kubectl get secret -o yaml` should show base64 data, and the underlying etcd value should be an encrypted blob, not the raw base64.
What Kubernetes RBAC permissions are considered dangerous privilege escalation paths?
The most dangerous permissions: (1) `create pods` in any namespace -- allows launching a pod with a privileged service account or hostPath mount, (2) `create pods/exec` or `get pods/exec` -- allows executing commands in existing pods, (3) `list/get secrets` -- allows reading credentials stored as secrets, (4) `create clusterrolebindings` or `create rolebindings` -- allows granting arbitrary permissions to any identity, (5) `patch deployments` -- allows modifying existing deployments to inject a malicious container. Any service account with these permissions should be treated as a critical privilege escalation risk.
How do I enforce that all pods must use explicitly named service accounts rather than the default one?
Create an OPA Gatekeeper or Kyverno policy that denies pod creation when spec.serviceAccountName is set to 'default' or is unset. In Gatekeeper, write a ConstraintTemplate with a Rego rule that checks `.spec.serviceAccountName != "default"`. In Kyverno, use a validate rule with `pattern: {spec: {serviceAccountName: '?*'}}` combined with a deny rule for the default SA name. Apply the constraint in Warn mode first to identify existing workloads that rely on the default service account, then remediate those before switching to Deny mode. This forces development teams to explicitly declare which service account each workload uses, making permissions auditable.
What Kubernetes RBAC permissions are most dangerous to grant to service accounts?
The highest-risk permissions for service accounts: get/list/watch on secrets (allows reading all secrets in the namespace or cluster), create on pods (allows injecting a privileged pod to escape to the node), create on deployments or daemonsets with hostPID or hostNetwork (allows host-level access), create on clusterrolebindings or rolebindings (allows escalating the service account's own permissions), and impersonate on users or service accounts (allows acting as any other principal). The wildcard verb on any resource is equivalent to cluster admin for that resource. Audit service account bindings using: kubectl get rolebindings,clusterrolebindings -A -o json | jq '.items[] | select(.subjects[]?.kind=="ServiceAccount")' to enumerate all service accounts with role bindings and the permissions those roles include.
Sources & references
Free resources
Critical CVE Reference Card 2025–2026
25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.
Ransomware Incident Response Playbook
Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.
Get threat intel before your inbox does.
50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.
Unsubscribe anytime. We never sell your data.

Founder & Cybersecurity Evangelist, Decryption Digest
Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.
Win a $2,495 Black Hat pass.
Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.
