All checks

Supabase service_role key exposed in the browser: what it means and how to fix it

Severity: CriticalQuick + Deep· 2 min read· Last updated

SHORT ANSWER

Supabase gives you two kinds of keys. The anon / publishable key is meant to be in your frontend; Row Level Security (RLS) decides what it can do. The service_role / secret key skips RLS completely and can do anything to your database. If the service_role key ends up in browser code, anyone who opens DevTools can read, edit or delete all of your data. Rotate it now, then move every use of it to server-side code.

How it ends up in the browser

  • An environment variable with a public prefix: NEXT_PUBLIC_, VITE_, REACT_APP_, EXPO_PUBLIC_ or PUBLIC_. Anything with these prefixes is bundled into the JavaScript your users download.
  • Creating the Supabase client with the service_role key "because RLS was blocking my query". An AI tool may suggest this as a quick fix.
  • The key committed to git, even if it was deleted later. It's still in the history.

How to check your app

  1. Search your code and .env files for service_role, SERVICE_ROLE and sb_secret_.
  2. Check every Supabase-related environment variable. If a secret one has a public prefix (NEXT_PUBLIC_, VITE_ and so on), it's exposed.
  3. Open your live site, go to DevTools → Sources (or Network), and search the JavaScript for your Supabase URL. Look at the key used next to it.
  4. Older Supabase keys are JWTs. Paste one into a JWT decoder: if the payload says "role": "service_role", it's the secret key. Newer keys say it in the prefix: sb_publishable_… is public, sb_secret_… is secret.

How to fix it

  1. Rotate the key immediately in your Supabase project's API key settings. Assume the old one was copied.
    • New-style secret keys (sb_secret_…) can be revoked one by one.
    • Older JWT keys are rotated by rolling the JWT secret, which also changes the anon key. Update both everywhere.
  2. Remove it from the frontend. Only the anon/publishable key belongs in browser code.
  3. Move admin actions to the server: an API route, a server action, or a Supabase Edge Function that reads the secret key from a server-only environment variable (no public prefix).
  4. Fix RLS instead of bypassing it. If a query fails because of RLS, write a policy that allows the right user, for example using (auth.uid() = user_id).
  5. Clean git history if the key was committed, or treat it as permanently public and rely on the rotation.
  6. Check your database for unexpected changes while the key was exposed.

FAQ

Is the Supabase anon key safe to expose?

Yes, as long as RLS is enabled on every table and your policies are correct. Without RLS, the anon key can read everything too.

Why does the service_role key bypass RLS?

It's meant for trusted server code like admin scripts, webhooks and background jobs, which need full access.

I used NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY. Is that exposed?

Yes. Anything prefixed NEXT_PUBLIC_ is shipped to every visitor's browser. Rotate the key and rename the variable without the prefix.

Can SetRepo find this?

Yes. A Quick scan finds service_role and secret keys (including sb_secret_) in your code, env files and git history. With a live URL, it also checks the JavaScript your site actually serves. We never use or test the secret keys we find, and we never show their values.

Check your app for this, free.

Start free scan