How Attackers Extract Secrets From JS Bundles
An API key in your JavaScript can be pulled in minutes with no special tools. See exactly how attackers find secrets in a JS bundle — and run the same check on yourself.
There's a comforting idea that a secret tucked inside a few megabytes of JavaScript is hard to find. But it is not buried. An API key in your JavaScript can be found by anyone in a few minutes, with tools that ship in every browser.
This post is a defensive walkthrough. We'll follow the exact steps someone uses to pull secrets out of a frontend — not to teach attacks, but because the only way to feel the risk is to watch how cheap it is, then run the same check on yourself first. By the end you'll know how to open your own app the way an outsider would, the patterns to search for, and the line between what's safe to ship and what must never appear in browser code.
⚡ TL;DR
- Your frontend code is public — the browser has to download it — so anyone can read your JavaScript with built-in dev tools. No special access or software.
- Finding a secret is a text search. Keys follow predictable formats (
sk_live_,sk-,sk-ant-,service_role), so a quick search surfaces them in seconds.- Run the same search on yourself. A public-by-design key (
pk_live, the Supabaseanonkey, the Firebase config) is fine. A secret key means rotate it.- The only real fix is to keep secrets server-side, where the browser never sees them.
Step 1: read the JavaScript every visitor already gets
Start with the thing founders forget: your frontend is not private. When someone visits your app, their browser downloads your JavaScript and runs it. That download is your source code, handed to every visitor by design.
So the first move is to open it. Anyone can load your live app, open the browser's developer tools (right-click → Inspect, or press F12), and go to the Sources tab to browse the JavaScript files it loaded — or the Network tab to watch them arrive. They're now looking at the same code your app shipped to the world. If your app published source maps, it's worse: instead of minified text, they see your original, commented source. We cover that multiplier in your source maps are publishing your source code.
A common reassurance is "but my code is minified — it's unreadable." Minification shrinks code and shortens names; it does not encrypt anything. A wall of minified text is still searchable text — which brings us to the part that takes seconds.
Step 2: search the bundle for key-shaped strings
Here's why this is so fast: secrets look like secrets. Providers give their keys distinctive prefixes so their own systems can recognize them — and that same recognizability lets anyone find them with a text search.
In dev tools, the global search (usually Ctrl/Cmd-Shift-F) scans across every loaded file at once. An outsider types in a known prefix and reads the hits. These are the patterns to search for:
sk_live_— a Stripe secret key. Full access to a Stripe account.sk-— an OpenAI API key. Metered, billable, secret.sk-ant-— an Anthropic API key. Same.service_role— the Supabase admin key. It's a JSON Web Token (JWT = a long, dotted token in three base64 parts) whose payload contains the text"role":"service_role".eyJ— the opening characters of almost every JWT. Searching this surfaces tokens worth a closer look, including a leakedservice_role.
To be more thorough, the same idea works as a regular expression (a pattern matching a shape of text rather than an exact string). A few that match common key formats:
# Stripe secret keys
sk_live_[0-9a-zA-Z]{24,}
# OpenAI / Anthropic keys
sk-[A-Za-z0-9-_]{20,}
sk-ant-[A-Za-z0-9-_]{20,}
# Any JSON Web Token (catches a leaked Supabase service_role)
eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+
There's no clever hiding spot: if a secret is in your JavaScript, a pattern match finds it. The people who do this at scale run automated scanners that crawl thousands of sites with these patterns — that's largely how researchers found these issues across AI-built apps. When Escape.tech scanned 5,600 vibe-coded apps, they turned up more than 400 exposed secrets. Attackers run the same playbook.
Step 3: watch the network for hidden endpoints
Searching the static bundle is the loud method; the quieter one is to watch the app work. With the Network tab open, an outsider clicks around your app and watches every request — which reveals two things a bundle search misses. First, endpoints you assumed were hidden. An admin route, an internal API, a "secret" URL — if your frontend ever calls it, it shows up in the network log. Hiding an endpoint by not linking to it is not security. Second, a key in transit: if your app sends a secret key in a request header from the browser, the network tab displays that header in full. The key never had to be in the bundle to leak — it leaked the moment the browser sent it. This is why "I load it at runtime, not hardcoded" doesn't help when the runtime is the browser.
What's safe to ship vs what must never appear
Now the most important distinction in this topic, because running these searches on yourself will turn up keys — and most are fine. Panicking over the wrong ones is its own failure mode.
Some keys are public by design. They're built to live in the browser, so finding them there is intended, not a leak:
- Stripe
pk_live_…— the publishable key. It tells Stripe which account a payment belongs to. It cannot move money or read your customers. - Supabase
anonkey — the anonymous public key. Supabase apps talk to the database from the browser, so this key has to be there. Your protection is Row Level Security (RLS = database rules controlling which rows each user can read), not hiding it. - Firebase web config (
apiKey) — public despite the name. It identifies your Firebase project. Your protection is Firebase Security Rules, not secrecy.
Other keys must never appear in browser code, because they carry real authority:
- Stripe
sk_live_…— full account access: charge cards, issue refunds, read your customer list. - Supabase
service_role— bypasses RLS entirely and can read and write every row in every table. - OpenAI / Anthropic keys — secret and metered. Whoever holds one runs requests on your bill.
🐺 Not a real problem
If your search finds a Stripe
pk_live, a Supabaseanonkey, or a Firebase web config, that is not a leak — those are meant to ship, so don't rotate them. Save the alarm forsk_live,service_role, and any OpenAI or Anthropic key — the ones an outsider's search is actually hunting for. The single-letterpkversusskdifference is the whole tell, and we break it down in did I leak my Stripe key?.
How to check your own app
Do the attacker's pass on yourself. Ten minutes, no tools beyond the browser.
- Open your live app and open dev tools (right-click → Inspect, or F12). Go to the Sources tab.
- Run a global search (Ctrl/Cmd-Shift-F) for each secret pattern:
sk_live_,sk-,sk-ant-,service_role, andeyJ. Read every hit. - Triage what you find. A
pk_live, ananonkey, or a Firebase config is expected — leave it. Ansk_live, aservice_role, or an OpenAI/Anthropic key in browser-delivered code is a real leak. - Open the Network tab and use your app. Click through your main features and scan the request list for unfamiliar endpoints and any secret key in a header.
- Check for source maps. If the Sources tab shows readable code or files ending in
.map, search those too.
This catches the obvious cases, not everything. Keys can hide in lazily-loaded chunks, in third-party scripts, or in API responses — the gap an automated scan closes.
Want the attacker's-eye view of your own app?
Run a free, read-only scan of your live app — no install, results in under a minute. Is My Site Hackable? runs these same searches and tells you which keys are real leaks and which are public by design.
Scan my app free →The fix: keep real secrets server-side
If the browser can read everything it runs, a secret key must never run there. The fix is not to hide it better — it's to move it where the browser can't reach.
The pattern is a small backend in the middle. Instead of your frontend calling OpenAI with your secret key, your frontend calls your own server, and your server holds the key and makes the call. The key lives in a server-side environment variable, a serverless function, or a Supabase edge function — somewhere only your infrastructure can see. The browser sends a request; you do the privileged work and return only the result.
One trap: build tools bake environment variables with a public prefix into the frontend bundle. Put a secret behind one — VITE_, NEXT_PUBLIC_, or REACT_APP_, as in VITE_OPENAI_KEY — and the build ships it to every visitor. Secret keys belong in unprefixed, server-only variables.
If your check turned up a real secret, moving it server-side is step two; step one is to rotate it, since it's already public. The order of operations — rotate, audit, monitor — is in a key just leaked — rotate, audit, monitor, and the bigger picture is in the complete guide to exposed secrets in frontend code.
FAQ
Can someone really read my API key from a JavaScript file?
Yes, easily. Your frontend JavaScript is downloaded by every visitor's browser, so it's public by definition. Opening dev tools and running a text search for a prefix like sk_live_ takes seconds. If a secret key is there, assume it can be found.
Does obfuscating or minifying my JavaScript protect the key?
No. Minification and obfuscation make code harder to read, but they don't encrypt it or prevent a text search. The key still exists as a string in the file, and pattern matching finds it. The only reliable protection is to keep the secret off the frontend.
How is finding an exposed key different from hacking?
It usually isn't hacking at all. Reading your public frontend code and searching it for key patterns uses nothing but the browser's built-in tools on code you chose to ship. No system is broken into — the information was already public. That's why the fix is prevention.
The bottom line
An API key in your JavaScript is not hidden by being buried in a big file — it's a text search away, and automated scanners check thousands of sites for it. The reassuring part is that the dangerous version is narrow: public-by-design keys like Stripe pk_live, the Supabase anon key, and the Firebase config are meant to ship. The fix for the real ones is clear — keep secret keys server-side, where the browser never sees them. An outsider can run this check on you in minutes, so run it on yourself first, and keep running it, because your AI builder can reintroduce a hardcoded key on the next deploy.
Find your gaps before an attacker does.
Is My Site Hackable? scans your deployed app for the exact issues in this article — exposed keys, missing RLS, open buckets — and tells you what's real and what's a false alarm.
Run a free scan →