Free tool · Supabase
Supabase self-check
Run one read-only query in your own Supabase SQL Editor, paste the result below, and get a plain-English list of tables that are readable or writable through your Data API, tables that will hit 42501, and public storage buckets.
Your result never leaves your browser. The report is built by JavaScript on this page. We don't send, store or log what you paste: the page has no form that submits it, makes no network requests with it, and its security policy blocks this page from connecting anywhere. It never asks for your project URL or keys, and it can't contact your Supabase project. It can only check a project you're signed in to, because you run the query yourself.
Step 1: Run this query in your own project
In the Supabase dashboard, open SQL Editor, start a new query, paste this and click Run. It only reads Postgres catalog information (table names, RLS flags, policy counts, grants and public bucket names). It reads no rows from your tables and changes nothing. Please read it before you run it: you should never run SQL you don't understand.
-- BuiltSafely Supabase self-check v1 (read-only)
-- Reads Postgres catalog metadata only: table names, RLS flags, policy
-- counts, grants and public bucket names. It reads no rows from your
-- tables and changes nothing. Run it in your own project's SQL Editor.
with t as (
select c.oid, c.relname as name, c.relkind as kind,
c.relrowsecurity as rls,
coalesce(array_to_string(c.reloptions, ',') ~* 'security_invoker=(true|on|1)', false) as invoker
from pg_catalog.pg_class c
join pg_catalog.pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public' and c.relkind in ('r', 'p', 'v', 'm')
),
r as (
select exists (select 1 from pg_catalog.pg_roles where rolname = 'anon') as has_anon,
exists (select 1 from pg_catalog.pg_roles where rolname = 'authenticated') as has_auth
)
select json_build_object(
'tool', 'builtsafely-supabase-check',
'v', 1,
'tables', coalesce((
select json_agg(json_build_object(
'name', t.name,
'kind', t.kind,
'rls', t.rls,
'invoker', t.invoker,
'policies', (select count(*) from pg_catalog.pg_policies p
where p.schemaname = 'public' and p.tablename = t.name),
'open', (select count(*) from pg_catalog.pg_policies p
where p.schemaname = 'public' and p.tablename = t.name
and (p.qual = 'true' or p.with_check = 'true')
and (p.roles && array['public', 'anon']::name[])),
'anon', case when r.has_anon then concat_ws(',',
case when has_table_privilege('anon', t.oid, 'SELECT') then 'S' end,
case when has_table_privilege('anon', t.oid, 'INSERT') then 'I' end,
case when has_table_privilege('anon', t.oid, 'UPDATE') then 'U' end,
case when has_table_privilege('anon', t.oid, 'DELETE') then 'D' end) end,
'auth', case when r.has_auth then concat_ws(',',
case when has_table_privilege('authenticated', t.oid, 'SELECT') then 'S' end,
case when has_table_privilege('authenticated', t.oid, 'INSERT') then 'I' end,
case when has_table_privilege('authenticated', t.oid, 'UPDATE') then 'U' end,
case when has_table_privilege('authenticated', t.oid, 'DELETE') then 'D' end) end
) order by t.name)
from t, r
), '[]'::json),
'public_buckets', coalesce((
select json_agg(b.name order by b.name) from storage.buckets b where b.public
), '[]'::json)
) as builtsafely_check;
Step 2: Paste the result here
Copy the result cell (or use the results copy or export option as JSON or CSV) and paste it below. Table names are the only project details in it.
What it checks
- RLS off on a table that
anon(signed-out visitors) orauthenticated(any signed-in user) can reach. See our RLS guide. - RLS on but no policies, which returns an empty
[]instead of an error. See the empty array fix. - Policies that allow everyone (
using (true)orwith check (true)foranonorpublic). Fine for data that's meant to be public, and only then. - Write grants for signed-out visitors. Policies don't remove grants, so it shows what each role can attempt.
- No grants at all, which raises
42501 permission denied. Since Supabase's Oct 30, 2026 change, new tables start this way. - Views without
security_invoker = true, which skip RLS by default. - Public storage buckets, where anyone with a file's URL can read the file (Supabase: Storage buckets).
It covers the public schema only. If you expose other schemas in your API settings, review them too. It can't tell whether a policy's logic is right, so test with two accounts and run Supabase's Security Advisor as well (how).
This self-check is educational and based on Supabase's and PostgreSQL's public docs. It isn't a security audit and can't prove your app is safe. Disclaimer.