new row violates row-level security policy (Supabase)
Your insert or update is blocked by RLS. Add a policy for that exact write, without turning RLS off.
One using (true) or with check (true) policy opens a whole table, even with RLS on and an owner-only policy beside it. Find them with one pg_policies query and replace them with owner-scoped policies.

RLS is switched on, the dashboard says the table is protected, yet one signed-in user can read (or change) another user's rows. The usual cause is a policy whose rule is always true: using (true) or with check (true). RLS is doing exactly what that policy says: let everyone in.
1. Find every always-true policy (Verified: run in the SQL editor or psql as the database owner. In our test it listed every always-true policy we planted, including a with check (true) insert policy and a (1 = 1) delete policy, skipped owner-scoped and restrictive policies, and returned 0 rows after the fix):
-- Permissive RLS policies whose USING or WITH CHECK is always true
select schemaname, tablename, policyname, cmd, roles,
qual as using_expr, with_check as with_check_expr
from pg_policies
where schemaname not in ('pg_catalog', 'information_schema')
and permissive = 'PERMISSIVE'
and (
lower(regexp_replace(coalesce(qual, ''), '[\s()]', '', 'g')) in ('true', '1=1')
or lower(regexp_replace(coalesce(with_check, ''), '[\s()]', '', 'g')) in ('true', '1=1')
)
order by schemaname, tablename, cmd;
How to read the result: using_expr is the rule for which existing rows a role can see, update or delete. with_check_expr is the rule for which rows it may write. The pg_policies view shows each policy's qual and with_check expressions as text (PostgreSQL: pg_policies). In our test, using (true) showed up as plain true (Verified). A roles value of {public} means the policy had no to clause, so it applies to every role, signed-out anon requests included. In our test, a select policy with no to clause let a signed-out request read every row (Verified). A query that only checks qual = 'true' misses insert policies, because those keep their rule in with_check (Verified).
Every row here is a table where that role can reach every row for that command. That's fine for data that's meant to be public (a product catalog, published posts). For anything private, go to step 2.
2. Replace the always-true policies with owner-scoped ones (Verified). Example for a notes table with a user_id column. Use your own table, column and policy names from step 1:
-- Remove the always-true policies (names come from step 1)
drop policy if exists "notes readable by everyone" on public.notes;
drop policy if exists "notes insert for signed-in users" on public.notes;
drop policy if exists "notes update for signed-in users" on public.notes;
-- Owner-only access (dropped first so this re-runs cleanly)
drop policy if exists "notes select own" on public.notes;
drop policy if exists "notes insert own" on public.notes;
drop policy if exists "notes update own" on public.notes;
drop policy if exists "notes delete own" on public.notes;
create policy "notes select own" on public.notes
for select to authenticated
using ( (select auth.uid()) = user_id );
create policy "notes insert own" on public.notes
for insert to authenticated
with check ( (select auth.uid()) = user_id );
create policy "notes update own" on public.notes
for update to authenticated
using ( (select auth.uid()) = user_id )
with check ( (select auth.uid()) = user_id );
create policy "notes delete own" on public.notes
for delete to authenticated
using ( (select auth.uid()) = user_id );
These follow the patterns in Supabase's RLS guide: auth.uid() for the signed-in user, a to authenticated role, and (select auth.uid()) wrapped in a select for performance (Supabase: Row Level Security). Dropping the old policies matters. Adding a correct policy next to a using (true) one changes nothing (see below).
3. If the data really is public, keep it read-only. Leave using (true) only on a for select policy, name the roles with to anon, authenticated, and keep writes owner-scoped. Supabase's RLS guide shows this shape for public profiles and announcements (Supabase: Row Level Security). The check in step 1 will keep listing that policy. That's expected. (Docs-only)
4. Re-run Security Advisor. Supabase's database linter has a lint, 0024_permissive_rls_policy, for this exact pattern (Supabase: Database Advisors). (Docs-only: we ran the query above, not the hosted Advisor.)
RLS decides access row by row, using the policies on the table. A policy's using rule picks which existing rows a role can see, update or delete. Its with check rule picks which new or changed rows a role may write (PostgreSQL: CREATE POLICY). If the rule is true, every row passes.
Policies are permissive by default. Postgres combines all permissive policies for a command "using the Boolean 'OR' operator" (PostgreSQL: CREATE POLICY). So one using (true) policy opens the whole table, even when a correct owner-only policy sits right next to it. That's why the table "has RLS" and still leaks (Verified: our test table had both).
Who it opens the table to depends on the roles. Supabase's RLS guide says a select policy for anon with using ( true ) "grants every unauthenticated visitor read access to every row the role can already reach through grants" (Supabase: Row Level Security). to authenticated doesn't mean "only the owner" either. It means any signed-in user, and the same guide notes that anonymous sign-ins also use the authenticated role.
Supabase's linter docs describe the always-true pattern as one that "defeats the purpose of having Row-Level Security enabled" (Supabase splinter: 0024 permissive RLS policy).
using (true) one in place. Permissive policies are OR'd, so the open one still wins.with check (true) to get past a new row violates row-level security policy error. It lets a user write rows owned by someone else. Fix the real cause instead (new row violates RLS).service_role key in the browser to "make it work". Supabase says that key bypasses RLS (Supabase: API keys).to authenticated keeps strangers out. Anyone who can sign up is authenticated.RLS is enabled on my Supabase tables, but users can see each other's rows.
Run this read-only query and show me the results:
select schemaname, tablename, policyname, cmd, roles, qual, with_check
from pg_policies
where schemaname not in ('pg_catalog', 'information_schema')
and permissive = 'PERMISSIVE'
and (lower(regexp_replace(coalesce(qual, ''), '[\s()]', '', 'g')) in ('true', '1=1')
or lower(regexp_replace(coalesce(with_check, ''), '[\s()]', '', 'g')) in ('true', '1=1'));
For each policy, tell me whether the table holds private data.
For private tables, propose SQL that drops the always-true policy and adds owner-scoped
policies using (select auth.uid()) = user_id, with with check on insert and update.
Do not disable RLS. Do not use a secret or service_role key in client code.
Show the SQL first and wait for my OK before applying anything.
for select policies.user_id, and try to edit A's row. Expect a 42501 "new row violates row-level security policy" error on the insert and 0 rows changed on the edit. (A 42501 "permission denied" is a different problem: a missing grant. See 42501 permission denied.)[] for private tables.Our Supabase RLS guide · 42501 permission denied · new row violates RLS · empty array with RLS on · is the publishable key safe?
Did the query find a using (true) you didn't know about? Tell us which command it was on (select, insert, update or delete). We'll add the patterns we see most.
This guide is educational and based on the vendor's public docs as of the review date. Platforms change: check the linked sources. Not a security audit. Disclaimer.
From BuiltSafely · builtsafely.com · https://builtsafely.com/fixes/rls-using-true-policy/
The BuiltSafely weekly
Platform changes that affect the security of AI-built apps, checked against official docs. Free. Unsubscribe any time.