Skip to content

2026-10-14 · tool deep dive · 4 min

Your .env Is Not Where Your Secrets End Up: Scanning the Bundle You Actually Ship

In 2019, researchers at North Carolina State University went looking for API keys and private keys in public GitHub repositories. Their NDSS paper, "How Bad Can It Git?", found that secret leakage was pervasive,…

This article was written with AI assistance and reviewed by the author.

In 2019, researchers at North Carolina State University went looking for API keys and private keys in public GitHub repositories. Their NDSS paper, "How Bad Can It Git?", found that secret leakage was pervasive, affecting over 100,000 repositories, and that thousands of new, unique secrets were leaked every day [1].

Most of the advice that followed focused on the repository: add .env to .gitignore, run a pre-commit scanner, never commit credentials. That advice is good. But there is a second place secrets leak that a clean repository does not protect you from: the JavaScript bundle you ship to every visitor.

How a secret walks out of the build

Modern frontend tools deliberately copy some environment variables into client code. Vite's documentation says it plainly: variables prefixed with VITE_ are exposed in client-side source code after bundling, and it warns you to avoid that prefix to prevent leaking variables to the client [2]. Next.js works the same way: to expose a variable to the browser it must be prefixed with NEXT_PUBLIC_, and those values are inlined into the JavaScript bundle during next build [3].

So the failure mode is not exotic. Someone needs a key "just for a quick test", gives it the public prefix (or pastes it straight into a config object), and the build faithfully writes the literal value into dist/assets/index-<hash>.js. The .env file never touches git. The secret still ends up on the public internet.

How a server secret reaches the browser: .env, build, deploy, leak
How a server secret reaches the browser: .env, build, deploy, leak

MITRE classifies this family of mistakes as CWE-798, Use of Hard-coded Credentials [4]. Once a key has shipped, deleting it from the next build is not enough. OWASP's Secrets Management Cheat Sheet notes that restarting an application does not revoke stolen credentials; they remain usable until the backing service expires or revokes them [5]. The only real fix is rotation.

Checking the artifact, not just the source

Source scanners look at what you wrote. The browser sees what the bundler produced. bundle-secret-guard is a small CLI that scans built frontend output (it auto-detects dist, build, out, .next/static, .output/public and .svelte-kit/output/client) and fails when secrets or API keys made it into the files you are about to deploy. It can also cross-check the values in your .env files, so a server-only value that appears literally in the bundle is flagged even if it does not match a known key format.

A demo build (with fake keys)

To be upfront: the project below is made up for this article, and every key in it is fake. The AWS pair is a widely used example key pair (note the EXAMPLE suffix), and the Stripe value is a placeholder shaped like a live key. The .env:

VITE_API_URL=https://api.example.com
STRIPE_SECRET_KEY=sk_live_51Hx9QkFakeDemoKey0000000000000000000000000000

And a "built" bundle at dist/assets/index-3f9a1c.js where someone hard-coded the config:

const cfg={apiUrl:"https://api.example.com",stripe:"sk_live_51Hx9QkFakeDemoKey0000000000000000000000000000",aws:{accessKeyId:"AKIAIOSFODNN7EXAMPLE",secretAccessKey:"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"}};

Running npx bundle-secret-guard dist -e .env with version 1.1.1 gives this real output:

bundle-secret-guard output: 4 errors in the demo bundle
bundle-secret-guard output: 4 errors in the demo bundle

Four errors, each with a file, line and column, and the secret itself masked in the output:

  • a Stripe live secret key, matched by format;
  • the same value flagged again as a server-only env leak, because it matches STRIPE_SECRET_KEY from .env;
  • an AWS access key ID;
  • an AWS secret access key.

Every fix starts the same way: rotate the key, because anything that was deployed must be treated as leaked. Then it points to where the call belongs instead, such as a server or API route, a serverless function, or pre-signed URLs for AWS. The command exits with code 1, so a CI job stops before the deploy step.

Wiring it into CI

Run it after the build and before the deploy:

- run: npm run build
- name: Check bundle for secrets
  run: npx bundle-secret-guard@1 dist -e .env.production --sarif bundle-secrets.sarif

Useful options:

  • --baseline <file> and --update-baseline to accept known findings (stored as hashes only) and fail only on new ones.
  • --entropy-scan for extra high-entropy string detection.
  • --gitleaks <file> to reuse existing gitleaks TOML rules.
  • --verify is opt-in and checks whether a key is live using the provider's own API. It is off by default.

The takeaway

The 2019 study showed how often secrets slip into source code [1]. Build tools add a second path, because public env prefixes are designed to be copied into client code [2][3]. Scan the artifact you actually deploy, and if a key ever ships, rotate it rather than just removing it [5].

  • Site: https://mrlouiswright.github.io/bundle-secret-guard/
  • npm: https://www.npmjs.com/package/bundle-secret-guard
  • GitHub: https://github.com/MrLouisWright/bundle-secret-guard

References

  1. Meli, M., McNiece, M. R. and Reaves, B. (North Carolina State University). "How Bad Can It Git? Characterizing Secret Leakage in Public GitHub Repositories". NDSS Symposium, 2019. https://www.ndss-symposium.org/ndss-paper/how-bad-can-it-git-characterizing-secret-leakage-in-public-github-repositories/
  2. Vite. "Env Variables and Modes". Accessed Oct 2026. https://vite.dev/guide/env-and-mode
  3. Vercel. "Next.js Docs: Environment Variables". Accessed Oct 2026. https://nextjs.org/docs/app/guides/environment-variables
  4. MITRE. "CWE-798: Use of Hard-coded Credentials". Accessed Oct 2026. https://cwe.mitre.org/data/definitions/798.html
  5. OWASP. "Secrets Management Cheat Sheet". Accessed Oct 2026. https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html
Newsletter

Research notes, monthly.

One email a month on malware analysis and security tooling.

About Research notes →

One email a month. No tracking pixels. Unsubscribe any time.