Free tool
Supabase RLS audit
One read-only query that lists the tables anyone might be able to read: Row Level Security switched off, policies that let everyone in, views that bypass RLS and public storage buckets.
1. Run the query
In your Supabase dashboard open SQL Editor → New query, paste this, and press Run. It only reads Postgres's own catalog, so it's safe on a live project. It checks the public schema (the one your API exposes by default) and Storage.
RLS audit SQL
-- SealMyApp RLS audit. Read-only: it only reads Postgres catalog views.
-- Run it in Supabase: SQL Editor -> New query -> paste -> Run.
select 'RLS OFF' as finding,
t.schemaname::text as schema_name,
t.tablename::text as object_name,
null::text as policy_name,
null::text as command,
null::text as applies_to,
'Row Level Security is off: anyone with your public key may be able to read and change every row' as meaning
from pg_tables t
where t.schemaname = 'public'
and not t.rowsecurity
union all
select 'POLICY USES TRUE',
p.schemaname::text,
p.tablename::text,
p.policyname::text,
p.cmd::text,
array_to_string(p.roles, ', '),
case p.cmd
when 'SELECT' then 'Every role listed can read every row'
when 'INSERT' then 'Every role listed can insert any row'
when 'UPDATE' then 'Every role listed can update any row'
when 'DELETE' then 'Every role listed can delete any row'
else 'Every role listed can read and change every row'
end
from pg_policies p
where p.schemaname in ('public', 'storage')
and (p.qual = 'true' or p.with_check = 'true')
union all
select 'RLS ON, NO POLICIES',
t.schemaname::text,
t.tablename::text,
null, null, null,
'Locked: nothing can use this table through the API. Safe; add policies only if your app needs it'
from pg_tables t
where t.schemaname = 'public'
and t.rowsecurity
and not exists (
select 1 from pg_policies p
where p.schemaname = t.schemaname and p.tablename = t.tablename
)
union all
select 'VIEW WITHOUT SECURITY_INVOKER',
n.nspname::text,
c.relname::text,
null, null, null,
'Runs with its owner''s rights, so it can bypass RLS on the tables it reads'
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where c.relkind = 'v'
and n.nspname = 'public'
and not exists (
select 1 from unnest(coalesce(c.reloptions, array[]::text[])) as o(opt)
where lower(o.opt) in ('security_invoker=true', 'security_invoker=on', 'security_invoker=1', 'security_invoker=yes')
)
union all
select 'PUBLIC BUCKET',
'storage',
b.name::text,
null, null, null,
'Files in this bucket can be downloaded by anyone who has (or guesses) the URL'
from storage.buckets b
where b.public
order by 1, 2, 3;2. Read the results
Each row is one finding. No rows means none of these patterns turned up. The applies_to column lists the roles a policy covers: anon is a visitor who isn't signed in, authenticated is anyone who has signed up, and public is every role.
| finding | What it means | What to do |
|---|---|---|
RLS OFF | The table is in the public schema with Row Level Security switched off. | Fix today unless the table is genuinely public. Turn RLS on, then add owner-based policies. |
POLICY USES TRUE | The policy's condition is just true, so it applies to every row for the roles in applies_to. A role of public means everyone, including anonymous visitors. | Fine for public data (published posts, a catalogue). On private data, replace it with a check against (select auth.uid()). |
RLS ON, NO POLICIES | RLS is on but nothing is allowed, so the API can't use the table at all. | Safe. If a screen in your app is empty, add the specific policy it needs, not using (true). |
VIEW WITHOUT SECURITY_INVOKER | Views run with their owner's rights by default, which can bypass RLS on the tables they read. | If the view is exposed through the API, recreate it with security_invoker = true (Postgres 15+), or revoke access for anon and authenticated. |
PUBLIC BUCKET | Anyone with a file's URL can download it. | Fine for avatars or marketing images. Move ID documents, invoices and private uploads to a private bucket and use signed URLs. |
The query can't tell which tables are meant to be public; you can. A true policy on products is probably fine. The same policy on profiles or orders is a leak.
3. Fix what it finds
Review every statement before running it, and replace the table, policy and column names with yours.
Turn RLS on and add an owner policy
-- Turn RLS on for one table (replace profiles with your table name):
alter table public.profiles enable row level security;
-- Then let people read only their own rows (adjust the owner column):
create policy "profiles: read own"
on public.profiles for select to authenticated
using ((select auth.uid()) = id);Replace a using (true) policy
-- Replace a "using (true)" policy on private data with an owner check.
-- Use the policy and table names from the audit results.
drop policy "Enable read access for all users" on public.orders;
create policy "orders: owner can read"
on public.orders for select to authenticated
using ((select auth.uid()) = user_id);After turning RLS on, parts of your app may show no data until you add policies. That's expected: add the specific policy each screen needs. Our Row Level Security guide has copy-paste policies for owner-only rows, public read with owner write, teams, insert-only forms and Storage.
What this query doesn't catch
- Policies that look specific but are too broad, such as one that only checks the user is signed in. Every person who signs up gets through.
- Tables in other schemas you've exposed through the API.
- Secret keys in your frontend. If the
service_roleorsb_secret_key is in your JavaScript, RLS doesn't matter. Check a key with our key checker.
Supabase's own Advisors → Security Advisor in the dashboard flags several of these too (for example tables with RLS disabled), and it's worth running alongside this query.
FAQ
Is this query safe to run on my production database?
Yes. It only reads Postgres catalog views (pg_tables, pg_policies, pg_class) and storage.buckets. It doesn't read your data and changes nothing.
Is a policy that uses true always wrong?
No. using (true) on a SELECT policy is fine for data that's meant to be public, like published posts or a product catalogue. It's a problem on profiles, orders, messages or anything with personal data.
The results are empty. Am I safe?
It means none of these patterns turned up in the public schema and storage. Policies can still be wrong in subtler ways, for example one that lets any signed-in user read every row. Test what a stranger can actually read too.
Does this work for Lovable Cloud?
Lovable's docs say Lovable Cloud is built on Supabase. If you can open a SQL editor for your database, the same query applies; otherwise check RLS in Lovable under More → Cloud → Database → RLS policies.
Sealed — a short weekly security note for vibe coders
One common security mistake in AI-built apps, how to fix it in your builder, and one useful tool. No spam; unsubscribe any time.