PreflightHow it worksPricingSampleGuides

Leaked your Supabase service_role key?

This key skips every RLS policy, so treat a leak as urgent, but not as a reason to rush. Here is the order that avoids breaking your app while you fix it.

Free scan: GET and HEAD requests only, after you verify you own the domain. The two-account cross-user test is the $29 Proof Report. Updated 25 Oct 2026

In short

  • ✓A service_role JWT or sb_secret_ key can read and write every table, bypassing RLS. Anyone who has it owns your data.
  • ✓First confirm what leaked and where. Close the source, then rotate. Supabase’s own runbook says to fix the root cause before rotating.
  • ✓New secret keys rotate one at a time. A leaked legacy service_role key is retired by moving to the new keys and deactivating the legacy pair.
  • ✓Then check what was touched: users, rows, storage and logs. Deleting the commit does not remove the key from git history.

01Step 1: confirm it is really the service_role key

Many “leaked key” alerts are the anon or publishable key, which is meant to be public. Check before you change anything.

  • Starts with sb_secret_: a secret key. Leaked.
  • Starts with sb_publishable_: the public key. Not a leak.
  • Starts with eyJ: a legacy JWT. Decode the middle part to see its role.
// paste into a browser console; the key never leaves your machine
const k = 'PASTE_THE_KEY';
JSON.parse(atob(k.split('.')[1].replace(/-/g, '+').replace(/_/g, '/'))).role
// "service_role" → leaked.  "anon" → public by design.

Supabase’s API key docs are blunt: a secret key bypasses every RLS policy and must never sit in a browser, a shipped app or source control.

02Step 2: find where it leaked, and close it first

Rotating while the leak is still open just leaks the new key. The usual sources, most common first in apps built with AI tools:

SourceHow to check
Public env prefixA variable like VITE_SUPABASE_SERVICE_ROLE_KEY or NEXT_PUBLIC_…. Build tools inline these into the bundle.
Deployed JavaScriptDevTools → Sources on your live site, search for sb_secret_ and eyJ, decode each JWT.
Git historygit log -p -S 'sb_secret_' and the same for the first characters of the JWT.
Logs and errorsServer logs, error trackers and CI output that printed env vars or request headers.
Chat and ticketsKeys pasted into an AI chat, a support ticket or a shared doc.

If the key is in the frontend, the fix is architectural: move the code that needed it into a server route or Edge Function, and make the browser use the publishable key with RLS. Redeploy before you rotate.

03Step 3: rotate without taking the app down

If an sb_secret_ key leaked, follow Supabase’s runbook:

  1. Create a new secret key in Settings → API Keys. Use one key per backend component, so a future leak only needs one rotation.
  2. Replace the leaked key everywhere: server env vars, Edge Function secrets, CI, cron jobs.
  3. Confirm every component works on the new key.
  4. Delete the leaked key. Deletion can’t be undone, which is why step 3 comes first.

If a legacy service_role JWT leaked, you can’t rotate it on its own. The legacy anon and service_role keys stand or fall together, so:

  1. Create a publishable key and a secret key.
  2. Switch your frontend to the publishable key and your backend to the secret key. Redeploy both.
  3. Deactivate the legacy keys in Settings → API Keys. This is reversible, so you can re-enable them if you find a client you missed.

Skipping the frontend switch is the classic mistake: deactivating the legacy pair also kills the anon key your app ships, and every page that reads data breaks.

04Step 4: check what someone could have done

With the key, an attacker could read and change any row, manage users through the Auth admin API, and read or delete storage. Look for traces:

-- users created or changed recently
select id, email, created_at, last_sign_in_at
from auth.users
where created_at > now() - interval '30 days'
order by created_at desc;

-- quick row counts per table, compare with what you expect
select relname, n_live_tup, n_tup_ins, n_tup_upd, n_tup_del
from pg_stat_user_tables
order by n_tup_del desc;
  • Open the API logs in the dashboard (Logs → API Gateway) and filter for requests made with the service role around the leak window.
  • Check storage buckets for new, missing or replaced files.
  • Look for new database functions, triggers or policies you didn’t write: select proname from pg_proc join pg_namespace n on n.oid = pronamespace where nspname = 'public';
  • If personal data may have been read, check your obligations to notify users and regulators where you operate.

05Step 5: clean up and stop it shipping again

  • Purge history. A later commit that deletes the key leaves it in history. Use git filter-repo or BFG, force-push, and ask collaborators to re-clone. Rotation is what actually protects you; the purge stops the next scanner from finding it.
  • Never prefix a secret. Name server-only variables without VITE_, NEXT_PUBLIC_ or EXPO_PUBLIC_.
  • Prefer the new secret keys. Supabase returns 401 when a secret key is used from a browser user agent, which catches some mistakes. It does not stop an attacker using the key from a script.
  • Scan the build, not just the repo. Repo secret scanning misses keys injected at build time. Check the deployed JavaScript after every release.

06What Preflight checks

The free scan downloads the JavaScript your live site serves and looks for sb_secret_ keys and any JWT whose role is service_role, plus secret keys for OpenAI, Anthropic, Stripe, Resend, AWS, GitHub and others. A hit is flagged as critical with the file it was found in. It does not read your git history, logs or server env, so it confirms the leak is gone from what visitors download, not that the key was never exposed. See a sample report.

?Questions people ask

  • Is a leaked Supabase service_role key serious?

    Yes. It bypasses Row Level Security, so whoever has it can read, change and delete every row, manage users and access storage. Treat it as an incident.
  • Can I just regenerate the service_role key?

    Not on its own. Legacy anon and service_role keys are retired together by deactivating them, after you move your app to a publishable key and a new secret key. New secret keys can be created and deleted one at a time.
  • I deleted the commit. Am I safe?

    No. The key stays in git history and in any clone or fork. Rotate the key first, then purge history.
  • Why is my service_role key in my Lovable or Bolt.new app?

    Usually an environment variable with a public prefix, or code that the AI tool wrote to bypass an RLS error. Move that code into an Edge Function and fix the policy instead.
  • Does rotating the key log out my users?

    Rotating the secret key does not. Deactivating legacy keys breaks any client still sending the old anon key, which is why the frontend switches to the publishable key first.

What an outside scan can't tell you

Can one user read another user's data?

Preflight signs in as two test accounts you create and tries to read one account's rows and files from the other. That cross-user test is the $29 Proof Report for Supabase apps. Start with the free scan: it checks your live app from outside with GET and HEAD requests only.