ConfigMap secrets-bundle/app-credentials exposes credential-shaped keys (aws_secret_access_key, database_dsn, db_password, jwt_token, oauth_client_secret) in plaintext
Scope · Object
ConfigMap secrets-bundle/app-credentials: readable by every subject with get configmaps in secrets-bundle, ships unencrypted in etcd, surfaces in kubectl describe and audit logs
Category: Data Exfiltration
Resource: ConfigMap/secrets-bundle/app-credentials
Namespace: secrets-bundle
ConfigMap secrets-bundle/app-credentials contains keys with names matching credential-like patterns: aws_secret_access_key, database_dsn, db_password, jwt_token, oauth_client_secret. The Kubernetes API treats ConfigMaps as non-sensitive: etcd stores them unencrypted by default (encryption-at-rest is opt-in and almost always limited to Secrets), kubectl describe configmap prints values inline, audit logs include the data on get/list, and RBAC defaults give workload service accounts much wider read on ConfigMaps than on Secrets.
Storing a credential in a ConfigMap therefore violates the basic Kubernetes data-classification model in three ways simultaneously: (1) it appears in plaintext to anyone with get configmaps (a much larger set of subjects than get secrets); (2) it ends up in cluster backups, etcd dumps, and platform observability tooling that explicitly excludes Secrets; (3) it does not benefit from any of the surface area Kubernetes builds around Secrets (envelope encryption, file-mode 0600 projection, KMS provider, External Secrets Operator).
The matched keys are heuristic, since key matches both apiKey and public_key, so review is required before assuming compromise. In practice the pattern is strong: in production clusters this finding correlates with real leaks the majority of the time. Treat as exposed-until-proven-otherwise.
Impact
If any of the flagged keys (aws_secret_access_key, database_dsn, db_password, jwt_token, oauth_client_secret) actually hold a credential, that credential is exposed to a wide audience: every workload SA in secrets-bundle, every backup operator, every audit log consumer, and possibly cluster-mirroring tooling.
How an attacker abuses this
BackgroundResourceConfigMap
A ConfigMap stores plain-text configuration that pods read at startup. They are not meant to hold secrets, but in practice teams put database URLs (with passwords), API keys, and tokens in ConfigMaps. Kubesplaining flags credential-shaped keys for that reason.
- Attacker compromises a workload in
secrets-bundle whose ServiceAccount has get configmaps (the typical default view role grants it). - They
kubectl get cm app-credentials -n secrets-bundle -o yaml and read the matched keys directly. No decoding needed since ConfigMap data is plaintext. - They identify the credential class (DB connection string, API key, OAuth client_secret, signing key) from the key name and value shape.
- They use the credential immediately: DB connection strings often grant the same write permission the application has; API keys often have no IP restriction; client_secrets unlock the upstream identity provider.
- Because audit-log review on ConfigMaps is rare, the read goes unnoticed until the upstream credential is rotated for unrelated reasons.
Remediation
Move the credential out of secrets-bundle/app-credentials into a Kubernetes Secret (or, better, an external secret store) and remove the keys from the ConfigMap.
- Inspect each matched key (
aws_secret_access_key, database_dsn, db_password, jwt_token, oauth_client_secret) to confirm whether it is a real credential. Some keys like cache_key_prefix are false positives. - For real credentials, create a Secret in
secrets-bundle and update consumers (envFrom or volume mounts) to read from the Secret. - Rotate the credential at its source: if it was already in plaintext in a ConfigMap, treat it as compromised (assume any subject with view-on-configmaps had access).
- Remove the credential keys from
secrets-bundle/app-credentials. Verify the consumer still works (kubectl rollout status). - Wire prevention: a Kyverno cluster policy that warns on
ConfigMap.data keys matching password|secret|token|credential|api_?key|access_?key|client_secret|connection_string|dsn. Pair with External Secrets Operator so the right path of least resistance is to use a real secret store.
Evidence
Matched keysaws_secret_access_keydatabase_dsndb_passwordjwt_tokenoauth_client_secret
Show raw JSON
{
"matched_keys": [
"aws_secret_access_key",
"database_dsn",
"db_password",
"jwt_token",
"oauth_client_secret"
]
}