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

  1. Open your project at supabase.com/dashboard and go to SQL Editor.
  2. Paste the script below (or download it) and click Run.
  3. 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

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.