Every web app ships a bundle: the JavaScript files a visitor's browser downloads to run the page. Anything inside those files is public, because the browser has to read them. This post shows you how to search your own bundle for a secret key in about ten minutes, with no coding.
Why a key in the bundle matters
Supabase, the hosted Postgres backend behind many apps built with AI app builders, issues two kinds of key. The publishable key, formerly called anon, is designed to be public and reaches only what Row Level Security allows. Row Level Security (RLS) is the set of per-row access rules on each table. The secret key, formerly called service_role, has the BYPASSRLS property and skips every one of those rules. Supabase's documentation says never to put it in a browser, a shipped app or source control.
Stripe works the same way. Keys starting pk_ are publishable. Keys starting sk_live_ or sk_test_ are secret keys with unrestricted permissions, and Stripe recommends restricted keys, starting rk_, for anything that does not need full access.
So a secret key in the bundle means any visitor can copy it and act as your server: read every row, change any record, or make charges on your account. It is worth knowing that Supabase's own tooling helps here. Tables created in the dashboard get RLS by default, and Security Advisor flags tables without it. None of that protects you if the key that skips RLS is sitting in a public file.
Escape's scan of more than 5,600 publicly deployed apps built with AI app builders found more than 400 exposed secrets (Escape, 2025). The check below tells you whether yours is one of them.
The ten-minute check
- Open your live app in Chrome, right-click anywhere and choose Inspect. Developer tools open at the side.
- Click the Sources tab. Press Cmd+Option+F on a Mac or Ctrl+Shift+F on Windows. This searches every file the page has loaded, not just the one on screen.
- Search for
service_role. Then search forsb_secret_. Thensk_live_andsk_test_. - Search for
eyJ. Legacy Supabase keys are JWTs, signed tokens that start with those three letters. A hit is not a problem by itself, because the publishable key is also a JWT. Copy the value and paste it into the API Keys page of your Supabase project settings to see which key it is. - Click the Network tab and reload the page. Click any request whose address contains
supabase.coand read theapikeyheader under Request Headers. Compare it with the keys in your dashboard.
A bad result is any hit on service_role, sb_secret_, sk_live_ or sk_test_, or an apikey header that matches the secret key rather than the publishable one.
If you use Next.js, also look at the names of your environment variables, the settings your hosting platform passes to the app. Any variable prefixed NEXT_PUBLIC_ is copied into the bundle at build time. A secret with that prefix is the same problem with a different cause; the pattern page for NEXT_PUBLIC_ on a secret covers it.
What to do if you find one
Do these in order, and do the first one today.
- Rotate the key. In Supabase, open Project Settings, then API Keys, and generate a new secret key, then revoke the old one. In Stripe, open Developers, then API keys, and roll the key. Anyone who loaded the old build may hold a copy, so a rotation is not optional.
- Move the work to the server. Whatever the browser was doing with the secret key needs to run in a place the visitor cannot read: a Supabase Edge Function, a Next.js route handler or server action, or your builder's backend functions. The browser then calls that server code, which holds the key.
- Put the publishable key back in the browser and confirm the app still works. If something breaks, it was relying on the secret key to skip RLS, which means the table's policies also need attention. Start with RLS disabled on a table.
- Redeploy and repeat the search above on the new build.
Ask your builder's chat to make the change if you do not write code yourself. Describe it exactly: "Move the Supabase secret key out of the frontend into an edge function and use the publishable key in the browser." Then run the check again rather than trusting the answer.
The full pattern record, with the code shape it takes and how we test for it, is at Service-role key shipped in the browser bundle. The Supabase stack hub lists the other Supabase defaults worth knowing and the security features Supabase provides.
What to do this week
- Run the Sources search on your live app and on any preview or staging URL: Service-role key in the bundle.
- Check for third-party keys the same way, using each provider's prefix: Third-party API key in the JavaScript bundle.
- Answer the self-check questions for your stack, offline and in a few minutes: Self-check.