>_ last-mile
guide

Leaked API keys in your frontend bundle: how to check

If a secret can call a paid API or bypass your database rules, it does not belong in JavaScript the browser downloads. Here is how to find leaks fast and what to do when you find one.

Vibe coding tools often paste a working key into whichever file makes the demo succeed. That is fine on your laptop. It is a liability the moment the same file ships to production.

What must never appear in client JS

  • Stripe sk_live_ / sk_test_ secret keys
  • OpenAI / Anthropic secret API keys
  • AWS AKIA… keys and secret access keys
  • GitHub ghp_ / github_pat_ tokens
  • Supabase service_role JWTs
  • Database URLs with passwords, private keys, webhook signing secrets

These are different from publishable tokens: Stripe pk_, Supabase anon JWTs, Firebase web config. Those are expected in the client, but they still require server rules / RLS / domain restrictions so the public token cannot do privileged things.

Check 1: Search the built assets

  1. Run a production build (npm run build or your host’s equivalent).
  2. Open the output folders (.next/static, dist/assets, etc.).
  3. Search for prefixes: sk_live, sk_test, sk-ant-, sk-, AKIA, ghp_, service_role, BEGIN PRIVATE.

Also search the deployed site: DevTools → Network → JS files → open response and find-in-file. Or view source and follow script tags.

Check 2: Scan git history

Deleting a key from the current tree is not enough if it was ever committed. Use gitleaks, trufflehog, or GitHub’s secret scanning. Treat every historical hit as compromised: rotate at the provider, do not just delete the line.

Check 3: Use a public URL scanner

Paste your production URL into the Last Mile pre-launch check. It fetches the HTML and same-origin JS (capped) and looks for common secret patterns, including Supabase JWTs by decoding the role claim. Matches are shown masked. It does not store results.

A clean scan is not a guarantee. Obfuscation, server-only routes, and mobile apps need other reviews. It catches the embarrassing class of “the secret is literally in app.js.”

What to do when you find a leak

  1. Rotate first at the provider (Stripe, OpenAI, AWS, Supabase, GitHub). Assume the old value is public forever.
  2. Remove the value from source, CI logs, screenshots, and agent chat transcripts you might reuse.
  3. Move the call behind a server route or edge function. The browser calls your backend; your backend holds the secret.
  4. Audit usage in the provider dashboard for spend or odd traffic during the exposure window.
  5. Redeploy and re-scan the live bundle.

Framework footguns

  • Next.js: only NEXT_PUBLIC_* is embedded in the client. Secrets must be unprefixed and used only in server components, route handlers, or server actions.
  • Vite: only VITE_* is exposed. Anything else you import into client code may still be bundled if referenced.
  • “It worked in Cursor composer”: agents love hardcoding. Add a ground rule: never write API secrets into files; read from env; refuse if the env is missing.

Public tokens still need discipline

Finding a Supabase anon key in your bundle is normal. The follow-up question is whether RLS is on. Finding a Firebase config object is normal; check your security rules. Finding a Stripe publishable key is normal; check that webhooks and secret keys stay server-side.

Key hygiene is one of about a hundred launch concerns. Pair this with the RLS checklist and the free launch preview, or get Last Mile for the full prompt pack.

False positives and how to read them

Automated scanners look for shapes, not intent. A tutorial string like sk_test_51ExampleOnly in a docs page may match. A Supabase anon JWT will match JWT rules even though it is supposed to be public. Treat each hit as a question:

  • Is this a real live credential or a placeholder?
  • Does possessing it let someone spend money, read private data, or push code?
  • If yes, rotate and remove. If it is a public token, verify the compensating control (RLS, Stripe domain settings, etc.).

Never paste full secrets into Discord, GitHub issues, or agent chats while debugging a leak. Use masked forms (first/last four characters) and rotate if you already pasted the full value.

Prevent the next leak

  • Add a pre-commit secret scanner locally if you use git.
  • Turn on GitHub secret scanning / push protection where available.
  • Give your coding agent a standing rule: refuse to hardcode credentials; require env vars.
  • Re-run a bundle search and the URL check after every production deploy.

Related guides

  • Supabase anon key exposed: is it a problem?

    Short answer: the anon key being visible is normal. Whether that's a problem depends entirely on your Row Level Security. The service_role key is a different story.

  • How to ship a Cursor or Claude Code app safely

    AI coding agents are great at generating working apps and equally good at “fixing” twenty files you did not ask about. Shipping safely is less about never using agents and more about putting them on a leash.

  • A vibe-coded app launch checklist

    Your app runs on a preview URL. Friends can click around. Launch is the moment real users and real money show up. This checklist is the short version of what usually breaks first.


Get launch notes

~/newsletter

$ subscribe --launch-notes # new checks, prompts and shipping tips for vibe coders. No spam.