AI coding tools are fast at wiring up services, and they often do it by pasting a key straight into a file. A Supabase service role key in lib/supabase.ts, an OpenAI key in a .env that was never gitignored, a Stripe secret in a test script. Once that file is committed and pushed, the key is in your repo's history, even if you delete the line later.
This guide walks through how to check, from quickest to most thorough, and what to do if you find something.
Why deleting the file isn't enough
Git stores every version of every file. If you committed .env on Monday and removed it on Tuesday, Monday's commit still contains it, and anyone who clones the repo can run git log -p to read it. On a public repo, automated scrapers watch new pushes for credentials, so assume a key pushed publicly has been seen.
That means the check has to cover all history and all branches, not just your current files.
Step 1: Look at the obvious places
Start with a quick manual pass:
# Is .env (or similar) tracked right now?
git ls-files | grep -iE '(^|/)\.env|secret|credential'
# Was a .env file ever committed, on any branch?
git log --all --oneline -- '*.env' '.env*'
If the second command prints anything, an env file was committed at some point. Open those commits with git show <commit> to see what was in them.
Also check that your .gitignore actually covers env files:
.env
.env.*
!.env.example
Step 2: Scan the full history with gitleaks
gitleaks is a free, open-source scanner that knows the formats of hundreds of key types. Install it (for example brew install gitleaks) and run it from the repo root:
gitleaks git -v
This scans every commit reachable in the repo. In older versions of gitleaks the command is gitleaks detect -v. To scan only the files currently on disk, including untracked ones:
gitleaks dir -v .
Each finding shows the file, the commit, the author and the rule that matched. Expect some false positives, like example keys in docs. Go through each one and decide whether it is a real credential.
Step 3: Check which keys are still live with TruffleHog
TruffleHog can go a step further and try a found credential against the provider's API to tell you whether it still works:
trufflehog git file://. --only-verified
You can also point it at a remote repo:
trufflehog github --repo https://github.com/your-org/your-repo --only-verified
A "verified" result means the key is active right now. Treat those as an emergency. Note that --only-verified hides keys it could not verify, so run it once without the flag too.
Step 4: Turn on GitHub secret scanning and push protection
GitHub has its own scanner. On public repositories, secret scanning alerts are free. Go to your repo's Settings > Code security (the exact label varies by plan) and enable:
- Secret scanning, which alerts you about known key formats in the repo, including history.
- Push protection, which blocks a push that contains a recognized secret before it reaches GitHub.
Some providers also take part in GitHub's partner program: when their key format is found in a public repo, GitHub notifies the provider, who may revoke the key automatically. If you've ever received an email from a provider saying your key was found on GitHub, this is why. Don't rely on it, but don't ignore those emails either.
Step 5: Check the places scanners miss
Scanning git history doesn't cover everything. Also look at:
- Forks. If someone forked your public repo while the key was in it, the fork has a copy you can't delete.
- GitHub Actions logs. A workflow that echoes an env var can print a secret into a public log.
- Issues, PRs and gists. People paste error output that contains keys.
- Your built frontend. Anything in a
NEXT_PUBLIC_orVITE_variable is in your JavaScript bundle for anyone to read, whether or not it's in git. See our guide on exposed keys in NEXT_PUBLIC_ and VITE_ variables.
For the frontend, open your live site, view the page source and the loaded JS files in your browser's dev tools, and search for strings like sk_live, sk-, service_role or eyJ (the start of a JWT).
What counts as a real problem
Not every key is a secret. Some are designed to be public:
| Key | Safe in public code? |
|---|---|
| Supabase anon or publishable key | Yes, if RLS is set up correctly |
Stripe publishable key (pk_) |
Yes |
Firebase web config apiKey |
Yes, access is controlled by security rules |
| Supabase service role or secret key | No |
Stripe secret key (sk_) or restricted key (rk_) |
No |
| OpenAI, Anthropic or other LLM API keys | No |
| Database connection strings with passwords | No |
The "safe" ones only stay safe if the backend rules behind them are correct. A public Supabase anon key plus tables with RLS off is still a data leak. Our Supabase RLS checklist and Firebase security rules guide cover that side.
If you found a leaked key
The order matters:
- Rotate the key first. Create a new key, deploy it, then revoke the old one. Rewriting git history does nothing for a key that someone already copied. Our guide on rotating leaked Supabase and OpenAI keys has the steps for common providers.
- Check for misuse. Look at the provider's usage and billing dashboard and logs for activity you don't recognize.
- Then clean up the repo if you want to. Tools like
git filter-repocan remove a file from history, but you'll need to force-push, and forks and existing clones keep their copies. After rotation this is cleanup, not the fix. - Prevent the next one. Move the secret to your host's environment variables, keep
.envgitignored, and enable push protection.
Make it a habit
A one-time scan tells you about the past. To catch the next leak, run gitleaks in a pre-commit hook or CI job, and keep GitHub push protection on. AI tools will happily paste a key again the next time you ask them to "just make it work".
If you'd like a second pair of eyes, PreflightPR checks a repo for hardcoded secrets alongside other common launch problems in AI-built apps, like missing RLS, unprotected API routes and unverified Stripe webhooks. Run a free scan on a public repo to see what it flags. It's an automated best-effort review, not a guarantee, so keep the scanners above in your toolkit too.