Milo Kits
Guide

Is my Supabase anon key exposed? What's safe to show and what isn't

Short answer: seeing your Supabase anon key (or the newer sb_publishable_… key) in your site's JavaScript is normal and expected. Supabase designed it to be public. What protects your data is Row Level Security (RLS): if RLS is off on a table, or a policy is too broad, anyone holding that public key can read (and often change) the table. The key you must never expose is the service_role / sb_secret_… key, because it bypasses RLS entirely.

The two kinds of Supabase key

KeyLooks likeOK in the browser?
Publishable key (new)sb_publishable_…Yes, with RLS on
anon key (legacy)A long JWT starting eyJ…Yes, with RLS on
Secret key (new)sb_secret_…Never
service_role key (legacy)A long JWT whose payload says "role":"service_role"Never

Supabase's own docs say the publishable key "is safe to expose with RLS enabled", and that the older anon key should be treated the same way: it identifies your project but isn't a secret. Secret and service role keys "are never safe to expose because they bypass RLS" (Supabase: Securing your data, API keys).

Why "the key is public" turns into a data leak

Apps built with Lovable, Bolt and similar tools usually talk to Supabase straight from the browser. That's fine as long as the database itself decides who can see which rows. When a table has no RLS, or a policy like using (true) on private data, the browser key becomes a skeleton key.

This isn't theoretical. CVE-2025-48757 describes exactly this pattern in Lovable-generated apps: insufficient RLS let unauthenticated visitors read or write tables. The researchers who reported it scanned 1,645 apps featured on Lovable's site and found 170 with inadequate RLS (researcher statement). Lovable has since added a security scan and reviewer (Lovable blog), but a scan saying "RLS enabled" doesn't tell you the policies are right.

How to check your own project (10 minutes)

  1. Make sure no secret key ships to the browser. Open your live site, open DevTools → Sources (or View Source on the JS files) and search for service_role and sb_secret_. If either shows up, skip to the fix below and rotate the key today.
  2. Check RLS is on for every table. In the Supabase dashboard, go to Database → Tables. Any table in the public schema without RLS is reachable through the API by any role that has a grant on it (Supabase RLS docs).
  3. Run the Security Advisor. Supabase's database advisors flag tables with RLS disabled and other common issues.
  4. Read each policy, not just the switch. Look for using (true) or policies granted to anon on tables that hold user data. Those let any visitor read every row.
  5. Test logged out. In a private window, with no session, query a table you expect to be private through your app or the REST API. You should get an empty list or a permission error, never someone's data.
  6. Test as a second user. Sign in as user B and try to load user A's records by changing an ID in the URL or request. You should get nothing.

Copy-paste fix prompts

If a secret / service_role key is in the frontend
A Supabase service_role / secret key is in my public frontend code. Please: 1. Remove it from all client code and from any env var with a VITE_, NEXT_PUBLIC_ or REACT_APP_ prefix. 2. Make sure the browser client only uses the anon / publishable key. 3. Move any admin operations into a server-side route or Supabase Edge Function that reads the secret key from a server-only environment variable. 4. Show me the diff.

Then rotate the secret key in the Supabase dashboard (Project Settings → API keys). Removing it from the code isn't enough, because the old one has been public.

If a table is readable without logging in
My Supabase table `[TABLE]` can be read with the anon key without logging in. Please: 1. Enable Row Level Security on `[TABLE]`. 2. Add policies so SELECT / INSERT / UPDATE / DELETE only work for the owning user (auth.uid() = user_id), or deny anon entirely if the table shouldn't be public. 3. Check every other table in the public schema and list any that still have RLS off or a `using (true)` policy on private data. 4. Give me the SQL to run in the Supabase SQL editor, plus a quick way to confirm an anonymous request now returns no rows.
To review everything at once
Review my Supabase setup for data exposure. For every table and view in the public schema, tell me: is RLS enabled, which policies exist, and which roles (anon, authenticated) can SELECT, INSERT, UPDATE or DELETE. Flag anything where a logged-out visitor, or one signed-in user, could read or change another user's rows. Then propose the minimal SQL to fix each issue. Don't run anything yet, just show me.

Things RLS doesn't cover

FAQ

Is it safe to have my Supabase anon key in my frontend code?

Yes. The anon key (and the newer sb_publishable_ key) is designed to be public. It's only safe because Row Level Security restricts what that key can do, so RLS must be enabled on every table in an exposed schema with policies that match who should see each row.

Which Supabase key must never be public?

The service_role key (legacy) and secret keys starting with sb_secret_. They bypass Row Level Security and give full access to your data. Keep them in server-side code or Edge Functions only, and rotate them immediately if they were ever in client code.

I turned RLS on. Am I safe now?

Not necessarily. With RLS on and no policies, the API returns nothing to the anon and authenticated roles. But a broad policy such as using (true) on a private table opens it right back up. Review each policy and test as a logged-out visitor and as a second user.

How do I check if my Supabase table is publicly readable?

Open a private browser window with no session and request the table through your app or the REST endpoint using only the public key. If you get rows back that should be private, the table needs RLS and tighter policies. The Supabase Security Advisor also flags tables with RLS disabled.

Do I need to rotate my anon key if it was public?

No, it's meant to be public. You do need to rotate any service_role or secret key that was ever exposed, and fix RLS on any table that was readable, because rotating the anon key won't stop someone who already copied your data.

Sources

This guide is general information, not a security audit. Platforms change, so check the linked docs for current details. Spotted something out of date? Email hello@milokits.com.

More guides