Skip to content

secrets · July 10, 2026 · 6 min read

API Keys in Your JavaScript Bundle: How to Find and Fix Them

Anything in your frontend JavaScript is public. Learn which API keys are safe to ship, which never are, and how to find leaks in your built bundle.

Your backend code is private. Your frontend code is not — every byte of JavaScript your site ships can be downloaded and searched by anyone, no login required. If an API key is in there, it’s public, and automated scrapers are much faster at finding it than you are.

Your frontend is public by definition

Every visitor’s browser downloads your bundle to run it. Anyone can open DevTools, view source, or fetch the files with a script. Minification is not protection: it renames variables, not string values, so a key sits in the file verbatim and a simple pattern match finds it instantly. Bots crawl the web doing exactly that, at scale, around the clock.

How keys end up in the bundle

  • Build-time inlining. Frameworks replace environment-variable references with literal strings at build time. Server-side env vars stay on the server — but anything referenced in client code gets pasted into the shipped JavaScript.
  • The NEXT_PUBLIC_ trap. In Next.js, only variables prefixed NEXT_PUBLIC_reach the browser. That prefix is a promise: “this value is safe to publish.” The classic failure: client code crashes because process.env.OPENAI_API_KEYis undefined in the browser, and someone — a developer or an AI assistant — “fixes” it by renaming the variable to NEXT_PUBLIC_OPENAI_API_KEY. The build succeeds, the app works, and the key is now public. Vite’s VITE_ and Create React App’s REACT_APP_ prefixes behave the same way.
  • Hardcoded keys.Pasted directly into code “temporarily” during prototyping, then shipped.
  • Config objects. Firebase configs, Supabase client setups, and similar objects mix values that are fine to publish with slots where the wrong key type is catastrophic.

AI coding tools optimize for “it runs,” which is often the shortest path — not the safest one. If your app came out of Cursor, Lovable, Bolt, v0, or Replit, an explicit bundle audit belongs on your ship checklist.

Which keys are safe to ship — and which never are

Some keys are designed to be public. They identify your project, and the real protection lives server-side:

  • Stripe publishable key (pk_live_...)
  • Supabase anon key — safe only when Row Level Security is enabled
  • Firebase web API key — safe only with proper security rules
  • Analytics and maps keys, ideally domain-restricted

These must never appear in a browser:

  • Stripe secret key (sk_live_...)
  • Supabase service_role key
  • OpenAI and Anthropic API keys
  • AWS access key IDs and secrets
  • Private keys, SMTP and SendGrid credentials, GitHub tokens

Rule of thumb: if a key can spend money, read other users’ data, or act with admin power, it belongs on a server. Full stop.

What a leaked key actually costs

This isn’t theoretical. A leaked OpenAI key can rack up thousands of dollars in fraudulent usage before you notice, because harvested keys get used within hours — often for resale. Leaked AWS credentials routinely end in crypto-mining bills. Beyond the invoice: your quota gets exhausted (real users see errors), your provider may suspend the account, and whatever data the key could read should be considered read.

How to check: search your bundle, not your source

Your source can look clean while your bundle isn’t — inlining happens at build time. So check the built output:

# search your build output (.next/, dist/, build/)
grep -rE "sk-[A-Za-z0-9]|sk_live_|AKIA[0-9A-Z]{16}|-----BEGIN|service_role" \
  .next/static dist build 2>/dev/null

Then check the deployed reality: open DevTools → Sources on your live site and search the JavaScript for sk-, sk_live_, AKIA, and service_role.

Finally, review what you’ve opted into publishing:

grep -h "NEXT_PUBLIC_\|VITE_\|REACT_APP_" .env* 2>/dev/null

For each line, ask: would I post this value on a billboard? If not, it needs a new home. And make sure the .env file itself isn’t downloadable — that’s its own critical issue.

How to fix a leaked key

  1. Rotate first, before touching code. Once a secret has shipped, treat it as stolen. Old bundles live on in CDN caches, archives, and downloads — revoke and reissue now.
  2. Move the call server-side. A small proxy route keeps the key on the server:
    // app/api/chat/route.ts — runs on the server; the key never ships
    export async function POST(req: Request): Promise<Response> {
      const res = await fetch("https://api.openai.com/v1/chat/completions", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.OPENAI_API_KEY}`, // no NEXT_PUBLIC_
          "Content-Type": "application/json",
        },
        body: JSON.stringify(await req.json()),
      });
      return Response.json(await res.json());
    }
    Your browser code calls /api/chatinstead of the provider. Add auth and rate limiting to the route so strangers can’t use your proxy for free.
  3. Un-prefix. Anything secret loses its NEXT_PUBLIC_ / VITE_ / REACT_APP_ prefix and moves behind a route like the one above.
  4. Set spend limits and usage alerts with every provider that offers them — they turn a disaster into an email.
  5. Restrict the public keys you do ship with domain allowlists where supported.

How BoringSec checks this

BoringSec’s bundle-secrets scanner downloads the JavaScript your site actually ships and runs 21 secret detectors against it: Supabase anon and service_role keys and project URLs, Firebase API keys and config, Stripe secret and publishable keys, OpenAI keys in both formats, AWS access key IDs and secrets, private keys, tokens for GitHub, Slack, Anthropic, Google OAuth, SendGrid, Hugging Face, Twilio, and Mailgun, plus generic API-key patterns.

It’s the highest-weighted category of the Boring Score — our score for AI-built apps— because a shipped secret does more damage than any missing header. Like every base URL module, it’s evidence-first: findings ship with captured proof and cite their standard (OWASP, CWE, RFC, MDN). Details on /methodology.

FAQ

Is my Supabase anon key in the bundle a leak?

No — it’s designed to be public, provided Row Level Security is enabled on your tables. The service_role key is the one that must never ship.

Does minifying or obfuscating my code hide keys?

No. Minification renames variables, not string values. The key sits in the file verbatim, and pattern matching finds it in milliseconds.

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

No. Rotate it. The old bundle still exists in caches, archives, and downloads. Revoke the key, reissue it server-side, and set a spend limit.

Keep reading

Check your site now

One scan applies the relevant checks from 20 base URL modules plus 3 verified-owner background engines. Free, no signup, with base results appearing first.

Scan your site free