Keep secrets outside the code and the repository, inject them at deploy time, and prefer a secrets manager when you can rotate and audit access. Environment variables are fine as the delivery mechanism, but know where they spread: child processes, crash output and logs.
The three options compared
| Option | Good for | Watch out for |
|---|---|---|
| Env vars set by the platform | Simple deploys, containers | Inherited by child processes, easy to print by accident |
| Env file on disk | Local development | Gets committed or copied, file permissions |
| Secrets manager | Rotation, access logs, many services | Extra moving part, its own credentials to protect |
None of these is secure by itself. Each moves the problem somewhere you can control.
How environment variables spread in Node
Node's child process documentation says the env option defaults to process.env. So when your app spawns a subprocess without setting env, the child gets a copy of everything the parent had. If that child is a tool you do not fully control, a build script or a shelled out command, it sees your database password.
Pass only what the child needs:
const { execFile } = require('node:child_process');
execFile('convert', ['in.png', 'out.jpg'], {
env: { PATH: process.env.PATH }
}, callback);
That one line limits what a compromised helper can read.
Loading from a file
Node can load a file at start with the --env-file flag. The documentation says a real environment variable wins over the same name in the file, and that a missing file raises an error unless you use the optional variant. That makes files a reasonable development default and a poor production one. Details are in the --env-file post.
Decision steps
- Is the secret ever committed? If yes, rotate it now, then fix the process. See your npm token leaked for the checklist mindset.
- Do you need rotation or per service access control? If yes, use a secrets manager and fetch values at startup or on a schedule.
- Is it a small single service? Platform injected environment variables are acceptable. Keep the values out of images and out of repository history.
- Do you spawn subprocesses? Set env explicitly on each call.
- Do you log configuration at startup? Redact before printing, and never log process.env wholesale.
- Scan for leaks before they ship. See pre commit secret scanning.
Mistakes worth naming
Printing config on boot for debugging, then leaving it on. Putting secrets into build arguments, where they can end up in image layers. Sharing one credential across environments, so a staging leak is a production leak. Giving the app a key with far more permissions than it uses.
Treat every secret as short lived. If rotation is painful, that is the thing to fix first, because you will need it on the day a secret escapes.
Safeguard's compliance and evidence automation can help record how secrets handling is controlled, and the free scan at gold.safeguard.sh/scan covers your dependencies.
Quick check
Are environment variables safe for secrets?
They are a common delivery method, not a safe store. Limit who can read them, avoid logging them, and set env explicitly for child processes.
Do I need a secrets manager for one small service?
Not always. If rotation and audit are easy with platform variables, that may be enough. Move when rotation or access control gets painful.
Sources
Last reviewed: 19 October 2026.