service_role vs anon Key: Which One Actually Ends Your Company
service_role vs anon key in Supabase: the anon key is safe in the browser, but a leaked service_role key bypasses all RLS. How to tell them apart and what to do.
Supabase gives your project two API keys, and the difference between them is the difference between "we're fine" and "we have a serious problem." One of them is meant to be public and lives happily in your browser. The other one can read, change, and delete every row in your database, no questions asked — and if it ever leaks, that's the kind of incident that can end a company.
If a scanner flagged a "Supabase service role key exposed," or you want to be sure you haven't shipped the dangerous one by accident, this post is for you. We'll show you how to tell the two keys apart, where each one belongs, and exactly what to do if the wrong one got out.
⚡ TL;DR
- The anon key is public by design. It's supposed to live in your frontend, and seeing it in your browser is not a leak. Your security comes from RLS policies, not from hiding this key.
- The service_role key bypasses Row Level Security entirely. It ignores every policy and can touch every row. It must never appear in client code, a public repo, or anything a browser can reach.
- You can tell them apart by the
roleclaim inside the key, and you can fix a leaked service_role key by rotating it immediately and auditing where it was used.
Two keys, two completely different jobs
Every Supabase project ships with two primary API keys. They look similar — both are long strings called JWTs (JSON Web Tokens, a standard token format) — but they have opposite purposes.
The anon key ("anonymous") is the public one. Your app embeds it in the browser so the frontend can talk to your database. It's designed to be seen by everyone who loads your site. Critically, the anon key respects Row Level Security (RLS) — the per-user rules on your tables that decide who can see which rows. When a request comes in with the anon key, the database still checks those policies and only returns rows the visitor is allowed to see. (New to RLS? Start with Supabase RLS explained.)
The service_role key is the powerful one. It's meant for trusted server-side code that needs full access — admin scripts, background jobs, server functions. The defining property of the service_role key is that it bypasses RLS completely. Every policy you carefully wrote is ignored. A request with this key can read, update, and delete every row in every table, as if no security existed.
That's the whole story in one line: the anon key plays by the rules; the service_role key is allowed to break all of them.
Why a leaked service_role key is the emergency
Put those two facts together and you can see why one of these keys is a shrug and the other is a five-alarm fire.
If your anon key is visible in the browser, an attacker who grabs it still has to get past your RLS policies. With good policies in place, they can only see what any normal visitor can see. The key alone gets them nothing extra. That's the system working exactly as intended.
If your service_role key leaks, RLS is irrelevant. The attacker holds a master key that ignores every lock in the building. They can dump your entire users table, read every private message, alter records, or wipe data — and your policies do nothing to stop them, because the service_role key was built to ignore policies. There is no "but my RLS will catch it" safety net here. This is why "Supabase service role key exposed" is the alert you act on within the hour, not the week.
So when you read a scanner report, the first question is always: which key is this? A flagged anon key is usually noise. A flagged service_role key is the real thing.
How to tell the two keys apart
You don't have to guess. There are two reliable ways to identify which key you're looking at.
1. Read the role claim inside the key. A Supabase API key is a JWT, and you can decode it without any secret. Paste the key into a JWT decoder (or have your AI builder decode it) and look at the payload. You'll see a role field:
{
"role": "anon", // ← safe, public, respects RLS
"iss": "supabase",
"ref": "your-project-ref"
}
{
"role": "service_role", // ← dangerous, server-only, bypasses RLS
"iss": "supabase",
"ref": "your-project-ref"
}
If the role says service_role, that's the one that must never be public. (Newer Supabase projects may use a different key format with a sb_secret_… prefix for the secret key, but the principle is identical: the secret/service key is the one that bypasses RLS.)
2. Look at where it appears. The anon key belongs in frontend code, environment variables exposed to the client, and your built JavaScript bundle. The service_role key should appear only in places a browser can never reach — server-side environment variables, a backend, or a Supabase Edge Function. If you find the service_role key anywhere in your client code, your shipped JS bundle, or a committed .env file in a public repo, you have a leak.
Where each key belongs
Here's the simple rule of thumb to keep straight:
- anon key → frontend, browser, client-side environment variables. Safe to ship. Public by design. Protected by RLS.
- service_role key → server-side only. Edge Functions, a backend server, trusted admin scripts. Never in client code, never in a public repository, never in anything that gets sent to a browser.
The mistake AI builders sometimes make is reaching for the service_role key to "make a feature work" when the anon key plus a proper RLS policy would have done the job. If your AI wired a feature to the service_role key and that code runs in the browser, the key is now public. The right fix is almost never "hide the key better" — it's to move that logic to the server, or to use the anon key with the correct policy.
How to check your own app
You can audit this in a few minutes.
- Find every place you use a Supabase key. Search your codebase for your project URL and for
service_role,SUPABASE_SERVICE, andSUPABASE_KEY. Note each spot. - Decode each key's
roleclaim using the method above, so you know which is which rather than guessing by variable name (names lie — a variable calledSUPABASE_KEYcould hold either). - Open your live app's JavaScript bundle. In your browser, view the page source or the network tab and search the loaded scripts for the service_role key's value. If it's there, it's public and you need to rotate now. (Attackers do exactly this — see exposed secrets & API keys in frontend code for how they pull keys out of bundles.)
- Check your repository history, not only the current files. A key that was committed once and "removed" later still lives in the git history and is recoverable.
Not sure if your app has this exact issue?
Run a free, read-only scan of your live app — no install, results in under a minute.
Scan my app free →If your service_role key leaked — do this now
If you confirm the service_role key is exposed, treat it as a live incident and work in this order.
- Rotate it immediately. In the Supabase dashboard, regenerate the service_role key (Project Settings → API). This invalidates the leaked key so it can no longer be used. Do this first, before anything else — every minute the old key is live is a minute an attacker can use it.
- Update your trusted server code with the new key. Only your server-side code and Edge Functions should need it, so the number of places to update should be small. If you find yourself updating frontend code, that code should not have had the key in the first place.
- Audit usage. Check your Supabase logs for unusual activity around the time of exposure — large reads, deletes, or requests from unfamiliar locations. The service_role key bypasses RLS, so assume the attacker could have touched any table, and review your most sensitive data (users, payments, private content) for tampering.
- Move the logic off the client for good. Rotating the key fixes the immediate leak, but if the architecture still calls for the service_role key in the browser, you'll only leak the new one. Relocate that work to a server function so the key never ships to a client again.
This same rotate-then-audit sequence applies to any leaked secret. The principle is always: stop the bleeding first (rotate), then figure out the damage (audit), then prevent a repeat (monitor and re-architect).
FAQ
Is it safe that my Supabase anon key is in the browser?
Yes. The anon key is public by design and is meant to be embedded in your frontend. It respects your RLS policies, so an attacker who has it can only access what your policies already allow any visitor to access. Your real protection is the RLS policies on your tables, not the secrecy of this key.
What exactly does "bypasses RLS" mean for the service_role key?
It means the database does not apply your Row Level Security policies to requests made with that key. Normally RLS filters every query so each user only sees their own rows. The service_role key turns that filtering off entirely, returning and modifying any row in any table. That's why it's safe only in trusted server code and catastrophic in the browser.
Do I need to rotate my anon key if it's exposed?
No. An exposed anon key is not a security incident — it's the expected state. Rotating it changes nothing because the new key is equally public. Spend that energy confirming your RLS policies are correct and that your service_role key is not exposed anywhere.
The bottom line
Two keys, two worlds. The anon key is public by design and protected by RLS — seeing it in your browser is fine. The service_role key bypasses RLS entirely, so it must never leave trusted server code; if it leaks, rotate it within the hour and audit what it touched. Knowing which key is which is half the battle, because it tells you when to stay calm and when to move fast. An attacker scanning JavaScript bundles can find an exposed service_role key in minutes — the point is to find it first, and to keep checking, because every new feature your AI ships is a new chance for the wrong key to slip into the wrong place.
Find your gaps before an attacker does.
Is My Site Hackable? scans your deployed app for the exact issues in this article — exposed keys, missing RLS, open buckets — and tells you what's real and what's a false alarm.
Run a free scan →