Blog

How to check if your Supabase secret key leaked (and what to do if it did)

Shaig Khaligli

Shaig Khaligli, security engineer
· 4 min read

Open your live site, search the JavaScript it loads for sb_secret_ and for the word service_role, and check your repository history and environment variable names. If the secret key turns up anywhere a user can reach, treat it as compromised: fix whatever leaked it, create a new key, swap it in, and only then delete the old one.

It takes about fifteen minutes, and it's worth doing even if you're fairly sure you never used the secret key in client code.

Key takeaways

  • The secret key (sb_secret_...) and legacy service_role key bypass every RLS policy. Anyone who has it has your whole database.
  • The most common leak is an environment variable with a public prefix like NEXT_PUBLIC_ or VITE_.
  • Search the built bundle, not just your source code. That's what attackers see.
  • Rotate in Supabase's order: fix the cause, create the new key, replace it everywhere, then delete the old one.

Why this key matters so much

Supabase's docs say the secret key "bypasses every Row Level Security policy you have. Never put one in a browser, a shipped application, or source control" (Supabase docs, API keys). It runs as service_role, which has Postgres's BYPASSRLS attribute, so none of your policies apply.

Compare that with the publishable key (or legacy anon key), which is meant to be public and only reaches what RLS allows. We explain that difference in is it safe to expose your Supabase anon key?.

Where to look, at a glance

Where it leaks How to check What you're looking for
Your live JavaScript bundle Search all files in browser dev tools sb_secret_ or a JWT whose role is service_role
Environment variables Your .env files and hosting settings A secret key under NEXT_PUBLIC_, VITE_ or EXPO_PUBLIC_
Repository history git log -S across all branches Any commit that ever contained the key
Logs and screenshots Error trackers, support tickets, docs Keys pasted while debugging

Step 1: Search what you actually ship

Your source code isn't what an attacker sees. The built bundle is. So check the live site:

  1. Open your production site, then open the browser's developer tools.
  2. In the Sources (Chrome) or Debugger (Firefox) panel, use search across all files (Cmd+Option+F on Mac, Ctrl+Shift+F on Windows).
  3. Search for sb_secret_, then service_role.

If your project still uses legacy JWT-style keys, both keys start with eyJ, so searching for that alone isn't conclusive. Decode any JWT you find (the middle part is base64) and check its role claim: anon is fine, service_role is not.

You can do the same from the command line against your build output:

grep -rEn "sb_secret_|service_role" dist/ .next/ build/ 2>/dev/null

Step 2: Check how your environment variables are named

This is the most common way the key ends up public. Frameworks ship any variable with a public prefix straight into the browser:

  • Next.js: NEXT_PUBLIC_
  • Vite: VITE_
  • Expo: EXPO_PUBLIC_

So NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY is, by definition, public. Search your .env files and your hosting provider's settings for any secret key stored under one of these prefixes.

Step 3: Check your repository history

Deleting a committed .env file doesn't remove it from history. Search the full history:

git log -p --all -S "sb_secret_" -- . | head -50
git log -p --all -S "service_role" -- . | head -50

If the repository is or ever was public, assume the key was seen.

Step 4: If you found it, rotate in the right order

Supabase's docs lay out the order, and it matters: "Fix the root cause of the leak before you rotate anything." Otherwise the new key leaks the same way.

  1. Fix the cause. Move secret-key usage to the server: an edge function, an API route or your backend. Rename the environment variable so it has no public prefix.
  2. Create a new secret key in the dashboard under API keys.
  3. Replace the old key everywhere it's used: servers, edge functions, CI, hosting settings. Deploy and confirm everything works.
  4. Delete the old secret key. Supabase's docs warn this "can't be undone", which is why it comes last. For a legacy service_role key, you deactivate the legacy keys in the same section instead.

Step 5: Find out whether it was used

Rotation stops future use. It doesn't tell you what already happened. Look through your database and API logs in the dashboard for requests you don't recognise around the time the key was exposed, and check for data that changed unexpectedly. If user data may have been accessed, you may have notification duties under laws like GDPR, so it's worth talking to whoever handles legal for you early.

Make sure it can't happen again

Once the key is safe, check the rest of your setup. Our Supabase RLS checklist covers the twelve things we review first on every project, and the free exposure checker shows what your public key can read right now.

If you want the whole thing reviewed by hand, including your bundle, policies, functions and storage, that's our premium scan. If we find no high or critical issues, you don't pay.