Vigia › Help › Supabase service_role key exposed

Supabase service_role key exposed? What to do now

Published 25 October 2026 · About 6 minutes · For apps built with Lovable, Bolt, Cursor or by hand on Supabase

Short answer: Treat it as leaked. A Supabase secret key, or the legacy service_role key, "bypasses every Row Level Security policy you have", and anyone who loaded your site may have a copy. Fix the cause, create a new secret key in Settings → API Keys, replace the old key everywhere, confirm, then delete the leaked secret key or deactivate the legacy keys.

Vigia publishes this page and sells an automated check for this problem (see the end). It is not a security audit or a guarantee.

On this page: Why it matters · How it happens · How to check yourself · How to fix it · Prompts for Lovable, Bolt and Cursor · FAQ · How Vigia checks this · Sources

Why it matters

Supabase's docs: secret keys "provide full access to your project's data, bypassing Row Level Security". The service_role key is the legacy version of a secret key. "Never put one in a browser, a shipped application, or source control."

How it happens

How to check yourself

  1. Search the live site. Open DevTools → Sources and search all files for sb_secret_. Any match in browser code is a leak.
  2. Look for legacy keys. Legacy keys are long strings starting with eyJ. Your anon key looks like that too, so you need to decode each one to see whether its role is service_role.
  3. Check your variables. No secret key should sit behind a VITE_ or NEXT_PUBLIC_ prefix.

How to fix it

Supabase's procedure for a leaked secret key or a leaked service_role key:

  1. Fix the root cause first. "Fix the root cause of the leak before you rotate anything." Move every call that needs the key to server-side code (an API route or an Edge Function) and use only the publishable key in the browser.
  2. Create a new secret key in Settings → API Keys. If your project only has legacy keys, creating the new keys is safe: they work alongside the legacy keys.
  3. Replace the compromised key everywhere your application uses it. A leaked service_role key is replaced by the new secret key.
  4. Confirm every component now uses the new key, including the easy-to-miss ones: CI/CD, cron jobs, webhooks and app versions already in users' hands.
  5. Retire the leaked key. Delete a secret key (this can't be undone, so do step 4 first). For a legacy anon or service_role key, deactivate the legacy keys in the same section. That is reversible if you find a client you missed.

Note: new secret keys return HTTP 401 when used from a browser. Supabase is clear that this is not a fix: "an attacker can still use the key from other tools."

Prompts for Lovable, Bolt and Cursor

Find every place the Supabase secret or service_role key is used in client-side code or in a VITE_ or NEXT_PUBLIC_ variable. Move each of those calls to a server-side function (an API route or a Supabase Edge Function) that reads the key from a server-only environment variable and checks who is calling. Use only the publishable key in the browser. If a query only worked with the secret key, write the RLS policy it needs instead.

FAQ

I removed the key from my code. Am I safe now?

No. Anyone who loaded your site may already have it. Create a new key, replace it everywhere, and delete or deactivate the old one.

Can I just rotate the legacy service_role key?

Supabase's procedure replaces a leaked service_role key with a new secret key and then deactivates the legacy keys. Supabase is deprecating the anon and service_role keys by the end of 2026.

Will deactivating the legacy keys break my app?

Anything still using the legacy anon or service_role key will stop working, so move every client to the publishable and secret keys first. Deactivation is reversible.

New secret keys don't work in a browser. Doesn't that protect me?

No. The browser block returns HTTP 401, but a leaked key still works from other tools. Delete it.

Is my anon or publishable key a leak too?

No, it is meant to be public, as long as RLS is on for every table. See Supabase RLS disabled?

How Vigia checks this

Vigia is a read-only check for apps you own. It downloads your home page and up to 6 same-origin JavaScript files linked from it (<script src> and module preloads; scripts from a CDN or subdomain aren't checked). It also requests /.env, /.env.local, /.env.production, /.git/HEAD and /.git/config. It reports sb_secret_ keys and Supabase JWTs with the service_role role as critical, with a fix prompt for Cursor, Lovable or Bolt: keep it server-only and rotate it. It asks you to prove you own the site (a token file or meta tag) before it runs the check. Only the browser tab that proved it can run the check, for 24 hours; after that, you prove it again with a new token. It masks every value it shows (first and last 4 characters at most), including the start of an exposed .git file.

Honest limits: it does not run your app or log in, so keys in scripts that load later or behind a login can be missed. It can't tell whether a key is still active, so rotate it either way. A clean report is not a security guarantee.

The free check is available now. Vigia Pro (€12/month or €120/year, VAT included) re-checks up to 3 apps every week and emails you what changed. Run the free check

Sources

Supabase, Vite, Next.js, Lovable, Cursor and Bolt are trademarks of their respective owners. Vigia is not affiliated with or endorsed by them.

Last reviewed 25 October 2026 against the official docs listed above.