GUIDE · SUPABASE

How to check if your Supabase data is exposed

Your Supabase data is exposed if a table has Row Level Security turned off, has a policy that allows everyone (using (true)), or if a service role key is in your front-end code. You can check all three in about 15 minutes with the SQL and requests below. Only test projects you own.

1. Find tables without RLS

select tablename, rowsecurity
from pg_tables
where schemaname = 'public'
order by rowsecurity, tablename;

Any row with rowsecurity = false is readable and writable through the API by anyone with your anon key. Supabase's Security Advisor in the dashboard flags the same thing.

2. Look for policies that allow everyone

select tablename, policyname, cmd, roles, qual, with_check
from pg_policies
where schemaname = 'public'
order by tablename;

Red flags: qual or with_check equal to true, policies for the anon role on private data, and UPDATE policies without a with check.

3. Call the API as a stranger

curl 'https://YOUR-PROJECT.supabase.co/rest/v1/profiles?select=*&limit=3' \
  -H "apikey: YOUR_ANON_KEY" \
  -H "Authorization: Bearer YOUR_ANON_KEY"

If this returns rows that should be private, anyone can do the same. An empty array [] means RLS filtered them out.

4. Test as a second user

Sign in as another test account and try to read or update the first account's rows. Row ownership should come from auth.uid(), never from a user id the browser sends.

5. Search for secret keys in the browser

Open your live site, view the loaded JavaScript, and search for service_role or sb_secret_. A legacy service role key is a JWT whose payload contains "role":"service_role". If you find one: move the call to a server or Edge Function, then rotate the key in the Supabase dashboard.

How to fix what you find

alter table public.profiles enable row level security;
create policy "own profile: select" on public.profiles
  for select to authenticated using ((select auth.uid()) = id);
create policy "own profile: update" on public.profiles
  for update to authenticated
  using ((select auth.uid()) = id) with check ((select auth.uid()) = id);

Write one policy per operation. Inserts need with check. Never "fix" RLS errors by switching the browser to the service role key. Sources: Supabase RLS docs, Supabase API keys.

Get a $149 diagnosis See pricing Fix my auth & database security

FAQ

Is my anon key being visible a problem?

No, the anon or publishable key is designed to be public. The problem is tables that RLS does not protect.

I enabled RLS and now my app shows nothing. Why?

RLS denies by default. Add a policy per operation for the right role. See Supabase RLS: empty results or 42501.

What should I do if data was exposed?

Close the gap first (enable RLS, fix policies, rotate leaked keys), then review logs and consider your notification duties under the privacy laws that apply to you.

Can you fix it for me?

Yes. An auth & database security fix is from $400 (one issue); larger scope quoted up to $800 after diagnosis.

Related

Check it yourself first, free

$149Get a $149 diagnosis

FREE · LAUNCH-READY CHECKLIST

Before you go: is your app ready for real users?

Get our 40-point launch-ready checklist for AI-built apps: data access, secrets, auth, payments, deploy, email and backups. It opens straight away.

No payment. See our privacy policy.