ShipSafe

Secrets · 7 min · Updated 26 Aug 2026

Exposed API keys in your JavaScript bundle: find them before an attacker does

Anything in your client bundle is public. Here is how to tell whether a secret key is sitting in yours, and how to fix it.

Everything your browser downloads to run your app is readable by anyone who visits it. Your JavaScript bundle is not hidden, not compiled away and not protected by minification. If a secret is in it, treat it as already public.

The rule

A key is safe in the client only if it is designed to be public: a Supabase anon key, a Stripe publishable key, a public analytics token. A secret key, a service-role key or a private API token in the bundle is a direct line into your data or your bill.

§ 01

Why coding agents ship keys to the client

An agent's job is to make the feature work. Calling an API from the browser is the shortest path, and pasting the key that makes the call succeed is the shortest path to that. The result runs perfectly in a demo and exposes the key to every visitor.

The second cause is a naming convention. In Next.js, a variable prefixed NEXT_PUBLIC_ is deliberately shipped to the browser; in Vite it is VITE_. Put a secret behind that prefix and the framework will faithfully publish it.

§ 02

Find a leaked key in your own bundle

You do not need any tools. Open your live app, open your browser's developer tools, and use the Sources or Network tab to search the loaded JavaScript for the patterns keys follow.

sk_live_        Stripe secret key            — must never be client-side
sk_test_        Stripe secret test key        — still should not ship
service_role    Supabase service-role JWT     — bypasses all RLS
AKIA…           AWS access key id             — root of cloud access
AIza…           Google API key                — often over-scoped
-----BEGIN      a private key of any kind      — never in a bundle
Search your bundle for these prefixes. A match on any of the top three is a problem.

A Supabase anon key (a long JWT) and a Stripe key starting pk_ are meant to be public and are fine to find. It is the sk_, the service_role and anything labelled secret or private that matters.

§ 03

If you find one, rotate before you refactor

The instinct is to move the key server-side and redeploy. Do that second. Do this first: assume the key is compromised and rotate it, because it has been downloadable for as long as the app has been live, and a leaked key is not un-leaked by deleting the line.

  1. 01Rotate the key in the provider's dashboard so the exposed value stops working.
  2. 02Move the real call server-side: an API route, an edge function or a serverless function that holds the key in a server-only environment variable.
  3. 03Confirm the variable is not prefixed NEXT_PUBLIC_ or VITE_, so the framework keeps it on the server.
  4. 04Redeploy, then re-open the bundle and confirm the key is gone from it.
  5. 05Check the provider's usage logs for calls you did not make while the key was exposed.
Retest, do not assume

A fix here is easy to believe and easy to get wrong — the key can linger in a cached bundle or a second entry point. The only way to know is to search the live bundle again after deploy and confirm the exact string no longer appears.

§ 04

What a Preflight sees here

This exposure is fully visible from outside, without a login, which is what makes it a good first thing to check. The free Preflight fetches your live bundle and source maps, scans them for secret key patterns, and quotes the exact line and the key type back to you with the time it was pulled.

If nothing sensitive is in the bundle, that is worth confirming too. The report says plainly what it checked and what it could not see from outside, so a clean secrets result is a real result, not a silence.

Questions people ask

Short answers, no hedging

How do I find an API key in my JavaScript bundle?
Open your deployed app, view the network panel, and search the downloaded JavaScript for the shapes keys take: sk_, AKIA, ghp_, AIza, eyJ, and the word secret. A build-time search of your own dist or .next output finds the same thing faster. Anything that appears there has already been served to every visitor.
Which keys are safe to ship in the browser?
Only keys whose whole purpose is to be public and whose permissions are enforced elsewhere: a Stripe publishable key, a Supabase anon key behind correct RLS, a Firebase web config behind correct rules, a Maps key restricted by referrer. Every other key belongs on your server behind an endpoint you control.
Is rotating the key enough?
Rotating is the first step, not the fix. Assume the old key was used, check the provider's logs for calls you did not make, then move the call server-side so the new key never reaches a bundle. A rotated key pasted back into client code is the same finding with a new value.
Run the check on your own app

Stop reading about it. Point us at your URL.

The free Preflight is passive and read-only. It checks your app for exactly the exposures this guide describes and returns a score, a verdict and one finding with its evidence. No signup, no card.

Keep reading