Skip to main content
cd ../config-traps
risk/register/namespace-wide-rbac-read-on-secrets-exposes-every-teams-credentials.html
Kubernetes Secrets Managementhigh severityKubernetes

Namespace-Wide RBAC Read on Secrets Lets Any Pod Read Every Team's Credentials

Severity
high
Reviewed
18 Aug 2026
Remediation
~20 minutes
Overview

A Role granting get/list/watch on secrets without resourceNames lets any pod's ServiceAccount read every Secret in the namespace, not just its own, when combined with default token automounting.

Operational summary

At a glance

Symptom
A platform team notices during a routine access review that a workload's service account can read Secret objects belonging to unrelated applications in the…
Likely cause
The Role manifest defined the rule as:rules: - apiGroups: [""] resources: ["secrets"] verbs: ["get", "list", "watch"]This grants get/list/watch on all Secret objects in whichever…
Impact
Any code executing inside the affected pod, including third-party or dynamically loaded plugin code, can enumerate and read every Secret in the namespace, including database credentials, TLS keys…
Verification signal
Validation must directly re-test the exact access path identified during diagnosis, not merely confirm the manifest changed.
Safe correction
Correct the misconfiguration in two independent layers, because either one alone leaves residual risk.
Rollback or recovery
If the corrected Role breaks the application (for example because it also legitimately reads a second Secret that was not identified during diagnosis), revert the Role to its…

Symptom

A platform team notices during a routine access review that a workload’s service account can read Secret objects belonging to unrelated applications in the same namespace. The workload was only ever intended to read its own single Secret, yet a quick kubectl auth can-i check against other Secret names in the namespace returns yes. No incident has occurred yet, but the access review flags it because the workload is internet-facing and runs third-party code via a plugin system.

False Assumption

The team assumed that because the Role was named after the specific application (for example payments-api-role) and was only bound to that application’s ServiceAccount, its permissions were scoped to that application’s own Secret. In Kubernetes RBAC, a Role’s resources: ["secrets"] rule without a resourceNames restriction grants access to every Secret object in the Role’s namespace, not just the one the application creates or consumes. Naming a Role narrowly does not narrow what it actually authorises; only the rule content does that.

Root Cause

The Role manifest defined the rule as:

rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "list", "watch"]

This grants get/list/watch on all Secret objects in whichever namespace the Role is deployed to, because no resourceNames field was set. The RoleBinding then attached this Role to the application’s ServiceAccount. Because Kubernetes documentation on RBAC explicitly separates the authorisation model (rules, verbs, resources, optional resourceNames) from authentication and workload identity, the missing resourceNames field is not a bug in Kubernetes; it is a correctly-applied but under-scoped rule. The default ServiceAccount token was also automounted into the pod (no automountServiceAccountToken: false set at pod or ServiceAccount level), so any process running inside that pod, including a compromised plugin or dependency, inherits the same broad read access to every Secret in the namespace.

Impact

Any code executing inside the affected pod, including third-party or dynamically loaded plugin code, can enumerate and read every Secret in the namespace, including database credentials, TLS keys and API tokens belonging to unrelated teams sharing that namespace. This converts a single compromised or malicious in-pod dependency into a namespace-wide credential-exfiltration path. The severity depends on what else shares the namespace; on a namespace containing only one team’s low-value test data, the blast radius is limited, but on a shared or multi-tenant namespace it is not.

Diagnosis

Confirm the scope of the problem before changing anything. First, identify what the ServiceAccount can actually do, not what the Role’s name implies:

kubectl auth can-i list secrets 
  --as=system:serviceaccount:payments:payments-api-sa 
  -n payments

A yes response confirms namespace-wide list access. Next, inspect the Role rule directly to confirm the absence of a resourceNames restriction:

kubectl get role payments-api-role -n payments -o yaml

Look for the secrets resource entry and confirm there is no resourceNames key limiting it to a specific Secret. Then check whether the pod actually mounts a token at all, since a Role with broad rights is only exploitable from inside the pod if a token is present:

kubectl get pod payments-api-7f9c8-abcde -n payments 
  -o jsonpath='{.spec.automountServiceAccountToken}'

An empty result or true (the Kubernetes default when unset) indicates the token is mounted, satisfying the second half of this trap. Together, the RBAC rule and the token automount confirm both the authorisation gap and the delivery path into the pod.

Correction

Correct the misconfiguration in two independent layers, because either one alone leaves residual risk. First, scope the Role to the single Secret the application actually needs using resourceNames:

rules:
- apiGroups: [""]
  resources: ["secrets"]
  resourceNames: ["payments-api-credentials"]
  verbs: ["get"]

Note that list and watch cannot be meaningfully combined with resourceNames for namespace-scoped enumeration semantics in the same way get can; if the application only ever fetches a known Secret by name, keep the verb list to get alone. Second, disable automatic token mounting for workloads that do not need the Kubernetes API at all, or that only need it for this one narrow read:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: payments-api-sa
  namespace: payments
automountServiceAccountToken: false

If the pod does need the token for this specific narrow purpose, keep automount enabled but rely on the corrected Role rather than disabling automount, since the two controls address different failure paths and are not interchangeable.

Validation

Validation must directly re-test the exact access path identified during diagnosis, not merely confirm the manifest changed. After applying the corrected Role, re-run the same can-i check against an unrelated Secret in the namespace:

kubectl auth can-i get secret unrelated-team-secret 
  --as=system:serviceaccount:payments:payments-api-sa 
  -n payments

Expect no. Then confirm the application’s own required Secret is still readable:

kubectl auth can-i get secret payments-api-credentials 
  --as=system:serviceaccount:payments:payments-api-sa 
  -n payments

Expect yes. Finally, exec into a running instance of the pod (in the isolated validation environment) and confirm the application still starts and reaches its dependencies correctly, since an over-corrected RBAC rule that removes a verb the application genuinely needs will produce a new, different failure at runtime rather than at deploy time.

Rollback

If the corrected Role breaks the application (for example because it also legitimately reads a second Secret that was not identified during diagnosis), revert the Role to its previous rule set using the manifest retained from before the change, then re-run diagnosis to identify every Secret the application actually touches before reapplying a corrected, fully-scoped rule. Do not restore the original unscoped rule as a permanent fix; treat any rollback as temporary while the full set of required Secret names is confirmed. Keep the pre-change Role manifest and RoleBinding as version-controlled artefacts specifically so this reversion is a single kubectl apply -f of a known-good file rather than a manually reconstructed guess.

Prevention

Require resourceNames on any RBAC rule granting access to secrets as a standard policy check in CI or admission control, so a namespace-wide grant cannot merge without an explicit, reviewed exception. Set automountServiceAccountToken: false as the default on ServiceAccount manifests and require an explicit opt-in comment or annotation when a workload genuinely needs API access, so the reviewer sees the decision rather than inheriting it silently. Periodically re-run the same kubectl auth can-i enumeration used in diagnosis against every ServiceAccount in shared namespaces as a scheduled access review, since RBAC drift accumulates quietly as Roles are copied and extended over time.

03

Apply the safer control

Before you change production

Confirm the affected scope, export the current configuration, and test the replacement control in a non-production environment first.

Fix commands and configuration

kubectl auth can-i
yes
payments-api-role
04

Verify, roll back or escalate

Verify

Validation must directly re-test the exact access path identified during diagnosis, not merely confirm the manifest changed.

Rollback

If the corrected Role breaks the application (for example because it also legitimately reads a second Secret that was not identified during diagnosis), revert the Role to its previous rule set using the manifest retained from before the change, then…

Escalate

Escalate when the blast radius is uncertain, the control cannot be tested safely, or remediation requires an outage or security exception.

After remediation

Further reading stays below the corrective workflow and is selected by platform, category and shared technical keywords.

Discover more

Connected KBY resources