.env file leak: how secrets escape on GitHub, Vercel and Netlify
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.
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:
- A committed file. With no
.gitignoreentry,git add .picks up.env. Deleting it later does not help, because it stays in every earlier commit. GitHub secret scanning reads the full Git history on all branches, and so can anyone who clones a public repo. - A public deploy. If your host publishes the project root instead of a build folder,
/.envdownloads like any other file. - A secret in your built JavaScript. Next.js copies the value of every
NEXT_PUBLIC_variable into the browser code at build time, so a secret behind that prefix ships to every visitor. Read why NEXT_PUBLIC_ and VITE_ variables are public and how to hide an API key in a React frontend. - A published source map. Source maps point the browser back to your original files, so any key hardcoded in client code is easy to read.
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.
# .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
# .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)
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.
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.
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.localand 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'
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.
Redeploy and check the live site
Rebuild so the new values are used, then run the checks in the next section. Confirm
/.envis not downloadable and no key shows up in the built JavaScript.Turn on scanning and add .env.example
Enable GitHub push protection and keep your host's scanning on. Commit a
.env.examplewith 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.
- A team owner can turn on Separate Production Secret Values, which forces Production to use a different value from Preview and Development.
- Vercel replaces a Secret with
[REDACTED]in build logs when the value is 32 characters or longer. The docs promise nothing for shorter values, so don't print env vars during your build.
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 acts | What happens | |
|---|---|---|
| GitHub push protection | At git push | Blocks the push |
| GitHub secret scanning | Repo history, issues, PRs | Alerts you, notifies partners |
| Netlify smart detection | Repo code and build output | Fails the build |
| Vercel Secret type | Saved variables and build logs | Write-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.
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
- Netlify docs: Secret scanning
- Vercel docs: Config and Secret environment variables
- GitHub docs: About secret scanning
- GitHub docs: About push protection
- 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 repoLeakcade 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