Dev.to WebDev 🛠 Dev 👁 0 📖 3 min read

Is the Supabase anon key safe to expose?

Practical checklist from ShipSafeStack. Not a penetration test, compliance audit, score, or proof that a project is safe. Informational only — no guarantees. Short answer: Supabase’s docs say the anon key, and the newer

Practical checklist from ShipSafeStack. Not a penetration test, compliance audit, score, or proof that a project is safe. Informational only — no guarantees.

Short answer: Supabase’s docs say the anon key, and the newer publishable key, are meant to be in the browser. They are not a secret. That only holds when row level security is on and the grants are tight. The service_role key and the newer secret key (sb_secret_…) bypass RLS and do not belong in the client.

If RLS is off, or grants are wide open, the anon key is a public handle on a public table. Setup steps: how to set up Supabase RLS for an AI-built app. After policies exist: RLS checklist.

Which key is which

Supabase’s API key docs describe four keys — two for code you ship, two that are elevated:

Key Format What Supabase says
Publishable key sb_publishable_… Low privilege. OK in a web page or mobile app. Only reaches what RLS allows.
Anon key Long JWT Legacy publishable key. Same idea: identifies the project; not a secret.
Secret key sb_secret_… Elevated. Bypasses RLS. Server / Edge Function / backend you control.
Service role key Long JWT Legacy secret key. Same bypass. Prefer a secret key where you can.

A publishable key with no signed-in user acts as the anon Postgres role. With a signed-in user it acts as authenticated. A secret/service_role key bypasses RLS — policies for anon / authenticated do not apply to it.

Creating a publishable or secret key does not revoke the legacy keys until you disable them under Settings → API Keys.

How to tell an anon JWT from a service_role JWT

Both legacy keys are JWTs, so both can start with eyJ. The prefix is not the difference — the payload is.

A JWT is three base64url segments. The middle segment is the payload (encoded, not encrypted). Decode that segment on your own machine and read the role field:

  • role of anon → anon key (expected in the client when RLS is correct)
  • role of service_role → service role key (never in the client, VITE_ / NEXT_PUBLIC_, or a public repo)
  • sb_publishable_… → newer public key; sb_secret_… → newer secret key

Do not paste live keys into a random website to decode them.

ShipSafeStack’s free passive check reports an anon JWT in public scripts as information; it does not treat that alone as a failure. A service_role JWT in public scripts is a different problem.

When the anon key is the wrong thing to worry about

Worry first about:

  • RLS disabled or missing on tables the client can reach
  • Policies that only check “signed in” without scoping rows to that user
  • Default grants that are wider than your policies
  • A service role / secret key that also shipped in the browser, repo, or chat

If the service role key leaked

Treat it as compromised. Remove it from the client first, put a new secret only on the server, rotate at Supabase, and review logs/data. Full steps: Supabase service role key exposed: what to do now.

FAQ (condensed)

  • Can the anon key sit in a public GitHub repo? Supabase treats it as non-secret; the risk is still your RLS and grants, plus accidentally committing the service role beside it.
  • Both eyJ keys shipped? Decode payloads locally; rotate and remove the service_role one immediately; keep anon only if RLS is correct.
  • Does enabling RLS protect tables the service role reads? No — service role bypasses RLS.
  • Lovable / Bolt: public Supabase URL + anon/publishable values often go in client env (VITE_…); service role belongs in server Secrets / Edge Functions, not in the bundle.

Check a public URL (passive, free): shipsafestack.com/scan — ordinary GETs only; nothing stored; informational only, not an audit.

Original: Is the Supabase anon key safe to expose?

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.