API Keys & Secrets: How Vibe-Coded Apps Leak Them (and How to Stop)
Here is a number that should make every vibe coder nervous: in 2024 alone, GitGuardian found 23.8 million secrets leaked in public GitHub repositories, a 25% jump over the year before, and leaks of AI-service credentials specifically grew 81% year over year. If you built your app fast with an AI tool, there is a real chance one of your keys is already sitting somewhere it should not be.
Leaked API keys are not a theoretical risk. Hardcoded credentials cause more breaches than software exploits do, and a single leaked key is often the shortest path from "zero access" to "full control of your cloud account." Bots scan every new public commit within seconds, so a key pushed to GitHub can be abused before you have finished your coffee.
This guide explains exactly how vibe-coded apps leak secrets, and the handful of habits that stop it.
Why AI-built apps leak keys more often
AI coding tools optimize for one thing: making the app work. When the fastest way to call an API is to paste the key into a React component, that is often what gets generated, because the page renders and the demo works. The danger stays invisible until someone goes looking. The rule of thumb: any key that must stay secret should never appear in client-side code, because browser code, network requests, source maps, and build artifacts can all be inspected.
There are two classic ways it goes wrong.
Leak 1: Secret keys in front-end code
This is the big one. A secret key, a Stripe sk_live_..., an OpenAI key, a Supabase service_role key, a raw database URL, gets used directly in the browser. Maybe in a React component, maybe in a Next.js file behind the NEXT_PUBLIC_ prefix. Either way, it ships to every visitor.
Here is the trap that catches everyone: in a front-end app, your .env values are compiled into the final JavaScript bundle. Anyone can open dev tools, read the network tab, or download the source maps and pull the key out in plain text. "It is in an environment variable" does not make a front-end key private.
The rule: if a key grants access you would not hand to a random stranger, it cannot live in the browser. Full stop.
Leak 2: Secrets committed to git
The second classic: the .env file, or a config file with credentials, gets committed and pushed. Even if you delete it later, it lives forever in your git history, and automated scanners find it fast. Public commits in 2025 contained tens of millions of new hardcoded credentials, so you are not hiding in the noise, you are in the crosshairs.
Know your two kinds of keys
Not every key is a secret. Most APIs issue two types:
- Publishable / public keys are designed for the browser: Stripe's
pk_live_..., a Supabaseanonkey, a Google Maps browser key. These are safe in client code, as long as the backing service is locked down (for Supabase, that means Row-Level Security). - Secret keys are admin-grade: Stripe
sk_..., Supabaseservice_role, your database password, your OpenAI key. These must never touch the client.
The single most useful skill here is telling these apart. When in doubt, treat the key as secret.
Fix 1: Get secrets off the client with a backend
If your front end needs to call an API that requires a secret key, do not give the browser the key, give it a backend endpoint instead. This is the Backend-for-Frontend (BFF) pattern: your front end calls your own server (a Next.js route handler, an edge function, a small API), and your server, holding the secret safely, calls the real API and returns only the result.
The fix: the browser talks to your backend; only your backend knows the secret. The key never leaves the server, so it can never be extracted from the bundle.
Fix 2: Keep secrets out of your repository
- Put every secret in an environment variable, and add
.env(and.env.local) to your.gitignorebefore your first commit. - On Vercel, Netlify, or your host, set secrets through the platform's encrypted environment-variable settings, never in code.
- Turn on GitHub secret scanning and push protection, which is free for public repositories and blocks many secrets before they are ever merged.
Fix 3: Rotate and monitor
- If a key has ever been exposed, rotate it now. Revoke the old one in the provider's dashboard and issue a new one. Do not just delete the file, the key is already out there.
- Clean leaked secrets out of git history with a tool like
git filter-repoor BFG, so the old key is not still sitting in an old commit. - Rotate important keys on a schedule (every 30, 60, or 90 days), and delete keys you no longer use.
- Remember secrets sprawl: keys end up in Slack messages, support tickets, and screenshots too, not just code.
The 60-second secrets audit
- No secret key (
sk_,service_role, database URLs, AI keys) appears anywhere in client-side code. - Nothing sensitive sits behind the
NEXT_PUBLIC_prefix. .envand.env.localare in.gitignore, and were never committed.- Production secrets live in your host's encrypted environment settings.
- GitHub secret scanning and push protection are on.
- Any key that ever leaked has been rotated, and the git history cleaned.
When the checklist isn't enough
Finding a leaked key is the easy part. Knowing whether one is already being abused, or whether a "public" key is dangerous because the backing service was never locked down, takes a real look. That is part of what a pentest checks: not just where secrets live, but what an attacker can actually do with what you have exposed.
If you want a human to find what your AI left exposed, a real pentest starts at $499. See a sample report first.
Frequently asked questions
- Are environment variables safe for API keys?
- It depends where they run. In a backend (a server, a serverless function), environment variables are a fine place for secrets. In a front-end app, they are not safe: build tools inline those values into the JavaScript bundle, so anyone can read them in the browser. In Next.js specifically, anything prefixed with NEXT_PUBLIC_ is shipped to the client.
- Is it safe to put a Stripe or Supabase key in my frontend?
- Only the publishable ones. Stripe's pk_ key and Supabase's anon key are designed for the browser and are safe there - as long as the backing service is locked down (for Supabase, that means Row-Level Security). Secret keys, like Stripe's sk_ key or Supabase's service_role key, must never appear in client code.
- What do I do if I accidentally committed an API key?
- Rotate it immediately - revoke the exposed key in the provider's dashboard and issue a new one. Deleting the file is not enough, because the key still lives in your git history and automated bots may already have it. After rotating, clean the secret from history with a tool like git filter-repo or BFG.
- How can my frontend call an API that needs a secret key?
- Use a backend as a proxy (the Backend-for-Frontend pattern). Your front end calls your own server endpoint; your server, which holds the secret, calls the real API and returns only the result. The key never leaves the server, so it can never be extracted from the browser bundle.