The Supabase service_role key: what it is and how to keep it safe
Every Supabase project ships with two API keys, and one of them is the most dangerous string in your entire stack. The service_role key bypasses every security rule you've set and hands over full control of your database. Here's exactly what it does, where it leaks, and how to make sure yours isn't sitting somewhere it shouldn't.
What the service_role key is
Supabase gives you two keys: the public anon key and the secret service_role key. The difference is everything. The anon key respects Row Level Security (RLS) — the policies that decide who can read and write each row. The service_role key ignores RLS entirely. It's the admin key: any request made with it has full, unrestricted read and write access to every table in your database.
That's by design — it exists so your server can do trusted work (background jobs, admin tasks, migrations) without being blocked by RLS. The problem is only what happens when it escapes the server.
The service_role key must only ever live on a server — never in your frontend, never in a mobile app, never in a public repo. Anyone who has it can read, edit or delete your entire database, RLS or not.
Why an exposed service_role key is catastrophic
With a leaked anon key and RLS off, an attacker can read whatever your policies (don't) protect. With a leaked service_role key, none of that matters — RLS is simply skipped. They can dump every table, modify records, or delete your whole database. There's no policy that saves you, because the key's entire purpose is to bypass policies. It is the single worst credential to leak in a Supabase app.
Where the service_role key leaks
It's supposed to stay server-side, so every leak is a case of it ending up somewhere public:
- Pasted into frontend code — used in a client component to "make an admin action work," then shipped to the browser in the JavaScript bundle.
- Wrong environment variable — exposed to the client build (for example a
NEXT_PUBLIC_/VITE_prefixed variable, which is bundled into public files). - Committed to a public repo — left in a
.env, a config file, or Git history for bots to scrape. - Hard-coded by an AI builder — tools like Lovable or Bolt sometimes reach for the wrong key when wiring up an action.
How to check if your service_role key is exposed
A quick manual check:
- Open your live app, then your browser's DevTools → Sources (or "view source"), and search the files for the string
service_roleand for longeyJ...JWT tokens. Theservice_rolekey is a JWT whose decoded payload contains"role":"service_role". - Check the Network tab while using your app — a request carrying that key in a header or body is exposing it.
- If your code is on GitHub, search the repo and its history for
service_role.
Both keys are long eyJ... tokens, so they look alike. Decode the JWT (paste it into any JWT viewer) and read the role claim: anon is safe to be public; service_role is not. We cover this in depth in Exposed Supabase keys: anon vs service_role.
Not sure if your key is exposed?
Paste your app's URL and our free scan checks it from the outside in about 10 seconds — including whether a secret key is reachable in your frontend.
Only scan apps you own or have permission for.What to do if it's exposed
- Rotate it immediately. In the Supabase dashboard, roll the
service_rolekey and treat the old one as fully compromised — assume a bot already has it. - Move it server-side. It belongs only in server code: an API route, an Edge Function, or a server-only environment variable. Your frontend should call your server, which holds the key.
- Use the anon key + RLS on the client. Anything the browser needs should go through the public
anonkey with Row Level Security enabled — never theservice_rolekey. - Re-check. After rotating and moving it, scan again to confirm nothing still exposes it.