Did my app leak its API key?
If a secret key ships in your frontend, anyone who opens your site can copy it. This page shows you how to check your live app the way an attacker would, and what to do if you find one.
Short answer: Open your live site, search its JavaScript for strings like sk_live_, sk-proj-, AKIA and eyJ, and decode any JWT you find. If a secret key is there, rotate it first, then move the code that uses it to a server function.
This is the detection page: how to find out whether a key already leaked. If you already know a key is in your frontend and want the background on why and how to restructure your code, read API key exposed in your frontend.
What a leaked key is
An API key is a password for a service. Some keys are meant to be public: a Stripe pk_live_ publishable key, a Supabase anon or sb_publishable_ key, a referrer-restricted Google Maps key. Others are secrets that can spend your money or read your data: Stripe sk_live_, an OpenAI key, an AWS access key, a Supabase service_role key.
A key "leaks" when a secret one ends up in code the browser downloads. Every visitor gets a copy of your JavaScript. Minification makes it ugly, not private. AI builders cause this often: you ask for "add OpenAI" and the generated code calls the API straight from a React component, with the key in a VITE_ or NEXT_PUBLIC_ variable. Vite and Next.js both document that those prefixes are inlined into client code.
How attackers find them
Nobody reads your code by hand. Bots load pages, download every script, and run regular expressions over the text. Secret keys have recognizable prefixes, so the search is cheap. These are the strings worth searching for:
sk_live_andrk_live_: Stripe live secret and restricted keys.sk-proj-,sk-svcacct-, or ansk-key containingT3BlbkFJ: OpenAI keys.sk-ant-: Anthropic keys.AKIAorASIAfollowed by 16 capital letters and digits: AWS access key IDs.AIza: Google API keys. Often fine if restricted, costly if not.eyJ: the start of a JWT. Supabase's legacy anon and service_role keys are JWTs, and only the payload tells you which one you have.sb_secret_: Supabase's newer secret keys.
How to check your own app (manual, 5 minutes)
- Open your live site, not your editor preview. The deployed build is what attackers see.
- Open DevTools (F12, or Cmd+Option+I on a Mac) and go to the Network tab. Filter by JS and reload. Every file listed is downloadable by anyone.
- Press Ctrl+Shift+F (Cmd+Option+F on a Mac) to open search across all loaded sources. Search each string from the list above, one at a time.
- For each
eyJhit, copy the token and decode the middle part (between the two dots). It's base64. Paste this in the DevTools Console, with your token in place of TOKEN:
If you seeJSON.parse(atob('TOKEN'.split('.')[1].replace(/-/g,'+').replace(/_/g,'/')))"role":"service_role", that key bypasses all Row Level Security. Treat it as an emergency."role":"anon"is the public key and is expected. - Click through a few pages, especially behind login. Some apps load extra chunks on demand, and those only appear in the search after they've loaded.
How to fix a leaked key
Order matters. Rotate first, because the leaked key stays valid until you kill it.
- Rotate or revoke at the provider. Stripe: Developers, then API keys, then roll the key. OpenAI: delete the key on the API keys page. AWS: deactivate the access key in IAM. Supabase: rotate the secret or JWT key under Project Settings, API. Create the replacement, but don't put it in the frontend.
- Check for misuse. Look at the provider's usage or logs since the key first shipped: Stripe events, OpenAI usage, AWS CloudTrail and billing.
- Move the call server-side. The browser should call your own endpoint (a Supabase Edge Function, a Next.js Route Handler, a Cloudflare Worker), and that endpoint uses the key from a server-only environment variable. Require a logged-in user and add rate limiting if it calls a paid API.
Find every place in this app that uses a secret API key in client-side code (including VITE_ or NEXT_PUBLIC_ variables). Move each call into a server-side function that reads the key from a server-only environment variable, require an authenticated user, add basic per-user rate limiting, and make the frontend call that function instead. Remove the key from all client code.
- Redeploy and recheck. Repeat the DevTools search on the live site. The old string should be gone from every script.
- Clean the repo. If the key was ever committed, it's in your git history. Rotating made it useless; rewriting history is optional cleanup.
Make it stick
- Keep secrets out of any variable with a
VITE_,NEXT_PUBLIC_orEXPO_PUBLIC_prefix. - Turn on GitHub push protection or run a secret scanner like gitleaks before you push.
- Recheck after every deploy that touches an integration. AI edits regress quietly.
What the free scan checks here (and what it doesn't)
The scan fetches your page and up to 25 scripts it references (script tags and modulepreload links) and searches them for: Stripe sk_live_/rk_live_, OpenAI, Anthropic and AWS access keys, Google API keys, and Supabase service_role JWTs and sb_secret_ keys. A Supabase anon or publishable key is reported as information, since it's public by design. It doesn't log in, doesn't load chunks that only appear after you click around, and doesn't know every provider's key format, so the manual search above is still worth doing. It also checks /.env, /.git/HEAD, four security headers and HTTPS; see the 6 mistakes guide. For Supabase data access, follow the Supabase RLS checklist.
FAQ
- Is it safe to have my Supabase anon key or Stripe publishable key in the frontend?
- Yes. Supabase anon or publishable keys and Stripe pk_ keys are designed to be public. The anon key is only safe if Row Level Security is on for every table. Secret keys such as sk_live_, service_role or sb_secret_ must never ship to the browser.
- I deleted the key from my code. Am I safe now?
- No. Anyone who loaded your site before the fix may already have it, and it may still be in your git history. Rotate or revoke the key at the provider first, then remove it from code and redeploy.
- Does the free scan find every leaked key?
- No. It checks your page and up to 25 scripts it links to for Stripe live secret and restricted keys, OpenAI, Anthropic, AWS access key IDs, Google API keys and Supabase service_role or sb_secret_ keys. Other providers and lazy-loaded chunks may be missed.