The Environment Variable Attack Surface: /proc to Containers
An interface designed to be readable
A process’s environment block was never designed as a confidentiality boundary. It’s designed to be inspectable — by the shell that spawned it, by debugging tools, by anything else running as the same user. That design goal predates modern secret management by decades, and it shows up as a documented attack surface at every layer: the OS, the container image, and the CI runner.
Layer 1: the operating system
On Linux, a process’s initial environment is readable directly from the filesystem. The
proc_pid_environ(5) man page
documents /proc/[pid]/environ and states that access is “governed by a ptrace access mode
PTRACE_MODE_READ_FSCREDS check” — the same permission tier used for attaching a debugger to
another process. In practice: anything running as the same user, or root, can read another
process’s full environment on demand. A compromised dependency, a malicious postinstall
script, or any sibling process on a shared host doesn’t need to find your .env file — it can
read the live environment of any process it has permission to touch.
Layer 2: the container image
Containers add a second, more permanent version of the same problem. NIST SP 800-190,
Application Container Security Guide,
names “embedded clear text secrets” as a specific container risk category (§3.1.4) and gives
the countermeasure directly (§4.1.4): “secrets should be stored outside of images and provided
dynamically at runtime as needed.” Docker’s own documentation on build
secrets says it even more bluntly: “build
arguments and environment variables are inappropriate for passing secrets to your build,
because they persist in the final image.” An ENV line or a --build-arg isn’t discarded after
the build — it’s baked into a layer, visible to docker history or docker inspect for anyone
who can pull the image, permanently, unless the layer is rebuilt from scratch. Docker’s
recommended alternative is --mount=type=secret, which never persists the value into a layer at
all.
Layer 3: the shared cloud runtime
Multi-tenant container platforms add a further wrinkle. Peer-reviewed research on information leakage in container clouds (Gao, Steenkamer, Gu, Kayaalp, Pendarakis, and Wang, IEEE Transactions on Dependable and Secure Computing, 2018) documents that co-located containers on shared infrastructure can leak information across supposed isolation boundaries — a reminder that “my secret only lives in this container’s environment” assumes an isolation guarantee that has been the subject of ongoing academic scrutiny, not a settled fact.
The same category of failure shows up in CI runners too — documented in detail in this site’s CI/CD secret leakage research — where a March 2025 supply chain compromise (CVE-2025-30066) dumped a GitHub Actions runner’s process memory and printed encoded secrets into public build logs. Different layer, same root cause: a process environment that something other than the intended program managed to read.
What actually reduces the exposure window
None of this means abandoning environment variables as the delivery mechanism a running process reads from — that part is fine, and rewriting every app to some other injection mechanism isn’t realistic. What changes is what’s durable. envseal keeps the plaintext secret’s lifetime as short as the container process is exposed for: it decrypts each value individually at runtime and injects it into that one process’s environment, while everything stored on disk, in the image, or in git stays ciphertext. An attacker who reads a container’s image layers or a committed vault file gets nothing usable without the matching private key — the exposure window shrinks to “while this specific process is running,” instead of “for as long as this image or repository exists.”
And because none of this stops a decrypted value from being pasted into a Jira ticket while someone debugs a failed deployment, Secret Sentinel covers the adjacent surface these controls don’t reach: the conversation about the secret, not just its storage.
Frequently asked questions
Can another process on the same machine read my app's environment variables?
Yes, if it runs as the same user or has ptrace-equivalent access. Linux's proc(5) documentation confirms /proc/[pid]/environ exposes a process's initial environment, gated only by a PTRACE_MODE_READ_FSCREDS check — the same permission level as attaching a debugger, not a secret-specific protection.
Does putting a secret in a Dockerfile ENV or build ARG keep it out of the final image?
No. Docker's own documentation states build arguments and environment variables "are inappropriate for passing secrets to your build, because they persist in the final image" — visible to anyone who can pull or inspect the image, in a separate image layer, indefinitely.
Is this an argument against using environment variables to configure applications at runtime?
No. Runtime injection into a live process is a normal, low-risk pattern for the process's own duration. The risk is in the surrounding steps — baking a value into an image layer, leaving it readable by sibling processes, or letting it sit in a plaintext file — not in the process environment of the one program that's actually supposed to use it.