Supabase service_role key leaked? How to rotate it and recover
· 4 min read
If your Supabase service_role key (or a newer sb_secret_... key) ended up in code that runs in the browser, treat it as compromised. That key bypasses Row Level Security, so anyone who has it can read, change and delete every row in your project.
Work through the steps below in order. The first two take a few minutes and stop the damage; the rest make sure it does not happen again. For the background on the different keys, see Supabase anon key vs service_role key.
How to tell if the key really leaked
The anon and publishable keys are meant to be public. Only the privileged key is a problem. Open your live site, then Developer Tools, then the Sources or Network tab, and search the JavaScript files for service_role and sb_secret_. A match means every visitor can copy it. A key that was only ever in a server-side file, such as an API route or an Edge Function, did not leak through your site.
It also counts as leaked if it was committed to a public repository, pasted into a public chat or shared in a screenshot, even if you deleted it afterwards. Git history and caches keep it.
Step 1: rotate the key now
Create a new secret key in the Supabase dashboard under API keys, switch your server to it, then delete the old one. If your project still uses the legacy service_role JWT key, rotating the JWT secret invalidates it, along with the other legacy keys, so plan to update your other keys at the same time. Do this before anything else: until the old key is revoked, nothing else you do matters.
Step 2: look for abuse
Check the Supabase logs for the period the key was exposed. Look for requests you do not recognise, bulk reads of tables such as users or orders, unexpected deletes, new rows and new auth users. If personal data may have been read, you may have legal duties to notify users, depending on where they live. Write down what you find and when.
Step 3: move the key out of the browser
A privileged key only belongs in code that runs on a server: an Edge Function, an API route or a backend. Never put it in a variable with a NEXT_PUBLIC_, VITE_ or PUBLIC_ prefix, because those are bundled into the JavaScript every visitor downloads. The browser should use the publishable (anon) key and rely on Row Level Security.
Step 4: add Row Level Security so one key is never enough
Even with the right keys, a table without RLS lets the public key read everything. Turn it on for every table in public, and add policies that limit rows to their owner. The post on how to check that Supabase RLS is enabled has the queries.
Step 5: scan the live site
Run a free FixPrompt scan on the deployed URL. It looks for privileged key names in your public JavaScript and flags missing headers, so you can confirm the leak is gone after you redeploy.
Prompt for your coding agent
My Supabase service_role / sb_secret_ key may have been exposed in client code. In this repository:
1. Search every file, including git history notes and .env files, for service_role, sb_secret_
and any other privileged key, and list where each one is used.
2. Move every call that needs a privileged key into a server-only place (Supabase Edge Function
or API route) that reads the key from a secret, never from a NEXT_PUBLIC_, VITE_ or PUBLIC_
variable.
3. Make the browser use only the publishable/anon key.
4. Enable Row Level Security on every table in the public schema and add owner-only policies.
5. Tell me exactly which keys I must rotate in the Supabase dashboard and in what order.
Explain each change in plain English.
Check your app in minutes
Paste your URL and get a plain-English report with prioritized risks and a fix prompt for your AI. Your first scan is free.
Scan my app free