Almost nobody loses a key to an attacker. They publish it themselves, in a commit, a screenshot, or a bundle of JavaScript sent to every visitor. The people who find it are not watching you personally. They run scanners across every public repository on GitHub, and a key posted in public is typically found and used within minutes.
The idea
A secret is any string that grants access on its own: an Application programming interface keyA long random string that proves to another service that requests are coming from your account. Anyone holding it can act as you. , a database password, a A string that stands in for a login. It grants whatever the account it was issued for can do, until it expires or is revoked., a webhook signing secret. Because it works for whoever holds it, the only meaningful question after an exposure is who has held it, and the only reliable fix is to make the old value stop working.
where secrets end up in public
your machine published surface who reads it
------------ ----------------- ------------
.env ----(committed)---> git history ------> automated scanners
chat ----(pasted)------> public issue -------> search engines
code ----(NEXT_PUBLIC_)--> browser bundle ----> anyone who opens devtools
screen --(recorded)-----> demo video --------> anyone who pauses the frameHow it works
- Committed to git. A
.envfile gets added because.gitignorewas missing or wrong. Deleting the file in a later commit does not help: git keeps every earlier version, so the key stays readable in the history and in every clone and fork that already pulled it. GitHub's own guidance on removing sensitive data says to treat the credential as compromised and rotate it, because rewriting history cannot reach copies other people already have. - Pasted into a public place. A stack trace with the connection string in it, dropped into a GitHub issue or a public Discord while asking for help.
- Shipped to the browser. Frontend code runs on the visitor's machine, so anything compiled into
it is public. In Next.js, any environment variable named with the
NEXT_PUBLIC_prefix is inlined into the JavaScript bundle by design. A secret given that prefix is not hidden by minification. - Filmed. A terminal, a dashboard, or an editor tab visible in a demo video or a conference screenshare. Frames can be paused.
What to do
- Rotate first. Issue a new key in the provider's dashboard, deploy it, then revoke the old one. Do this before you work out how it leaked. Investigation takes hours and rotation takes minutes, and until the old value is dead the exposure is still live.
- Keep secrets in
.env.localand confirm it is ignored. Rungit check-ignore -v .env.local. If it prints nothing, the file is not ignored and your nextgit add .will commit it. - Check what actually reaches the browser. Search your source for
NEXT_PUBLIC_and confirm every match is something you would print on a billboard. The publishable keys from Stripe and Supabase belong there. Service-role and secret keys never do. - Turn on push protection in the repository's settings on GitHub, so a recognised key format is blocked at push time rather than found later.
Where it breaks
Rotation has a cost you should know about before the bad night: swapping a database password or a webhook secret takes production down until every place that uses it has the new value, so write down where each secret lives now rather than discovering it under pressure. Scanners only catch formats they recognise, so a self-issued token or a plain password can sit in a repository for years without a single alert. And rotation limits future damage without undoing past damage. If a key with read access to your database was public for a week, the copy someone took still exists, which makes it a data exposure question rather than only a key question.