Prefer Using Secrets As Files Over Secrets As Environment
More Info:
Secrets exposed as environment variables are more easily leaked through logs, child processes, and crash dumps. Mount secrets as files instead and read them from the filesystem.
Risk Level
Medium
Address
Security
Compliance Standards
- CIS OKE
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
List pods that reference Secrets in env/envFrom
- On any machine with kubectl access:
kubectl get pods -A -o json \| jq -r '.items[]| select(([.spec.containers[], (.spec.initContainers // [])[]]| map((.env // [])[]?.valueFrom.secretKeyRef? // empty+ (.envFrom // [])[]?.secretRef? // empty)| length) > 0)| "\(.metadata.namespace) \(.metadata.name)"'
- On any machine with kubectl access:
-
Inspect how each affected pod uses Secrets
- For each
<namespace> <pod>from step 1, get its full spec:kubectl get pod <pod-name> -n <namespace> -o yaml - Review for
env:andenvFrom:entries withsecretKeyRef/secretRef, and check whether there is already an alternativevolume/volumeMountusing the same Secret.
- For each
-
Review application code / startup scripts for each pod
- For each affected workload (Deployment/StatefulSet/Job, etc.), identify the image and owning controller:
kubectl get pod <pod-name> -n <namespace> -o jsonpath='{.metadata.ownerReferences[0].kind}{" "}{.metadata.ownerReferences[0].name}{" "}{.spec.containers[*].image}{"\n"}'
- Using your source control or image documentation, determine whether the application reads these secret values from environment variables, or can instead read from files (e.g., configurable paths, options, or library support).
- For each affected workload (Deployment/StatefulSet/Job, etc.), identify the image and owning controller:
-
Decide and implement code/config changes to use mounted files
- If the application can be changed, update code or configuration to read secrets from known filesystem paths (for example
/var/run/secrets/<name>), and rebuild/publish images as needed. - If the application cannot reasonably be changed (legacy, third‑party, or breaks existing integrations), document the exception and rationale for continuing to use environment variables.
- If the application can be changed, update code or configuration to read secrets from known filesystem paths (for example
-
Refactor pod specs to mount Secrets as files instead of env vars
- For each controller owning affected pods, edit its manifest to:
- Add a Secret volume and mount path. Example (edit with
kubectl editor your GitOps/IaC):spec:template:spec:volumes:- name: app-secretsecret:secretName: my-secretcontainers:- name: appvolumeMounts:- name: app-secretmountPath: /var/run/secrets/my-secretreadOnly: true# Remove or minimize:# env:# - name: SECRET_VALUE# valueFrom:# secretKeyRef:# name: my-secret# key: key1
- Add a Secret volume and mount path. Example (edit with
- Apply updated manifests with
kubectl apply -f <file.yaml>from any machine with kubectl access.
- For each controller owning affected pods, edit its manifest to:
-
Verify remaining use of Secrets in environment variables
- After rollouts complete, re-run the evidence-gathering query:
kubectl get pods -A -o json \| jq -r '.items[]| select(([.spec.containers[], (.spec.initContainers // [])[]]| map((.env // [])[]?.valueFrom.secretKeyRef? // empty+ (.envFrom // [])[]?.secretRef? // empty)| length) > 0)| "\(.metadata.namespace) \(.metadata.name)"'
- Confirm that only pods with documented exceptions still appear, and that the rest use mounted Secret volumes instead.
- After rollouts complete, re-run the evidence-gathering query:
Using kubectl
Using kubectl
Run these commands from any machine with kubectl access.
1. List pods that use env or envFrom (cluster-wide)
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{" "}{.metadata.name}{"\n"}{range .spec.containers[*]}{" container: "}{.name}{"\n"}{" env: "}{range .env[*]}{.name}{"="}{@}{"\n"}{end}{" envFrom: "}{range .envFrom[*]}{@}{"\n"}{end}{"\n"}{end}{"\n"}{end}'
Problem indication:
- Any
enventry that includesvalueFrom.secretKeyRef. - Any
envFromentry that includessecretRef.
These are containers consuming secrets via environment variables.
2. Inspect a specific pod’s environment for secret usage
Replace <namespace> and <pod>:
kubectl get pod <pod> -n <namespace> -o yaml
Check under spec.containers[].env and spec.containers[].envFrom:
env[].valueFrom.secretKeyRef→ secret as environment variable (problem).envFrom[].secretRef→ entire secret as environment variables (problem).
Also confirm whether the same pod (or deployment) already mounts the secret as a volume under spec.volumes and spec.containers[].volumeMounts; if so, environment use is usually unnecessary.
3. Identify higher-level controllers using secrets as env
To fix properly, you must usually modify the controller (Deployment, StatefulSet, etc.), not the Pod.
Find the owning controller of a pod:
kubectl get pod <pod> -n <namespace> -o jsonpath='{.metadata.ownerReferences}'
Then inspect the controller spec:
kubectl get deployment <name> -n <namespace> -o yaml
kubectl get statefulset <name> -n <namespace> -o yaml
kubectl get daemonset <name> -n <namespace> -o yaml
Problem indication in these specs:
- Any
env[].valueFrom.secretKeyRefunderspec.template.spec.containers[]. - Any
envFrom[].secretRefunderspec.template.spec.containers[].
4. Confirm where secrets are used as volumes (for comparison)
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{" "}{.metadata.name}{"\n"}{range .spec.volumes[*]}{" volume: "}{.name}{" "}{@}{"\n"}{end}{"\n"}{end}'
Healthy indication:
- Secrets appear under
spec.volumes[].secret.secretName. - Corresponding
spec.containers[].volumeMounts[].namereference those secret volumes. - No (or minimized) use of the same secrets via
env/envFrom.
Human review required:
- Decide, per application, whether you can change the code/config to read from mounted files instead of environment variables.
- Plan and implement changes in manifests and application code; there is no safe automated
kubectltransformation.
Automation
#!/usr/bin/env bash
# Report pods that use Kubernetes Secrets as environment variables
# Run on: any machine with kubectl access and current context set
set -euo pipefail
# Header
echo "NAMESPACE,POD,CONTAINER,ENV_TYPE,REF_KIND,REF_NAME,ENV_VAR"
# Function to process a pod JSON and emit CSV lines
kubectl get pods --all-namespaces -o json \
| jq -r '
.items[]
| . as $pod
| ($pod.metadata.namespace // "default") as $ns
| ($pod.metadata.name) as $podname
| [
# regular containers
($pod.spec.containers[]? | {type: "container", name, env, envFrom}),
# init containers
($pod.spec.initContainers[]? | {type: "initContainer", name, env, envFrom})
]?
| select(. != null)
| . as $c
|
# 1) env vars directly from secretKeyRef
(
$c.env[]?
| select(.valueFrom.secretKeyRef != null)
| [
$ns,
$podname,
$c.name,
($c.type),
"SecretKeyRef",
.valueFrom.secretKeyRef.name,
.name
]
| @csv
),
# 2) envFrom entries from Secret
(
$c.envFrom[]?
| select(.secretRef != null)
| [
$ns,
$podname,
$c.name,
($c.type),
"SecretRef",
.secretRef.name,
"*"
]
| @csv
)
'
cat <<'EOF'
Explanation:
- Any CSV line in the output indicates a pod/container that exposes a Secret as environment variables.
- Columns:
NAMESPACE : Namespace of the pod
POD : Pod name
CONTAINER : Container or initContainer name
ENV_TYPE : "container" or "initContainer"
REF_KIND : "SecretKeyRef" = individual env var from secret; "SecretRef" = all keys from secret via envFrom
REF_NAME : Name of the Secret object
ENV_VAR : Specific env var name, or "*" when all keys are imported via envFrom
What indicates a problem:
- Any non-empty output means there are pods using Secrets as environment variables.
- Focus review on these lines and consider refactoring those workloads to mount the Secret as a volume
and read it from the filesystem instead of via environment variables.
EOF