Free · 2 minutes · read-only
Is your Supabase app leaking data?
Most leaks in apps built with Lovable, Bolt or Replit come from a handful of database settings. This script checks them in your own project. You run it yourself, it only reads settings, and nothing is sent to us.
How to run it
- Open your project at supabase.com/dashboard and go to SQL Editor.
- Paste the script below (or download it) and click Run.
- Every row it returns is worth fixing. No rows means none of these common leaks.
-- Postlaunch 2-minute leak check for Supabase (read-only).
-- Paste into Supabase > SQL Editor and click Run. It only reads settings; it changes nothing.
-- Every row it returns is something worth fixing. No rows = none of these common leaks.
-- https://postlaunch.agency/leak-check
select severity, problem, item, what_to_do from (
-- 1. Tables with Row-Level Security off that visitors can read
select 1 as rank, 'critical' as severity,
'RLS is off and anyone with your public key can read this table' as problem,
format('%I.%I', n.nspname, c.relname) as item,
'Turn on RLS and add policies for who should see which rows' as what_to_do
from pg_class c join pg_namespace n on n.oid = c.relnamespace
where c.relkind in ('r', 'p') and n.nspname = 'public'
and not c.relrowsecurity
and has_table_privilege('anon', c.oid, 'SELECT')
union all
-- 2. Tables with RLS off that visitors can't read today (signed-in users or a future grant can)
select 2, 'high',
'RLS is off on this table',
format('%I.%I', n.nspname, c.relname),
'Turn on RLS before the table is exposed to your app'
from pg_class c join pg_namespace n on n.oid = c.relnamespace
where c.relkind in ('r', 'p') and n.nspname = 'public'
and not c.relrowsecurity
and not has_table_privilege('anon', c.oid, 'SELECT')
union all
-- 3. Policies that let every request through: USING (true) or WITH CHECK (true)
select 3, 'high',
'Policy lets every ' || case when 'anon' = any(p.roles) or 'public' = any(p.roles) then 'visitor' else 'signed-in user' end
|| ' ' || case p.cmd when 'SELECT' then 'read' when 'INSERT' then 'add' when 'UPDATE' then 'change'
when 'DELETE' then 'delete' else 'read and change' end || ' any row',
format('%I.%I: "%s"', p.schemaname, p.tablename, p.policyname),
'Limit the policy to the rows each user should reach, e.g. their own or their team''s'
from pg_policies p
where p.schemaname = 'public'
and (coalesce(p.qual, '') = 'true' or coalesce(p.with_check, '') = 'true')
union all
-- 4. SECURITY DEFINER functions that anyone can call
select 4, 'medium',
'Function runs with owner rights and visitors can call it (often fine, worth checking)',
format('%I.%I()', n.nspname, p.proname),
'Fine if it only checks the caller (e.g. uses auth.uid()); otherwise revoke EXECUTE from anon'
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
where n.nspname = 'public' and p.prosecdef
and has_function_privilege('anon', p.oid, 'EXECUTE')
union all
-- 5. Public storage buckets
select 5, 'medium',
'Public storage bucket: anyone with a file URL can download it',
b.name,
'Make it private and use signed URLs unless every file is meant to be public'
from storage.buckets b
where b.public
) findings
order by rank, item;
What it checks
- Critical: tables with Row-Level Security off that anyone holding your public key can read.
- High: tables with RLS off, and policies that let every user or visitor through (
USING (true)). - Medium: functions that run with owner rights and that anyone can call, and public storage buckets.
It doesn't test your app's logic: whether customer B can reach customer A's data through a policy that looks right, whether payments can be faked, or whether keys are in your website code. That needs a person. It's what the Security Snapshot does.
Found something?
Data lockdown, $290 fixed: RLS on every table, policies matched to who should see what, Supabase access grants, keys and storage, tested as two customers and a visitor. Or start with a $99 Diagnosis, credited to the fix.