Updated June 2026
Exposed API Keys in AI-Built Apps
AI coding tools leak secrets by slapping NEXT_PUBLIC_ or VITE_ on your keys, which bakes them straight into the JavaScript bundle every visitor downloads. A 2026 scan of roughly 380,000 AI-built apps found about 5,000 leaking sensitive data. Grep your repo for these prefixes, move every secret server-side, and rotate any key that ever touched git history.
Lovable, Bolt, Cursor, Replit, v0: they turn an idea into a running app overnight, and they are genuinely good at it. The trouble is what they do with your secrets. To make a Stripe call or an OpenAI request “just work” from the browser, they take the path of least resistance: a client-side environment variable. That one move can hand your billing key, your database credentials, or your third-party tokens to anyone who opens DevTools. Here is exactly how the leak happens, how to find it in your own app in about ten minutes, and how to shut it for good.
How AI tools leak your keys (the NEXT_PUBLIC trap)
Every modern framework splits environment variables into two worlds: server-only and client-exposed. A prefix decides which one you get. In Next.js, anything starting with NEXT_PUBLIC_ gets inlined into the JavaScript that ships to the browser. In Vite, the prefix is VITE_. In Create React App, it was REACT_APP_. Drop the prefix and the variable stays on the server, where the client never sees it.
Here is the trap. An AI tool writes a component that calls Stripe or OpenAI straight from the frontend, the build complains the variable is undefined in the browser, and the quickest way to kill that error is to add the public prefix. So the AI adds it. The app starts working, the demo looks great, and your secret key is now sitting in plain text inside main.[hash].js, free to download for every visitor who shows up.
- Server-side variables (no public prefix) only ever live on the machine running your code. A user never sees them. This is where every API key, database URL, and token belongs.
- Client-side variables (
NEXT_PUBLIC_,VITE_,REACT_APP_) get compiled into the bundle anyone can download. Fine for a public publishable key or a feature flag. Catastrophic for anything secret. - The mental model that fixes it: if leaking the value would cost you money or data, it never carries a public prefix and it never gets read in browser code.
The 380,000-app wake-up call
This is not theoretical. In a 2026 scan reported by Axios, the security firm RedAccess found roughly 380,000 publicly accessible assets built with AI “vibe coding” tools, and about 5,000 of them were leaking sensitive corporate or personal data. Exposed credentials and private information, sitting in the open, reachable by anyone who knew where to look. The same scan got picked up and corroborated across the security press.
The pattern is the real story, not the headline number. None of these were exotic zero-days. They were ordinary apps shipped fast, with secrets the builder never knew were public, because the tool made the insecure path feel like the working path. If you built something with an AI tool and wired in a real API key, you are in the population that scan was counting.
Find your exposed secrets in 10 minutes
You do not need a security team for this. Run these three passes in order, fastest to most thorough.
- Grep the source for public prefixes. From your project root, search for
NEXT_PUBLIC_,VITE_, andREACT_APP_. For every hit, ask one question: is this value safe for the public to see? A publishable key is fine. A secret key, database URL, service-role token, or any private API key is a leak. One-liner:grep -rnE "NEXT_PUBLIC_|VITE_|REACT_APP_" src/. - Inspect the built bundle. The source can look clean while the build still ships a secret. Run your production build, then search the output for telltale key shapes:
grep -rE "sk_live|sk_test|AKIA|AIza|xoxb-" dist/ build/ .next/. Or load the deployed app, open DevTools, and search the loaded JavaScript forsk_or your provider’s prefix. If a secret turns up here, every visitor already has it. - Scan git history with a secrets scanner. Deleting a key from the current code does nothing if it is still sitting in an old commit. Run TruffleHog or gitleaks across the whole history:
trufflehog git file://.orgitleaks detect --source .. These tools verify many keys against live APIs, so they tell you not just what leaked but what is still active.
Rotate and remediate
Find an exposed secret and deleting it from the code is the easy part, and the least important part. The key is already out. Anyone who downloaded your bundle or cloned your repo still has it. The only thing that actually closes the hole is rotation: generate a new key in the provider’s dashboard, swap it in server-side, and revoke the old one so the leaked value stops working.
Git history is where people get caught twice. Even after you remove a secret and commit the fix, the original value is still sitting in every prior commit, in every clone, in every fork. That is why “I already deleted it” means nothing. Any key that ever touched a commit is compromised: rotate it, revoke it. If you need the value gone from history entirely, say before you open-source, rewrite history with git filter-repo or the BFG. But rotation is the thing that protects you regardless.
| Pattern | Risk | Fix |
|---|---|---|
NEXT_PUBLIC_ / VITE_ prefixed secret | Inlined into the browser bundle and readable by every visitor | Drop the public prefix, read the value only in server code, rotate the key |
API key in a committed .env file | Lives in git history forever; reachable in every clone and fork | Remove from tracking, add .env to .gitignore, rotate the key, scan history |
Secret used in a client-side fetch call | Key travels to the browser to build the request; visible in source and network tab | Move the call behind a server route or API endpoint that holds the key |
| Hardcoded token in source code | Committed in plain text; leaks the moment the repo is shared or scanned | Replace with a server-side environment variable, rotate the token |
Server-side patterns that fix it for good
The durable fix is architectural. The browser should never hold a secret, so route every privileged call through your own server. The client asks your backend, your backend holds the key and talks to the third party, and the secret stays on the machine you control.
- Use a server route as a proxy. In Next.js, put the call in a Route Handler or Server Action. In Vite or a plain SPA, stand up a small backend endpoint. The frontend calls
/api/checkout; the server readsSTRIPE_SECRET_KEY(no public prefix) and makes the real request. - Keep the public/private split honest. Publishable and anon keys can live in client code. Secret keys, service-role keys, and database URLs never can. When in doubt, treat it as private.
- Add the guardrails once. Put
.envin.gitignorefrom day one, store production secrets in your host’s encrypted environment settings (Vercel, Netlify, your platform of choice), and run a secrets scanner in CI so a leaked key fails the build instead of shipping.
This is the same class of problem we cover in our guide on AI-generated code security risks, and it is one of the first things we check in our full audit of AI-generated code. Built on Replit specifically? See is Replit safe for the platform-specific gotchas.
Want us to run this audit for you?
We do a free 15-minute build audit: you show us your AI-built app, we tell you the specific security and production gaps and what it takes to fix them. No obligation.
FAQ
Why does my AI-built app leak API keys?
AI coding tools make a secret "work" in the browser by prefixing it with NEXT_PUBLIC_ or VITE_, which inlines the value straight into the JavaScript bundle every visitor downloads. Drop the public prefix, read the key only in server code, and rotate any key that was ever exposed.
How do I check if my API keys are exposed?
Run three passes. Grep your source for NEXT_PUBLIC_, VITE_, and REACT_APP_ prefixes. Build the app and search the output bundle for key shapes like sk_live or AIza. Then scan your full git history with TruffleHog or gitleaks. If a secret shows up in the bundle or history, treat it as public.
Do I need to rotate a key after I delete it from my code?
Yes. Deleting a key does not un-publish it. Anyone who downloaded your bundle or cloned your repo still has the value, and git history keeps it in every prior commit. The only reliable fix is to generate a new key, swap it in server-side, and revoke the old one.
Where should API keys live in a web app?
Secret keys belong only on the server, in environment variables without a public prefix, ideally in your host's encrypted settings. The browser calls your own server endpoint, and the server makes the privileged request. Only publishable or anon keys are safe to ship to the client.