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
- Fixed, or we keep working free until it is (within the written scope).
- A human reviews every fix before it ships.
- read-only access; fixes arrive as pull requests you merge
- Your $149 diagnosis is credited in full toward any paid job.
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.