Dev.to AI πŸ€– Ai πŸ‘ 2 πŸ“– 5 min read

How to Find Exposed API Keys in Your Vibe Coded App (a 5 Minute Check)

If you built your app with Lovable, Bolt, Replit, Cursor or v0, there's a real chance something secret ended up in the code your browser downloads. Not because you did anything dumb. The AI just put the key where it made

How to Find Exposed API Keys in Your Vibe Coded App (a 5 Minute Check)

If you built your app with Lovable, Bolt, Replit, Cursor or v0, there's a real chance something secret ended up in the code your browser downloads. Not because you did anything dumb. The AI just put the key where it made the feature work.

Short answer: to find exposed API keys in a vibe coded app, open your live site, open DevTools, press Ctrl+Shift+F (Cmd+Option+F on Mac) and search every loaded file for strings like sk_, sk-, service_role and secret. If a secret key shows up, it's public. Move it to the server and rotate it today.

That's the whole idea. Below is how I actually do it, what the results mean, and what to do if you find something.

Why vibe coded apps leak API keys

When you ask an AI builder to "add Stripe" or "call OpenAI", the fastest working version often calls the API straight from the frontend. It works on the first try, so nobody looks again.

The problem is that anything in your frontend is shipped to every visitor. Minified, sure. Hidden, no. Anyone can open DevTools and read it.

Build tools make this easy to miss. In Vite, any env variable that starts with VITE_ gets bundled into the client code (Vite docs). In Next.js it's the NEXT_PUBLIC_ prefix (Next.js docs). So VITE_OPENAI_KEY in your .env file isn't private. It's in the bundle.

The 5 minute exposed API keys check

You don't need any tool for this. Just Chrome or any Chromium browser.

Chrome DevTools search panel searching every loaded JavaScript file for the text service_role

  1. Open your live site, not localhost. Log in if most of the app sits behind a login, because some code only loads after that.
  2. Open DevTools (F12, or right click and Inspect).
  3. Press Ctrl+Shift+F on Windows/Linux or Cmd+Option+F on Mac. This opens a search across every file the page loaded.
  4. Search these one at a time:
    • service_role
    • sk_live and sk_test
    • sk- (OpenAI style keys start like this)
    • secret
    • apikey and api_key
    • Bearer
  5. Click around the app a bit, open the main pages, then search again. Lazy loaded chunks only show up after you visit them.
  6. Open the Network tab, reload, and click a couple of requests to your own API or to third party APIs. Look at the request headers. If an Authorization header carries a secret key from the browser, that key is public too.

Most of the time you'll get matches. Don't panic yet. Some keys are supposed to be there.

Which keys are fine to be public (and which aren't)

Checklist of keys that are OK to be public versus keys that should never be in the browser

Usually fine in the browser:

  • The Supabase anon (publishable) key. Supabase built it to be public, and your Row Level Security policies are what protect the data (Supabase docs on API keys). Fine, as long as RLS is actually on.
  • Stripe publishable keys that start with pk_. Those are made for the frontend (Stripe docs).
  • The Firebase web config object. It identifies your project, and Firebase Security Rules do the protecting.

Never in the browser:

  • The Supabase service_role (secret) key. It skips RLS completely. Anyone with it can read and write every table.
  • Stripe secret keys, sk_live_ or sk_test_. Stripe's own docs say to keep them on the server.
  • OpenAI, Anthropic or any other AI API key. Someone copies it, and you pay for their usage.
  • Email provider keys (Resend, SendGrid, etc). These can send mail as your domain.

What to do if you find an exposed key

I'd do it in this order:

  1. Rotate the key first. Make a new one in the provider dashboard and delete the old one. Removing it from the code is not enough, because the old bundle may still be cached, and the key may already be in your git history.
  2. Move the call to the server. A Supabase Edge Function, a Next.js API route, a Replit backend route, whatever your stack has. The browser calls your endpoint, your endpoint holds the key.
  3. Check usage. Look at the provider dashboard for calls you don't recognize. For AI keys, set a hard monthly spend limit if the provider has one.
  4. Search again. Redeploy, hard refresh, run the Ctrl+Shift+F check one more time.

A prompt that usually works in Lovable, Bolt or Cursor:

Move every call that uses a secret API key out of the frontend into a server side function. The frontend should call that function instead. Read the key from a server only environment variable, not one with a VITE_ or NEXT_PUBLIC_ prefix. Show me which files you changed.

Then check what it did. Don't just trust the "done!" message.

Exposed API keys checklist (copy this)

  • [ ] Ran Ctrl+Shift+F on the live site for service_role, sk_, sk-, secret, apikey, Bearer
  • [ ] Logged in and visited the main pages, then searched again
  • [ ] Checked Network tab request headers for secret keys
  • [ ] No secret keys in env variables that start with VITE_ or NEXT_PUBLIC_
  • [ ] Any exposed key was rotated, not just deleted from code
  • [ ] Secret calls now go through a server function
  • [ ] Spend limits set on AI and email providers
  • [ ] RLS is on for every Supabase table (since the anon key is public anyway)

FAQ

Is the Supabase anon key safe to expose?

Yes, it's designed to be public. But it's only safe if Row Level Security is enabled with real policies on every table. With RLS off, the anon key can read your tables.

Can someone really find my API key in minified JavaScript?

Yes. Minifying shortens variable names, it doesn't hide strings. A key stays a plain string, and the DevTools search finds it in seconds.

I deleted the key from my code. Am I safe?

Not yet. Rotate it. Old builds, caches and git history can still have the old key.

Does this check cover everything?

No. It catches keys sitting in the frontend. It won't tell you if your database rules let user A read user B's data, which is the other big one in vibe coded apps.

A free self-check for the rest

I built ShipShield after seeing the same handful of problems over and over in AI-built apps. The free self-check is 10 yes/no questions, takes about two minutes, and needs no signup. It covers exposed keys plus the things this post doesn't, like RLS and who can see whose data.

You can run it here: shipshield.app

If you run the Ctrl+Shift+F check and find something weird, tell me in the comments. I'm curious what people actually find.

πŸ“° Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.