MondayMedic

Guide

Is my Supabase key leaked? Anon key vs service_role key, explained

Check your live app first. Paste your app's public link into the free MondayMedic check. It reads only public pages and scripts, takes about a minute, needs no account, and flags a secret key in the browser or tables the public key can read. It does not log in or change data, and it is not a full security audit.

Check my app free

The short answer

How to tell which key you're looking at (one minute)

  1. Open your live app, view source or DevTools > Sources, and search for eyJ, sb_secret, sb_publishable and service_role.
  2. Legacy keys are JWTs (three parts split by dots, starting eyJ). Decode the middle part (any JWT decoder, or atob in the console) and read the role field: anon is the public key, service_role is the secret one.
  3. Newer keys say what they are in the prefix: sb_publishable_ is fine in the browser, sb_secret_ never is.
  4. Also check env var names. Anything starting with VITE_ or NEXT_PUBLIC_ gets baked into the browser bundle, so a secret key stored under one of those names is public.

If you found the service_role or sb_secret key

Do these in order. Rotate before you touch code, because the old key keeps working until you delete it.

  1. In Supabase, open Settings > API Keys and create a new secret key.
  2. Put the new key wherever it legitimately lives (Edge Functions, server, CI) and redeploy.
  3. Delete or roll the old key.
  4. Make sure the browser client only uses the anon/publishable key.
  5. Turn RLS on for every public table and add owner-only policies (see our RLS guide).
  6. Check your logs for writes you didn't make during the time the key was public.

If you only found the anon key

Don't rotate it. A new anon key is just as visible. Instead, test whether it can read things it shouldn't: log out and query each table with the anon key. If private rows come back, RLS is off or a policy is too open (for example using (true)). See the RLS guide for fixes.

Why AI builders hit this

Tools like Lovable, Bolt and Cursor wire the browser straight to Supabase. When a feature needs "admin" access, an assistant may reach for the service_role key and put it where the frontend can read it, or a key pasted into a chat ends up in code. It works, so nothing looks wrong.

FAQ

Is the Supabase anon key a secret?

No. It's meant to be in the browser. RLS is what protects data.

Can I hide the anon key behind env vars?

Env vars with VITE_/NEXT_PUBLIC_ still end up in the bundle. That's fine for the anon key and never fine for a secret key.

Does MondayMedic call the anon key a leak?

No. The free check flags secret keys and tables that answer the public key.

How do I keep it from coming back?

Re-check after every new feature. The paid Monday checkup emails you new public issues each week.

Sources