Guides / Supabase “RLS Policy Always True”: find and fix USING (true) policies
Supabase “RLS Policy Always True”: find and fix USING (true) policies
Turning on Row Level Security is only half the job. A policy that says using (true) lets every request for that role through, so the table behaves almost as if RLS were off.
The short answer
If Supabase's Security Advisor shows 0024 permissive_rls_policy (“Security policy allows unrestricted access”), at least one policy uses an always-true condition like using (true) or with check (true). For the roles it names, every row passes. Unless the table is meant to be public, drop that policy and write one that checks who owns the row, usually (select auth.uid()) = user_id.
A close relative is 0013 rls_disabled_in_public (“Table publicly accessible”), where RLS isn't enabled at all. Supabase rates that one ERROR: anyone with your project URL can read, insert, update and delete rows in that table.
Why this happens so often in AI-built apps
When an app builder or coding agent hits a “permission denied” or empty result, the quickest way to make the error go away is a policy that allows everything. Supabase's own lint docs list the usual causes: placeholder policies created during development and never updated, policies written on the assumption that some other policy would restrict access, and copy-paste from examples. The app works, so nobody looks again.
Two details make it worse:
- Permissive policies are OR-ed. If a table has a careful owner-only policy and a
using (true)policy for the same role and command, thetrueone wins. to authenticatedis not much of a filter. If signup is open, anyone can make an account in a minute and get theauthenticatedrole. Anonymous sign-ins use that same role too (lint 0012).
How to check
1. Run the Security Advisor
In the Supabase dashboard, open Advisors → Security Advisor. If you work from the terminal, the CLI has supabase db advisors. Look for 0013, 0024 and 0023 (sensitive columns such as password, token or api_key in a table without RLS).
2. List the always-true policies yourself
Run this in the SQL Editor. It shows every policy in public whose condition is literally true, plus tables with RLS switched off:
-- Policies that let every row through select tablename, policyname, roles, cmd, qual, with_check from pg_policies where schemaname = 'public' and (qual = 'true' or with_check = 'true') order by tablename, cmd; -- Tables with RLS off select tablename from pg_tables where schemaname = 'public' and rowsecurity = false;
Note that Postgres stores the expression text, so tautologies like 1 = 1 won't match 'true'. Read the qual and with_check columns for the rest of your policies too. An insert or update policy with no with check at all is also flagged by the lint.
3. Test it from the outside
This is what an attacker does: take the publishable (or legacy anon) key out of your frontend and call the Data API directly. Run it against your own project only:
curl "https://YOUR_PROJECT_REF.supabase.co/rest/v1/profiles?select=*&limit=5" \ -H "apikey: YOUR_PUBLISHABLE_KEY"
If you get rows back without signing in, the anon role can read that table. An empty array [] means RLS filtered everything out. A permission error means the role has no grant on the table at all. To test the signed-in case, sign up a throwaway user in your own app and repeat the request with -H "Authorization: Bearer USER_ACCESS_TOKEN". If that user can see other people's rows, the policy is too loose.
Stick to reads on production. If you want to prove a write policy is broken, do it on a branch or a local stack (supabase start), not on live data.
How to fix it
Replace the always-true policy with an owner check
drop policy "allow_all" on public.notes; create policy "Users can read their own notes" on public.notes for select to authenticated using ( (select auth.uid()) = user_id ); create policy "Users can create their own notes" on public.notes for insert to authenticated with check ( (select auth.uid()) = user_id ); create policy "Users can update their own notes" on public.notes for update to authenticated using ( (select auth.uid()) = user_id ) with check ( (select auth.uid()) = user_id ); create policy "Users can delete their own notes" on public.notes for delete to authenticated using ( (select auth.uid()) = user_id );
Wrapping auth.uid() in (select …) is Supabase's recommended form. It's evaluated once per query instead of once per row (lint 0003).
Turn RLS on where it's off
alter table public.notes enable row level security;
After this, the anon and authenticated roles get nothing until you add policies, so add the owner policies above in the same migration or your app will suddenly show empty screens.
When using (true) is fine
It's fine for data that really is public, like a product catalog, published blog posts or a public FAQ, as long as it's only on select and nobody has a write policy or grant they shouldn't. If you only want some rows public, keep the permissive policy and narrow it with a restrictive one:
create policy "only_published" on public.posts as restrictive for select to anon, authenticated using ( status = 'published' or (select auth.uid()) = user_id );
Then re-check
Re-run the Security Advisor and the curl test above. The rows should be gone for anon, and a second test user should only see their own data. For a longer-term safety net, Supabase recommends pgTAP tests (supabase test db). Our Supabase RLS guide walks through grants, the four-policy pattern and testing in more detail.
next step
Check your live app for free
Our free Leak Checker looks at your deployed app the way any visitor's browser does and flags exposed secret keys, public source maps and missing security headers. It won't read your RLS policies, but it shows whether your live app ships a secret key that would skip them entirely.
If you'd rather have the whole app looked at, the $99 Scan + Report covers your repo and live app, including Supabase RLS, leaked keys and auth checks.
Sources
Every product step above was checked against these official pages. Last checked Oct 2026. Products change; if a menu moved, the docs below are the source of truth.