Your app works. It looks fine. It demoed great. Somewhere in the JavaScript your browser just downloaded, though, there may be a key that was never supposed to leave your server — and anyone who opens DevTools can read it.
If you built with Lovable, Base44, Replit, Bolt, v0, or had Cursor/Claude Code scaffold the integration, this is worth an actual check, not an assumption. It’s not a hypothetical: security researchers scanning thousands of publicly deployed vibe-coded apps keep finding exactly this.
Why this keeps happening
When an AI agent wires up a third-party service — an LLM API, a payment provider, your database — its shortest path to “it works” is usually to read a key from .env and call the API straight from the frontend. Nothing in that loop stops to ask whether the key it just grabbed is meant to be public. The agent doesn’t distinguish between a key that’s designed to ship to the browser and one that never should — it just sees a working environment variable and uses it wherever the code needs it.
That distinction actually exists, though, and it matters:
- Public/anon keys (a Supabase or Firebase “anon” key, for example) are designed to be visible client-side — they’re safe only because a separate layer, Row Level Security or security rules, is supposed to enforce who can read or write what. Safe assumes that layer is actually configured. An agent that generates the anon key wiring but not a real RLS policy leaves you with a public key and no lock behind it.
- Secret keys — an OpenAI/Anthropic API key, a Stripe secret key, a Supabase service role key — are never supposed to reach the browser at all, public or not. If one of these is sitting in your shipped JavaScript, that’s not a misconfiguration to tune, it’s a straight-up leak.
The scale here is measured, not anecdotal. Wiz Research found security risks in 20% of vibe-coded apps they examined. A scan of 5,600 publicly deployed vibe-coded apps by Escape.tech turned up over 2,000 vulnerabilities and 400+ exposed secrets — including medical and financial data sitting behind nothing. GitGuardian’s State of Secrets Sprawl 2026 report counted 28.65 million new hardcoded secrets committed to public GitHub in 2025 alone (a 34% jump year over year), and found AI-assisted commits leak secrets at more than twice the rate of human-only ones. Two named incidents from earlier this year: a single Lovable app with 16 vulnerabilities (6 critical) that leaked 18,000+ people’s data, and Moltbook, a vibe-coded social app, exposing 1.5 million auth tokens and 35,000 email addresses through a misconfigured database.
Check your own app right now
1. Look at what actually shipped, not what the editor shows you. Open your live app in an incognito window, open DevTools, and check two places: the Network tab (watch what your app sends to third-party APIs directly from the browser) and your bundled JS itself — view-source or the Sources tab, then search (Ctrl/Cmd+F in the Sources panel) for suspicious strings: sk-, service_role, SECRET, or any variable that looks like a key but isn’t the one anon/public key your backend’s docs say is safe to expose.
2. If you find a secret-looking key client-side, don’t assume it’s the safe kind. Check which key it actually is against your provider’s docs — Supabase and Firebase are explicit about which key is the public one; anything else (a service role key, a raw third-party API key) found in shipped code is a real exposure, not a false alarm.
3. If it’s the anon/public key, verify the lock is actually there. Log into your database dashboard and check Row Level Security (Supabase) or security rules (Firebase) are enabled and actually restrict access — not just present as a stub. An anon key with RLS disabled is functionally a master key with public visibility.
The fix, not just the diagnosis
Move anything requiring a real secret behind your own server. Add a small serverless/edge function (Cloudflare Workers, Vercel Functions, Supabase Edge Functions — whatever your stack already has) that holds the secret key and makes the third-party call itself; the frontend calls your endpoint, never the provider directly. This is a real code change, not a config toggle — budget for it as one.
Rotate anything already exposed, don’t just relocate it. A key found in a deployed bundle has to be treated as already compromised — crawlers and researchers scrape public deployments continuously, and your git history likely has it too. Regenerate the key at the provider (OpenAI, Stripe, Supabase service role, etc.) and update your server-side config with the new one. Removing the exposed reference from your code doesn’t undo the exposure; the old key is still valid until you revoke it.
Verify with a fresh load, not the editor’s cached state. After the fix, reload the live URL in a new incognito window and repeat step 1 — view-source, search the bundle, check Network. “It works in the builder” and “the secret is actually gone from what ships” are two different claims, and only the second one is the fix.
Comments
Sign in to join the conversation.
No comments yet — be the first.