Sensitive Data Exposed Through Container Environment Variables

Critical Risk Infrastructure Security
containersdockerkubernetesenvironment-variablessecrets-exposureapi-keyspasswordscredentialsprocess-disclosure

What it is

A critical security vulnerability where sensitive information such as database passwords, API keys, authentication tokens, and encryption keys are passed to containers through environment variables. These secrets become visible in process lists, container inspection commands, orchestration platform UIs, logs, and crash dumps. Environment variables are inherited by child processes and can be accessed by any process running within the container.

# VULNERABLE: Kubernetes Pod with secrets in environment variables
apiVersion: v1
kind: Pod
metadata:
  name: vulnerable-app
  namespace: production
spec:
  containers:
  - name: webapp
    image: webapp:latest
    # VULNERABLE: Secrets directly in environment variables
    env:
    - name: DATABASE_PASSWORD
      value: "prod_database_password_123"
    - name: API_KEY
      value: "sk_live_1234567890abcdef"

# VULNERABLE: Deployment using Secret as environment variable
apiVersion: apps/v1
kind: Deployment
metadata:
  name: vulnerable-deployment
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      containers:
      - name: webapp
        image: webapp:latest
        # VULNERABLE: Secret exposed through environment
        env:
        - name: DATABASE_PASSWORD
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: db-password
# SECURE: Kubernetes Pod with secrets mounted as files
apiVersion: v1
kind: Pod
metadata:
  name: secure-app
  namespace: production
spec:
  containers:
  - name: webapp
    image: webapp:latest
    # SECURE: No secrets in environment
    env:
    - name: SECRETS_PATH
      value: "/run/secrets"
    # SECURE: Secrets mounted as files
    volumeMounts:
    - name: app-secrets
      mountPath: "/run/secrets"
      readOnly: true
  volumes:
  - name: app-secrets
    secret:
      secretName: app-secrets
      defaultMode: 0400

# SECURE: Deployment with secrets as volume mount
apiVersion: apps/v1
kind: Deployment
metadata:
  name: secure-deployment
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      containers:
      - name: webapp
        image: webapp:latest
        # SECURE: Secrets as volume mount
        volumeMounts:
        - name: secrets
          mountPath: "/run/secrets"
          readOnly: true
      volumes:
      - name: secrets
        secret:
          secretName: app-secrets
          defaultMode: 0400

💡 Why This Fix Works

The vulnerable configuration passes secrets through environment variables, making them visible in process lists, kubectl describe output, and to any process within the container. The secure version mounts secrets as files in /run/secrets with read-only permissions (0400), preventing exposure through environment variable inspection while still allowing the application to read secrets from the filesystem.

Why it happens

Kubernetes Pod and Deployment manifests use the env or envFrom fields to inject secrets directly into container environment variables. When developers use env with secretKeyRef or envFrom with secretRef, the secret values become accessible to any process within the container via /proc/*/environ, visible in kubectl describe pod output, exposed through container runtime APIs (docker inspect, crictl inspect), and inherited by all child processes spawned within the container, creating widespread secret exposure.

Root causes

Secrets Passed Through env or envFrom

Kubernetes Pod and Deployment manifests use the env or envFrom fields to inject secrets directly into container environment variables. When developers use env with secretKeyRef or envFrom with secretRef, the secret values become accessible to any process within the container via /proc/*/environ, visible in kubectl describe pod output, exposed through container runtime APIs (docker inspect, crictl inspect), and inherited by all child processes spawned within the container, creating widespread secret exposure.

Hardcoded Credentials in Dockerfile ENV Instructions

Dockerfile contains ENV instructions with embedded secrets like ENV DATABASE_PASSWORD=secret123 or ENV API_KEY=sk_live_abc. These hardcoded credentials persist in the container image layers, become visible in docker image history output, get distributed to all environments (dev, staging, production) using the same image, and remain in container registries indefinitely. Image layer inspection tools can extract these secrets even after containers are terminated.

ConfigMaps with Sensitive Data as Environment Variables

Kubernetes ConfigMaps containing sensitive information (database credentials, API tokens, encryption keys) referenced via envFrom.configMapRef or env.valueFrom.configMapKeyRef. ConfigMaps lack encryption-at-rest protection in etcd, have broader RBAC permissions than Secrets, appear in plaintext in kubectl get configmap -o yaml output, and when used as environment variables, expose sensitive data through the same channels as direct secret environment variable injection.

Docker Run Commands with -e Flags Exposing Secrets

Docker containers launched with docker run -e DATABASE_PASSWORD=secret or docker-compose files using environment sections with inline secret values. These secrets become visible in ps aux output showing the docker command, persist in Docker daemon logs, appear in container inspect JSON output, get logged by process monitoring tools, and are accessible to any user with docker inspect permissions on the host, expanding the attack surface beyond the container itself.

Secrets Visible in Introspection Commands and Process Lists

Secrets stored as environment variables are discoverable through multiple introspection mechanisms: kubectl describe pod shows env configuration, kubectl exec allows attackers to run env or printenv commands, /proc/self/environ and /proc/*/environ files expose all environment variables to any process with read access, ps -eww displays full environment for all processes, and container crash dumps or core files capture the complete environment variable state, creating persistent exposure even after container termination.

Fixes

1

Use Kubernetes Secret Volumes Instead of Environment Variables

Configure Kubernetes Pods to mount Secrets as volumes rather than injecting them as environment variables. In the Pod spec, define volumes with type secret and mount them to containers using volumeMounts. For example: volumes: [{name: app-secrets, secret: {secretName: app-secrets}}] and volumeMounts: [{name: app-secrets, mountPath: /run/secrets, readOnly: true}]. This approach prevents secret exposure through kubectl describe, container inspection APIs, and process listings, as secrets are only accessible as files within the mounted directory.

2

Mount Secrets to Standard Paths with Read-Only Permissions

Mount secret volumes to conventional paths like /run/secrets (Docker Swarm convention) or /etc/secrets with readOnly: true and restrictive file permissions using defaultMode: 0400 (owner read-only). Set these permissions in the Secret volume definition: secret: {secretName: app-secrets, defaultMode: 0400}. Configure applications to read secrets from files in these directories (e.g., reading /run/secrets/db-password). Read-only mounts and restrictive permissions prevent modification by compromised processes and limit exposure to only the container's primary process user.

3

Integrate External Secret Management Systems

Implement dedicated secret management solutions like HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, or Google Secret Manager using Kubernetes operators or CSI drivers. Use External Secrets Operator to sync external secrets into Kubernetes Secrets, or use Secrets Store CSI Driver to mount secrets directly from external providers as volumes. These systems provide encryption-at-rest, encryption-in-transit, fine-grained access control, comprehensive audit logging, automatic secret rotation, and separation of secret storage from application infrastructure, significantly enhancing security posture.

4

Implement Automated Secret Rotation and Access Auditing

Configure automatic secret rotation policies in your secret management system to regularly update credentials (e.g., rotating database passwords every 30-90 days). Use Kubernetes admission controllers like OPA Gatekeeper or Kyverno to enforce policies preventing secrets as environment variables and requiring volume mounts. Enable audit logging in Kubernetes API server (--audit-log-path) to track secret access events. Monitor secret mount events and file access patterns within containers using runtime security tools like Falco to detect unauthorized secret access attempts.

5

Eliminate Secret Logging in Application Startup

Review and remediate application startup scripts, entrypoint.sh files, and initialization code to ensure they never log, echo, or print environment variables or secret file contents. Replace debugging statements like echo $DATABASE_PASSWORD or printenv with secure alternatives. Implement structured logging that explicitly excludes secret fields. Use secret-scanning tools in CI/CD pipelines (like git-secrets, truffleHog, or GitHub secret scanning) to detect accidental secret logging. Configure log aggregation systems to redact patterns matching common secret formats (API keys, tokens, passwords) before storage.

Detect This Vulnerability in Your Code

Sourcery automatically identifies sensitive data exposed through container environment variables and many other security issues in your codebase.