Abbi Labs

Guides

Supabase service_role key exposed? Do this in the next 15 minutes

By Pritesh Ghodge, security engineer · Last updated 23 October 2026

If your Supabase service_role key (or a newer sb_secret_ key) is sitting in your site's JavaScript, a public repo or a leaked env file, treat it as compromised. This is the one Supabase key that matters most. Here's what to do, in order.

Why this key is different

The anon (or sb_publishable_) key in your front end is meant to be public. Row Level Security (RLS) decides what it can see.

The service_role key and sb_secret_ keys skip RLS completely. Anyone holding one can read, change or delete every row in your database, whatever your policies say. They're meant for servers and edge functions only.

The usual way it ends up in the browser: an AI agent hits a "permission denied" error from RLS while building, swaps in the service_role key to make it go away, and puts it in a VITE_ or NEXT_PUBLIC_ variable. Anything with those prefixes is built into the JavaScript every visitor downloads.

First, confirm what you're looking at

Open your live site, then DevTools → Sources, and search all files (Ctrl+Shift+F, or Cmd+Option+F on a Mac) for service_role, sb_secret_ and eyJ.

Also check .js.map files if your site serves them: source maps can contain the original code, keys included.

Step 1: Replace the key without breaking your app

Supabase's own procedure for a leaked secret or service_role key is: create the replacement, switch everything over, confirm, then retire the old one. In practice:

  1. Create a new secret key in the Supabase dashboard under Settings → API Keys. On older projects you may first see a button to create the new publishable and secret keys. That's safe: it adds them next to your legacy keys without turning anything off.
  2. Remove the key from the front end. Move the code that needed it to the server (Step 3) and give that server code the new secret key.
  3. If your front end uses the legacy anon key, switch it to the publishable key in the same change. When you retire a leaked legacy service_role key, the legacy keys are deactivated together, so anything still on the old anon key would stop working.
  4. Deploy and check that sign-in, reads and writes still work, and that the old key no longer appears anywhere in your live JavaScript.
  5. Retire the old key. Delete a leaked sb_secret_ key (this can't be undone). For a legacy service_role key, deactivate the legacy keys in the same dashboard section. Deactivation can be reversed if you find a client you missed.

If the database holds sensitive data and you can't ship a fix quickly, retire the key now and accept some downtime. A broken page for an hour costs less than someone quietly copying your users table.

Step 2: Check whether anyone used it

Nobody can tell you for certain whether a public key was copied. You can look for signs it was used:

If you find signs that personal data was accessed, you may have to tell your users, and in some countries a regulator. Check what applies where you and your users are.

Step 3: Move privileged code to the server

Most apps don't need the service_role key in the request path at all. If it was added to get past an RLS error, the real fix is a policy that allows the action for the right user. See how to check and fix your RLS policies.

When you do need it (admin tasks, webhooks, background jobs), keep it in server-only code.

Next.js: keep it in an env var without the NEXT_PUBLIC_ prefix, and only read it in a Route Handler, Server Action or other server file. Importing server-only makes the build fail if a client component ever imports that file:

// lib/supabase-admin.ts
import "server-only";
import { createClient } from "@supabase/supabase-js";

export const supabaseAdmin = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SECRET_KEY! // no NEXT_PUBLIC_ prefix
);

Check every request that uses it on the server: who is signed in, and whether they're allowed to do this. The key itself skips all of those checks.

Lovable, Bolt and other Vite apps: there's no server in the browser bundle, so privileged calls belong in a Supabase Edge Function, with the key stored as a function secret. A prompt you can paste:

Security fix: the Supabase service_role / secret key must never reach the browser.
1. Remove every use of it from client code and from any VITE_ variable.
2. For each place that used it, either fix the RLS policy so the normal
   client can do this for the signed-in user, or move the call into an
   Edge Function that checks the user's session and reads the key from
   a function secret.
3. Show me the diff and the list of places you changed before applying.

Step 4: Clean up git and redeploy

If the key was ever committed, it's in your git history even after you delete it. On a public repo, assume it's been seen. Replacing the key (Step 1) is what actually fixes this; rewriting history is optional tidying. To find old secrets, run a scanner such as gitleaks over the whole history:

gitleaks git . -v

Then add .env* to .gitignore, redeploy, and search your live JavaScript once more to confirm the old key is gone.

Stop it happening again

Abbi Labs does that last part for you. On domains you've verified you own, it scans your home page, its JavaScript files and any public source maps for Supabase service_role and sb_secret_ keys and other secrets (Stripe, OpenAI, Anthropic, AWS, GitHub and more). It also probes common tables with your public key and checks for exposed .env and .git files. The first scan is free, and Monitor re-scans weekly and after every deploy.

Run a free scan

FAQ

Is my Supabase anon key a leak too?

No. The anon key and the newer sb_publishable_ key are designed to be public and are expected in your front-end code. Row Level Security decides what they can read. Only the service_role key and sb_secret_ keys bypass RLS and must stay on the server.

How do I tell if the key in my bundle is anon or service_role?

New keys say what they are: sb_publishable_ is public, sb_secret_ is secret. Legacy keys are long JWTs starting with eyJ. Paste one into a JWT decoder and read the role field in the payload: "anon" is fine, "service_role" is the one to replace.

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

No. If it was ever deployed or pushed to a public repo, assume someone copied it. Deleting it from the code doesn't revoke it. Only retiring the key in Supabase does.

Will replacing the key break my app?

It can, if something still uses the old key when you retire it. Create the new key first, switch every server-side use over, deploy, confirm it works, and only then retire the old one. Legacy keys are deactivated rather than deleted, so you can switch them back on if you missed something.

Does Abbi Labs check for this?

Yes. On domains you've verified you own, it reads your home page, the same-origin JavaScript files it links to and any public source maps, decodes any Supabase JWTs it finds, and flags service_role keys and sb_secret_ keys as critical. Reports show only a masked version of the key.

Related guides

Weekly AI-app security checklist

One short email a week: a practical check for apps built with Supabase, Next.js and AI tools (RLS, leaked keys, headers and more). Unsubscribe anytime.