LEAKCADE

.env file leak: how secrets escape on GitHub, Vercel and Netlify

Updated 8 min read

A .env file leak happens when your secrets file ends up in Git, on your live site or inside your build output. Rotate every key in it first, which means creating a new key and revoking the old one. Then tell Git to stop tracking the file and add .env* to .gitignore. Turn on GitHub push protection and your host's secret scanning so the next slip gets blocked.

QUICK CHECKLIST

  • Rotate every key that was ever in a committed .env
  • Add .env* to .gitignore and allow only .env.example
  • Never put secrets behind NEXT_PUBLIC_ or VITE_
  • Store host secrets as Vercel Secret or Netlify secret vars
  • Use test keys in Preview and live keys only in Production
  • Turn on GitHub push protection and read Netlify deploy logs

What a .env file leak looks like

A .env file holds the keys your app uses to talk to other services: database passwords, payment keys, AI provider keys. It's meant to live on your machine and in your host's settings. A .env file leak is any route that puts a copy where strangers can read it.

It usually leaks through a public repo, a live site that serves /.env, a secret baked into your build's JavaScript, or a published source map. Once a key is public, assume someone has already copied it. Automated scanners watch public code for key patterns, so waiting to see if anyone notices is a bad bet.

How a committed .env becomes a billA four step attack path. A .env file is committed, pushed to a public place, found by automated scanners, and the stolen key is used on your account.COMMITgit add . picksup .envPUSHPublic repo orlive /.envSCRAPEBots match thekey patternABUSEYour key, theirbillCOMMITgit add . picks up .envPUSHPublic repo or live /.envSCRAPEBots match the key patternABUSEYour key, their bill
Every step after the push happens without you seeing it.

The Next.js docs say the default create-next-app template adds all .env files to .gitignore. Projects scaffolded another way, or a .gitignore that got overwritten, don't have that protection.

How a .env file leak happens in AI-built apps

A .env file leaks in four common ways:

For the wider picture of exposed credentials, see leaked API keys.

Keep .env out of Git and use a .env.example

The fix has two halves. Ignore every .env* file except an example file, and commit only the example. The example lists variable names with empty values, so a new teammate (or your AI tool) knows what to set without ever seeing a real secret.

BEFOREBefore: real values in a tracked file
# .gitignore
node_modules
.next

# .env  (tracked, pushed to GitHub)
STRIPE_SECRET_KEY=sk_test_xxx
SUPABASE_SERVICE_ROLE_KEY=YOUR_SERVICE_ROLE_KEY
NEXT_PUBLIC_OPENAI_KEY=YOUR_KEY
AFTERAfter: ignore env files, commit names only
# .gitignore
node_modules
.next
.env*
!.env.example

# .env.example  (committed, names only)
STRIPE_SECRET_KEY=
SUPABASE_SERVICE_ROLE_KEY=
OPENAI_API_KEY=

# .env.local  (ignored, real values, never pushed)
The NEXT_PUBLIC_ prefix on a secret ships it to every visitor. Drop the prefix and call the provider from a server route.

How to fix a .env file leak, step by step

People tidy the repo first and leave the key alive. Rotate first, then clean up.

  1. Rotate every key in the file

    Treat each value as public: database password, payment secret, AI provider key, service role key. Create new ones, put them in your host's settings and redeploy, then revoke the old ones. If you can't redeploy quickly, revoke anyway. Downtime is cheaper than abuse. The rotation guide has the order for common providers.

  2. Stop tracking the file

    Tell Git to stop tracking it (the file stays on your disk) and ignore it from now on. Repeat for .env.local and any other tracked env file.

    git rm --cached .env
    printf '\n.env*\n!.env.example\n' >> .gitignore
    git check-ignore -v .env   # should print the .env* rule
    git add .gitignore && git commit -m 'Stop tracking .env'
  3. Put real values in your host's settings

    On Vercel, choose the Secret type for passwords, API keys and tokens. On Netlify, mark the variable as a secret. Use test keys for Preview and Development and the live key for Production only.

  4. Redeploy and check the live site

    Rebuild so the new values are used, then run the checks in the next section. Confirm /.env is not downloadable and no key shows up in the built JavaScript.

  5. Turn on scanning and add .env.example

    Enable GitHub push protection and keep your host's scanning on. Commit a .env.example with names only.

Rewriting Git history is optional. GitHub's docs call it time-intensive and often unnecessary once the credential is revoked. Forks and old clones may keep copies anyway, which is one more reason rotation comes first.

What Netlify, Vercel and GitHub do for you

Netlify secrets scanning looks for the values of variables you mark as secret. Smart detection, available on Personal, Pro and Enterprise plans, also scans your repository code and build output with no setup. When it finds a likely secret, the build fails and the deploy log shows where.

If your build failed, open Site dashboard > Deploys, read the log, remove the value from the location it names and redeploy. For a smart detection false positive, add the string to a variable named SECRETS_SCAN_SMART_DETECTION_OMIT_VALUES, comma separated. Don't switch smart detection off to get past a real hit.

Vercel sensitive environment variables are now called the Secret type. When you add a variable you pick Config or Secret. A Secret is write-only after saving, so nobody can read it back in the dashboard.

GitHub has two tools. Secret scanning runs automatically, is free on public repositories and scans the whole Git history. For partner services it also notifies the provider so they can act, for example by revoking the key.

Push protection blocks the push before the secret lands. It's on by default for you on GitHub.com when you push to public repos. Repository-level push protection needs GitHub Secret Protection and is off by default. You can bypass a block by giving a reason, but don't do that for a real secret. Rotate the key instead.

Where it actsWhat happens
GitHub push protectionAt git pushBlocks the push
GitHub secret scanningRepo history, issues, PRsAlerts you, notifies partners
Netlify smart detectionRepo code and build outputFails the build
Vercel Secret typeSaved variables and build logsWrite-only, 32+ char values redacted in logs

Check each docs page for plan requirements.

All of these match known patterns, so treat them as a safety net. A .gitignore entry is still your first defense.

Check for a .env file leak yourself

Run these against your own repo and your own deployed app only.

First, check what Git tracks and whether an env file was ever committed, in any folder:

git ls-files | grep -E '(^|/)\.env'
git log --all --oneline --name-only -- ':(glob)**/.env*'

Good: only .env.example files show up. Bad: any other env file name means it was tracked or committed at some point, so rotate its keys.

Next, ask your live site for the file. Read the body, because single-page apps often return 200 with your home page for any path.

curl -s https://YOUR-APP.example.com/.env | head -5

Bad: lines that look like KEY=value. Good: a 404 or your normal HTML.

Then build and search the browser files. Use dist instead of .next/static for Vite. Swap in the first characters of a real secret for YOUR_KEY_PREFIX.

npm run build
grep -rEn 'sk_live_|sk_test_|BEGIN [A-Z ]*PRIVATE KEY' .next/static
grep -rF 'YOUR_KEY_PREFIX' .next/static
find .next/static -name '*.map'

Bad: any match, or .map files you didn't mean to publish. In Next.js the source map setting is productionBrowserSourceMaps. No output is a good sign, but this build used your local env values, so also search the loaded JavaScript of your deployed site in DevTools.

Finally, open your repo's Security tab for secret scanning alerts. Partner secrets go to the provider and don't appear there, so an empty list doesn't prove you're clean.

A repo check and a live URL check find different things, see repo scan vs URL scan. For the rest of your launch list, use the vibe coding security checklist.

Fix prompt you can paste

Give this to Cursor or Claude Code in your repo. It lists what to change and leaves the key rotation to you.

FIX PROMPT
Audit this repo for .env file leaks. Do these in order:

1. Run `git ls-files` and list any tracked .env, .env.local, .env.production or similar files, in any folder. Check `git log --all --name-only -- ':(glob)**/.env*'` and tell me if any env file other than .env.example was ever committed.
2. Check .gitignore. It must contain `.env*` and `!.env.example`. Add them if missing.
3. Search all source files for hardcoded secrets: sk_live_, sk_test_, service_role, any '-----BEGIN ... PRIVATE KEY-----' block, long random tokens assigned to variables named key, secret, token or password.
4. Find any NEXT_PUBLIC_ or VITE_ variable that holds a secret. Move the call to a server route handler or server action and rename the variable without the prefix.
5. Check next.config and vite.config for productionBrowserSourceMaps or build.sourcemap set to true. Check netlify.toml, vercel.json and the host's publish directory for the project root.
6. Remove any console.log of process.env or of key values.
7. Create .env.example with variable names only, no values.

For every finding, give me the file path and line number. Explain each change in one sentence. Do not print secret values. At the end, list by name only which keys I must rotate, and do not rotate or delete anything yourself.

After you've fixed it, you can run the repo through Leakcade as a second check.

Further reading

  1. Netlify docs: Secret scanning
  2. Vercel docs: Config and Secret environment variables
  3. GitHub docs: About secret scanning
  4. GitHub docs: About push protection
  5. Next.js docs: Environment variables

FAQ

.env file committed to GitHub: what do I do?

Rotate every key in the file right away. Then run git rm --cached .env and add .env* to .gitignore. GitHub's docs say removing secrets from Git history is time-intensive and often unnecessary once the credential is revoked.

Why did Netlify secrets scanning fail my build?

Either standard scanning found the value of a variable you marked as secret, or smart detection (Personal, Pro and Enterprise plans) found a likely secret in your code or build output. The deploy log says which and where. Remove the value and redeploy. For a false positive, add the string to SECRETS_SCAN_SMART_DETECTION_OMIT_VALUES.

What are Vercel sensitive environment variables?

Vercel now calls them the Secret type. When you add a variable you choose Config or Secret. A Secret is write-only after you save it, so nobody can read it back in the dashboard, and changing it means entering a new value.

Is it safe to commit a .env.example file?

Yes, if it holds variable names or obvious placeholders and no real values. Add !.env.example after .env* in .gitignore so it stays tracked.

Will GitHub push protection stop me from committing my .env?

Only if it recognizes a secret in the push, and only where it applies. The default user-level protection covers public repos. Private repos need repository-level push protection, which requires GitHub Secret Protection and is off by default. It matches known secret patterns, so a custom password can still get through. Keep .env* in .gitignore as your first defense.

Want a second pair of eyes?

We review your whole repo and write a fix prompt for every finding. $19, report within an hour.

Check my repo

Leakcade is an automated check. It can miss vulnerabilities and is not a substitute for a professional security audit or pentest. Provided as is. You stay responsible for your code. Where the law allows, our liability is limited to what you paid ($19). Terms

Patch Notes by Leakcade

A short weekly email: what changed in AI coding tools, one real security mistake, one prompt you can reuse.