Stop Leaking Your Secrets! Secure Your Node.js Environment Variables.



This content originally appeared on DEV Community and was authored by Shahrad Elahi

Let’s be real: we all sling secrets in .env files. It’s the default for a reason. You drop your DATABASE_URL and API_KEY in there, load them up, and you’re off to the races.

But have you ever stopped to think about what happens after you load them into process.env?

So, What’s the Big Deal?

When you dump everything into process.env, you’re essentially tossing your secrets into a global free-for-all. Every single package in your node_modules black hole—and all of their dependencies—can take a peek.

Usually, that’s fine. But in an ecosystem this massive, it only takes one sketchy dependency or one compromised package to start hoovering up your sensitive data. That nagging feeling in the back of your head? It’s probably justified.

A “Virtual Environment” for Your Env Vars

This exact worry is why I built process-venv. It’s a simple idea: create a “virtual environment” for your config. Instead of exposing everything, it keeps your variables in a safe, isolated container. Only the variables you explicitly mark as “shared” will ever touch process.env.

Here’s the payoff:

  • Keep Your Secrets Actually Secret: Your API_KEY stays tucked away, safe from prying eyes in your dependency tree.
  • Fail-Fast with Schema Validation: Using tools you already love like Zod, Valibot, or ArkType, you can validate your env vars on startup. No more chasing down undefined errors at 3 AM.
  • No More Runtime Shenanigans: The environment is immutable. Once it’s loaded, it’s locked. No accidental (or malicious) changes during runtime.

Let’s See It in Action

Here’s how easy it is to get started. First, grab the packages:

pnpm install process-venv zod

(We’re using Zod here, but any Standard Schema validator will do.)

Next, set up your environment config. I like to do this in its own file.

// src/env.ts
import { createEnv } from 'process-venv';
import * as z from 'zod';

export const venv = createEnv({
  // Define the shape of your environment variables.
  // Zod is great for this.
  schema: {
    NODE_ENV: z
      .enum(['development', 'production', 'test'])
      .default('development'),
    PORT: z.coerce.number().default(3000),
    DATABASE_URL: z.string().url(),
    API_KEY: z.string(), // This will be private by default.
    SHARED_SECRET: z.string(), // We'll share this one.
  },

  // Now, tell process-venv which variables are safe to expose globally.
  // These are usually things your framework needs.
  shared: ['NODE_ENV', 'PORT', 'SHARED_SECRET'],
});

Now, in the rest of your app, you just import and use it.

// src/index.ts
import { venv } from './env';

// Things like your web framework can still access what they need.
console.log('NODE_ENV from process.env:', process.env.NODE_ENV);

// But you access everything else from the safe venv instance.
console.log('API_KEY from venv:', venv.API_KEY);
console.log('PORT from venv:', venv.PORT);

// And here's the magic: your API key is nowhere to be found globally.
console.log('API_KEY from process.env:', process.env.API_KEY);
// => undefined

That’s it. Your API_KEY is now available to your code via the venv object, but it’s invisible to the rest of process.env. Peace of mind.

Give It a Spin!

I’d be thrilled if you gave process-venv a try in your next project. It’s a small change that can make a real difference in your app’s security posture.

You can find it on npm and the source is on GitHub.

If you dig it, a star on GitHub would be awesome! Let me know what you think in the comments. Cheers!


This content originally appeared on DEV Community and was authored by Shahrad Elahi