CWE-526: Cleartext Storage of Sensitive Information in an Environment Variable
Overview
This weakness occurs when an application stores sensitive information - credentials, API keys, encryption keys - unencrypted in an environment variable. Environment variables are a common place to hold configuration in containerized and cloud environments, but anything with process or debug access can read them. They leak through error pages that dump the environment, debug endpoints that expose process state, server-side template injection that renders environment context, and logging misconfigured to capture the whole environment.
Relationship to Other CWEs
The name of this entry is the test for whether it fits. A secret sitting unencrypted at rest anywhere other than the process environment belongs against its parent, CWE-312 (Cleartext Storage of Sensitive Information), not here - MITRE asks you not to reach for a narrower entry than the finding actually supports.
The pages around it differ by where the secret is being kept:
- CWE-526 (this page) - the secret is in the process environment, inherited by every child process and readable by anything with process, orchestrator, or crash-dump access
- CWE-312 (Cleartext Storage of Sensitive Information) - the parent, and the right level for unencrypted storage in any other medium: a database column, a file on disk, a log, a backup. Report against this page only when the environment is where the secret lives
- CWE-798 (Use of Hard-coded Credentials) - the credential is a literal in source rather than in the environment. That page offers an environment variable as its minimum fallback, so a CWE-798 finding "fixed" by moving the literal into the environment arrives here; the fix both pages actually want is a secrets manager
- CWE-547 (Use of Hard-coded, Security-relevant Constants) - not a storage-location question at all, but the same security-relevant value written out as a literal in several places, so a policy change updates some and misses the rest. It reaches this page only because its guidance also covers what a checked-in configuration file may and may not hold
- CWE-522 (Insufficiently Protected Credentials) - the general credential-protection page, and where to go for the full storage, transmission, and rotation picture once the environment variable itself is dealt with
OWASP Classification
A02:2025 - Security Misconfiguration
Risk
High: Exposed environment variables often contain database passwords, API keys, encryption keys, OAuth client secrets, and AWS credentials. An attacker who reads them can use those credentials directly - there is no further weakness to exploit, because the credential is the access.
Remediation Steps
Core Principle: Don't put long-lived secrets in the process environment at all. Fetch them at runtime from a secrets manager, or mount them as files with restricted permissions, so there is nothing sensitive in the environment for a leak channel to disclose. Closing the leak channels is worth doing, but on its own it hides the finding rather than removing the secret.
Use Secret Management Systems (Primary Defense)
The finding is that a secret lives in an environment variable, so the fix is to move it out of one. Fetch it at runtime from a secrets manager or platform secret mechanism, leaving the process environment holding only a reference - a secret name, a vault path, a role identifier - and never the credential itself:
// VULNERABLE - the credential sits in the environment for the whole process lifetime
db_password = env('DB_PASSWORD')
// SECURE - fetched at runtime from a secrets manager, never stored as a plain env var
function get_secret(secret_name):
return secrets_manager.get_secret_value(secret_name)
db_password = get_secret('prod/db/password') // the environment holds the name, not the value
This also buys rotation: a secret fetched at process start or per use can be rotated centrally, while an environment variable is fixed until the process restarts. Where the platform supports it, prefer short-lived credentials issued to a workload identity (IAM roles for service accounts, managed identities, OIDC-federated tokens) over any long-lived secret, fetched or otherwise - there is then no stored credential to leak at all.
The distinction worth keeping straight is injection versus storage. An environment variable is a legitimate way for a container runtime or orchestrator to hand a process a value it fetched from a secret store at start-up, and a poor place to keep a long-lived secret: it is inherited by every child process, captured by crash dumps and error-reporting SDKs, visible through docker inspect and kubectl describe pod, and cannot be rotated without a restart. A finding here is about the second case.
The sections below close the channels through which a process environment gets disclosed. Treat them as defense in depth for the window where an environment variable is genuinely unavoidable - a platform that injects configuration no other way, a service part-way through migration. Each one makes a scanner quieter without removing the secret, so do them in addition to the move above, not instead of it.
Remove Debug Endpoints (Defense in Depth)
// VULNERABLE - dumps the entire process environment
route('/debug/env', handler = () => json(all_environment_variables()))
// SECURE - delete this route entirely in production; don't just gate it behind a flag
Filter Error Messages (Defense in Depth)
// VULNERABLE - debug mode returns the stack trace and environment dump to the caller
app.config.debug = true
// SECURE - generic response to the caller, full detail logged server-side only
app.config.debug = false
on_unhandled_error(e):
log.error('unhandled error', e, include_stack_trace = true)
return { error: 'internal server error' }, 500
Sanitize Logs (Defense in Depth)
// SECURE - redact common secret patterns before a log line is written
function redact_secrets(message):
return replace_pattern(message, /(password|api[_-]?key|token)=\S+/i, '$1=***')
log_filter = redact_secrets // registered on the logger, applied to every record
A regex filter is a safety net, not the primary control - it only catches patterns it was written to recognize. Keep secrets out of log-worthy data in the first place: use structured logging with an explicit allowlist of fields instead of logging whole request/response objects.
Prevent Server-Side Template Injection (Defense in Depth)
// VULNERABLE - user input compiled and executed as a template
template = compile_template(user_input) // attacker input: {{config.items()}}
output = template.render() // executes attacker-supplied template directives
// SECURE - user input is data passed to a fixed template, never compiled as one
output = render_fixed_template('<div>{{ user_data }}</div>', user_data = user_input)
Container Security
For Docker/Kubernetes:
- Use secrets management (external secret stores, Docker secrets, or Kubernetes Secrets with RBAC and encryption at rest)
- Prefer mounting secrets as files over injecting them as environment variables. A mounted secret file isn't inherited by every child process the way an environment variable is, and doesn't appear in
/proc/<pid>/environ. On the host, access to/proc/<pid>/environis governed by aPTRACE_MODE_READ_FSCREDScheck on top of the file's own owner-read permissions, which in the ordinary case means the owner of the process and root.CAP_SYS_PTRACEalso satisfies the check, but only held in the target's user namespace, and an LSM can refuse regardless -ps ewwreads it, while plainpsreads/proc/<pid>/cmdlineand never shows the environment. The wider exposures are outside the host:docker inspectreturns the fullEnvarray to anyone with Docker socket access, an ECS task definition and akubectl describe podshow env values to anyone with read access, and crash dumps and error-reporting SDKs routinely capture the whole environment - Scan container images for exposed credentials
- Use minimal base images
Additional Resources
- CWE-526: Cleartext Storage of Sensitive Information in an Environment Variable
- OWASP Top 10 2025 A02: Security Misconfiguration
- 12-Factor App: Config
- proc_pid_environ(5): Linux manual page - access to
/proc/<pid>/environis governed by aPTRACE_MODE_READ_FSCREDScheck