VNX-515: Covert Storage Channel: secret placed in the process environment
Detects Python code that stores a password, secret, token, API key or credential in its own process environment (os.environ[...] =, os.putenv, os.environ.setdefault/update), where every child process inherits it and other processes of the same user can read it.
| Field | Value |
|---|---|
| Rule ID | VNX-515 |
| Severity | Low |
| SARIF level | note |
| Kind | sast |
| Languages | Python |
| CWE | CWE-515 |
| CAPEC | CAPEC-66 |
| ATT&CK | T1190 |
| CWSS vector | CWSS:1.0/TI:L/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:L/DI:H/EX:L/EC:N/P:L |
| Tags | covert-storage-channel environment secrets cwe-515 |
| Source | internal/sast/rules/vnx-515.rego |
Overview
VNX-515 maps to CWE-515: Covert Storage Channel. A covert storage channel moves information through shared storage that was never meant to carry it. Most such channels (file metadata, timing, lock files) cannot be recognised line by line. The one ordinary application code creates routinely is the process environment: a secret written to os.environ is inherited by every child process the program starts and can be read by other processes of the same user (and root) through /proc/<pid>/environ, ps e and crash dumps.
The rule reports os.environ[KEY] = value, os.putenv(KEY, value), os.environ.setdefault(KEY, value) and os.environ.update({KEY: value}) / update(KEY=value) (also via from os import environ) when the key names a password, secret, token, API key, private or access key or credential, or when the key is a variable and the value is a variable named as one.
What Gets Flagged
password = getpass.getpass("DB password: ")
os.environ["PGPASSWORD"] = password # FLAGGED
os.putenv("AWS_SECRET_ACCESS_KEY", secret) # FLAGGED
os.environ.update({"VENDOR_API_KEY": api_key}) # FLAGGED
What Is Not Flagged
env = os.environ.copy()
env["PGPASSWORD"] = password # a copy for one child process
subprocess.run(["pg_dump", "app"], env=env)
os.environ["GITHUB_TOKEN"] = os.environ.get("GH_TOKEN", "") # re-export
os.environ["SECRET_KEY_FILE"] = "/run/secrets/key" # a path, a constant
os.environ["TOKENIZERS_PARALLELISM"] = str(flag) # not a token
Reads, deletes and pops, constants, comments, strings and test code are not reported.
Remediation
- Hand the secret only to the process that needs it: an
env=copy for that one subprocess, its standard input, or an owner-only (0600) file. - Remove secrets from
os.environonce they are no longer needed, and do not start unrelated children while they are set. - Prefer a secret manager or a file descriptor over environment variables for long-lived credentials.
References
- CWE-515: Covert Storage Channel
- CWE-526: Exposure of Sensitive Information Through Environmental Variables
Suppressing this rule
Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):
# nosec vnx-515
// nosec vnx-515
Or record an ignore rule: vulnetix ignore add --rule vnx-515 --reason "...". See Suppressing findings.