You built an app with Cursor or Claude Code on top of Supabase. Then someone opens the browser's developer tools, finds a long key in your code, and tells you it's "leaked". Is it?
Probably not. But the real answer depends on which key it is, and on one database setting that matters far more than the key. Here's the plain-English version.
Supabase has two kinds of keys
Supabase gives every project a public key and a secret key. Newer projects use new names and formats, and Supabase says it is deprecating the old anon and service_role keys by the end of 2026.
| Old name | New name | Starts with | Safe in the browser? | |
|---|---|---|---|---|
| Public key | anon |
publishable key | sb_publishable_ |
Yes, it's designed for it |
| Secret key | service_role |
secret key | sb_secret_ |
Never |
The public key is meant to sit in your app's browser code. It identifies your project. On its own it's not a password, and anyone using your app can see it.
The secret key is different. Supabase's docs are blunt: a secret key bypasses every Row Level Security policy you have. Never put one in a browser, a shipped app, or source control. It belongs only on a server.
How to tell which key you're looking at
- New keys: read the start.
sb_publishable_is public.sb_secret_is secret. - Old keys: both are long tokens starting with
eyJand look almost identical. The difference is the "role" written inside the token:anonorservice_role. You can ask your AI to decode the middle section of the token and tell you the role, without pasting the key anywhere public.
If the key in your browser code is the publishable or anon key, that's expected. Breathe.
The thing that actually protects your data: Row Level Security
Here's the catch. The public key is only safe if your database tables are locked. Supabase does this with Row Level Security (RLS): rules on each table that decide who can read, add, change or delete each row.
Supabase's own docs explain what happens without it: if RLS is off on a table in the public schema, anyone with your project URL can read, edit and delete all the data in that table. Its built-in checker flags this as an error called "RLS Disabled in Public".
This is not a rare edge case. In August 2026, Reeve (a company that sells an app security scanner) scanned 30,998 live apps built with AI app builders and published the results. They found the Supabase service_role key in only 3 apps. But of 8,429 apps using Supabase, at least 2,096 had a table that answered requests from someone who wasn't logged in. Those are their numbers, not ours, but the lesson is clear: the open table is the common problem, not the leaked key.
Check your project in 10 minutes
- Open Security Advisor. In your Supabase dashboard go to Database → Security Advisor. It runs automatically. Fix anything marked as an error first, especially "RLS Disabled in Public".
- Turn RLS on for every table. Tables created in the Supabase dashboard have RLS on by default. Tables created with SQL may not. The fix is one line per table:
alter table public.your_table enable row level security; - Write a policy for each action. After RLS is on, nothing gets through with the public key until you add policies. A common "owner only" pattern from Supabase's docs checks that
(select auth.uid()) = user_id. - Don't "fix" an empty app with
using (true). When RLS goes on, your app may suddenly show no data. The tempting fix is a policy that allows everyone. On private data, that undoes the whole point. - Test with two accounts. Sign in as user A in a normal window and user B in a private window. User A should never see user B's data.
What if your secret key really did leak?
Deleting it from your code is not enough. The old value still sits in your git history and possibly in copies of your site. Do this instead:
- In Supabase, go to Settings → API Keys and create a new secret key.
- Replace the old key everywhere your server uses it (for example, in your hosting provider's environment variables).
- Check that everything works with the new key.
- Delete the leaked secret key (or, for a legacy
service_rolekey, deactivate it). - Check your database and billing for activity you don't recognize.
Let your AI do the first pass
Paste this into Cursor or Claude Code to review your tables:
Review every table in my database setup ([WHERE IT LIVES, E.G.
"supabase/migrations"]). For each table, tell me in plain English: is
Row Level Security ON, and who can read, create, update and delete rows?
Flag any table with RLS off, any policy using "true" on private data, and
any table missing a policy for an action. Suggest fixes but don't apply
them until I say "go".
The AI can miss things, so treat its answer as a first pass and still run the two-account test.
Keep it from happening in the first place
Our free starter pack includes two rules that help here: one keeps secrets in a git-ignored .env.local file and out of public variables, and one tells your AI to give every Supabase table RLS with a policy for each action. It also has a one-page launch checklist. No card needed.
Want the security prompts ready to go? The Vibe Coder Prompt Pack ($9) has 15 security prompts, including a leaked-secret scan, an RLS review and a step-by-step "prove one user can't see another user's data" test. For the full setup, the Ship-Safe Starter Kit ($19) adds a dedicated Supabase safety rule for Cursor and Claude Code and a 12-section pre-deploy security checklist.
The short version
- The publishable (
anon) key is supposed to be public. - The secret (
service_role) key must never be public. If it leaks, make a new one and delete the old one. - RLS on every table, with real policies, is what keeps your users' data private.
Vibe Shipped is independent and not affiliated with Supabase, Cursor (Anysphere, Inc.) or Anthropic, PBC. This is general guidance, not a professional security audit. Written with AI assistance and reviewed by a human.
Want these rules ready-made?
The free starter pack has them, set up for Cursor and Claude Code.