Skip to content
SealMyApp

How to find leaked API keys in your vibe-coded app's JavaScript bundle

Anything in your frontend bundle is public. Here's how to search your Lovable, Bolt, v0 or Cursor app's JavaScript for secret keys — and fix a leak.

Updated 2026-10-16

Open your live app, press Cmd+Option+F (Ctrl+Shift+F on Windows) in Chrome DevTools to search every loaded script, and look for secret-key prefixes such as sk_live_, sk-ant-, sb_secret_ and service_role. Anything you find there is public — remove it from the frontend, move the call into a server function, and rotate the key, because deleting it isn't enough.

This matters for vibe-coded apps in particular. AI builders often call third-party APIs straight from the browser because it's the quickest way to make a feature work. According to security vendor Escape's own passive scan of over 5,600 publicly available vibe-coded apps (methodology post, October 2025), more than 400 exposed secrets turned up, and Escape notes keys were "often trivially retrievable from frontend bundles".

Only run these checks on apps you own.

Why keys end up in the bundle

Your frontend is shipped to every visitor as JavaScript files. Build tools paste certain environment variables directly into those files:

  • Vite (used by Lovable, Bolt and many others): any variable starting VITE_ "will be exposed in client-side source code after Vite bundling", per the Vite docs, which add that these variables should not contain API keys.
  • Next.js (common with v0 and Cursor): variables starting NEXT_PUBLIC_ are inlined "into any JavaScript sent to the browser", per the Next.js docs.

So VITE_OPENAI_API_KEY is not a secret, however safe the .env file feels. Neither is a key the AI pasted directly into a component.

Public by design vs must stay secret

Not every key in your bundle is a problem. Don't panic over these:

Public by designWhy it's OK
Supabase anon / sb_publishable_…Protected by Row Level Security — see our anon key guide
Firebase web config apiKey (AIza…)Identifies your project; data is protected by Security Rules (Firebase docs)
Stripe publishable key pk_live_… / pk_test_…Stripe lists it as safe to expose (Stripe docs)
Analytics IDs (PostHog, Plausible, GA)Write-only identifiers

These must never be in the browser. The prefixes below are the common formats at the time of writing; providers add new ones, so check your provider's docs if you're unsure:

Look forWhat it is
sk_live_, rk_live_Stripe secret / restricted key — can move money and read customer data
whsec_Stripe webhook signing secret
sb_secret_, or an eyJ… JWT containing "role":"service_role"Supabase secret key — bypasses all RLS
sk-proj-, sk-OpenAI key (common formats)
sk-ant-Anthropic key
AKIA (or ASIA for temporary keys) followed by 16 charactersAWS access key ID (the secret key is usually nearby)
re_Resend API key
SG.SendGrid API key
ghp_, github_pat_GitHub tokens
xoxb-, xoxp-Slack tokens
AIza… that isn't in your Firebase configPossibly a Gemini or Maps key. Firebase's docs say not to put a Gemini API key in your app's code
postgres://…:password@A database connection string

Method 1: search in the browser (2 minutes)

  1. Open your live app in Chrome and open DevTools (Cmd+Option+I / F12).
  2. Click around: log in, open the main screens, trigger the AI feature or checkout. Many apps load code in chunks, so a key used on the billing page only appears once you've visited it.
  3. Press Cmd+Option+F (Mac) or Ctrl+Shift+F (Windows/Linux) to open the search panel that searches all loaded files.
  4. Search for each prefix in the table above, one at a time: sk_live, sk-, sb_secret, service_role, AKIA, whsec_, api_key, apiKey, secret, Bearer .
  5. Also check the Network tab: click a request to a third-party API (api.openai.com, api.stripe.com, etc.). If it's coming from the browser with an Authorization header, that key is public.

Method 2: download and scan (5 minutes)

For a more thorough check, save your bundle to disk and run a secret scanner over it.

# Use your own app's URL
APP="https://your-app.example.com"
mkdir -p ~/bundle-check && cd ~/bundle-check

# Grab the page and every script it references
curl -s "$APP" -o index.html
grep -oE 'src="[^"]+\.js"' index.html | sed -E 's/src="([^"]+)"/\1/' | while read -r p; do
  case "$p" in http*) url="$p" ;; *) url="$APP/${p#/}" ;; esac
  curl -s "$url" -o "$(basename "$p")"
done

# Quick grep for the dangerous prefixes
grep -oE '(sk_live_|rk_live_|whsec_|sb_secret_|sk-ant-|sk-proj-|AKIA)[A-Za-z0-9_\-]{8,}' *.js | sort -u

Then run TruffleHog (free, open source), which recognises hundreds of key formats and can check whether a key is still live:

brew install trufflehog
trufflehog filesystem ~/bundle-check

This only grabs scripts referenced in the first page load. Lazy-loaded chunks won't be included — Method 1 covers those.

Also check these three places

  • Source maps. If https://your-app/assets/index-abc123.js.map loads, your original source code (comments and all) is public. Turn source maps off for production builds unless you upload them privately to an error tracker.
  • Exposed files. Visit https://your-app/.env and https://your-app/.git/config. Both should return 404. If either loads, treat every value in it as leaked.
  • Your Git repo. If you've synced your project to GitHub, keys committed in the past are still in history even after you delete them. Run trufflehog git file://path/to/repo locally. GitHub also offers secret scanning on public repositories.

How to fix a leaked key

Do it in this order:

  1. Rotate the key first. Create a new key in the provider's dashboard and revoke the old one. Removing it from code doesn't help — it's already been served to every visitor, may be cached, and may be in archives. For Stripe, use Rotate key on the API keys page. For Supabase secret keys, create a new one and disable the old one under Project Settings → API Keys.
  2. Check for misuse. Look at the provider's usage and billing pages for anything you don't recognise. With Stripe, check the key's request logs.
  3. Move the call to the server. The browser should call your function, and your function calls the third-party API using a key stored as a server-side secret:
    • Lovable: store it in Secrets and have Lovable call the API from backend code (an edge function, or a server function in newer projects), which is the pattern Lovable's docs describe.
    • Bolt: store it as a Bolt Cloud secret (in the database settings) and use it from a server function.
    • Replit: use Tools → Secrets; secrets aren't available to Static Deployments.
    • v0 / Vercel: add it as an environment variable in the project settings, and only read it in server code (route handlers, server actions) — never with a NEXT_PUBLIC_ prefix.
    • Cursor / hand-rolled apps: a serverless function on your host, with the key as an environment variable without a public prefix.
  4. Protect the new endpoint. A server function that forwards anything to OpenAI is just a leaked key with extra steps. Require a signed-in user, check what they're asking for, and add a rate limit.

Prompt for your AI builder:

Find every API key or secret used in frontend code, including anything in
VITE_ or NEXT_PUBLIC_ variables. Move each call that needs a secret key into a
server-side function that reads the key from server secrets. The function must
check the user is signed in and rate-limit requests. Leave only public keys
(Supabase anon/publishable, Firebase web config, Stripe publishable) in the
frontend. List every key you moved so I can rotate it.

FAQ

Is it safe if the key is in a .env file?

Only if the variable has no public prefix and is read in server code. VITE_ and NEXT_PUBLIC_ variables are copied into the browser bundle at build time.

Can I obfuscate or base64-encode the key instead?

No. The browser has to decode it to use it, so anyone can too. If the browser can use a key, the user can read it.

I removed the key. Am I safe now?

Not until you've rotated it. Assume anything that was ever in a public bundle has been copied.

Is my Supabase anon key a leaked secret?

No — it's public by design. The secret one is service_role / sb_secret_.

How often should I check?

After any AI edit that touches payments, AI features, email or integrations — that's when keys tend to get wired into the frontend "temporarily".

Check it for free

SealMyApp's free scan searches your live bundle for secret-key patterns, checks for exposed .env, .git and source-map files, and doesn't flag public-by-design keys as critical. You'll need to prove you own the app first. Run a free scan.

Sealed — a short weekly security note for vibe coders

One common security mistake in AI-built apps, how to fix it in your builder, and one useful tool. No spam; unsubscribe any time.