Encrypt your .env with AWS KMS: Secrets that never touch process.env

calendar_today agoschedule3 min read
— Originally published at blog.faizahmed.in

A year ago I'd have told you a .env file was fine.

Then we patched a CVSS 10.0 RCE in Next.js (CVE-2025-66478) and spent the next two days rotating every secret we owned — because we couldn't prove which ones an attacker could have read. They were all sitting in process.env. One env dump away from gone.

That incident is why I built @faizahmed/secret-keystore.

The actual problem isn't committing .env

Everyone knows not to commit secrets. The part that hurts you is what happens the moment your process is compromised. The default Node setup:

require('dotenv').config(); // every secret → process.env, at startup

Attacker gets code execution (a dep RCE, an SSRF, a framework CVE). Their first move:

env

One line. Every DB password, API key, and JWT secret you own, in plaintext, in one place. That's your blast radius — and then you're rotating everything and hoping, because you can't prove what leaked.

The idea: a KMS Key ID is not a secret

The whole design rests on one decision: the only thing a developer ever handles is an AWS KMS Key ID — which isn't sensitive. It's a pointer. The key material never leaves KMS, and access is gated by IAM. No private keys, no passphrases, nothing for anyone to leak.

Your .env stores ciphertext:

DB_PASSWORD=ENC[AQICAHh2nZPq...]
API_KEY=ENC[AQICAHh2nZPq...]

At runtime, values are decrypted on demand into an in-memory store — and never put back into process.env. So the next RCE leaks the handful of keys your code actually touched, not the entire vault.

In practice

npm install @faizahmed/secret-keystore

# encrypt the secrets in your .env (in place)
npx @faizahmed/secret-keystore encrypt --kms-key-id="alias/my-key"

Load them at runtime without ever touching process.env:

const { config } = require('@faizahmed/secret-keystore');

const secrets = await config({ kmsKeyId: 'alias/my-key' });
const dbPassword = secrets.get('DB_PASSWORD');
// decrypted, in memory only

Or, for an app you don't want to modify, inject into the child process and run it:

npx @faizahmed/secret-keystore run \
  --kms-key-id="alias/my-key" -- \
  node server.js

There's also rotate, edit, keys, status, and import — plus optional AWS Nitro Enclave attestation when you need to prove what's running.

Being honest about what it does NOT do

It's not magic, and the README says so. An attacker with full code execution inside your process can still call the keystore or scrape memory. You still patch and rotate!

What it removes is bulk exposure: no single env dump that hands over everything, no plaintext in git, and secret access that's per-key and grep-able.

It's also AWS-only by design, that's the point. The moment you hand a human a private key or passphrase (age, PGP, password-based tools), you recreate the leak risk KMS exists to remove.

If you need multi-cloud, SOPS is the better fit. If you're already on Secrets Manager/SSM, use those - same KMS underneath; this is for teams who want encrypted config files in their existing workflow.

The full write-up

I wrote a 4-part series with the complete threat model, every CLI command, the runtime/config() internals, and a comparison to dotenvx / SOPS / Secrets Manager:

Repo (with runnable Next.js + NestJS examples): github.com/faizahmedfarooqui/secret-keystore

If you ship Node on AWS and have ever had to "rotate everything and hope," this is the pattern I wish I'd had before the incident. Feedback and hard questions welcome.

🔥 Join developers growing publicly
Share your knowledge, build in public, and grow your developer presence with a global community.

More Posts

10 Proven Ways to Cut Your AWS Bill

rogo032 - Jan 16

How to Reduce Your AWS Bill by 50%

rogo032 - Jan 27

AWS Certifications Are a Building Block, Not the Final Destination

Ijay - Jun 16

Implementing Cellular Redundancy: Cross-Cloud Failover with AWS Transit Gateway and Azure ExpressRou

Cláudio Raposo - May 5

Comparison: Universal Import vs. Plaid/Yodlee

Pocket Portfolio - Mar 12
chevron_left
121 Points2 Badges
Bangalore, Indiafaizahmed.in
1Posts
0Comments
I’m a Principal Technical Consultant with 10+ years of experience building scalable backend systems,... Show more

Related Jobs

View all jobs →

Commenters (This Week)

1 comment
1 comment
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!