// guide
RLS enabled but still leaking? 5 policy mistakes to check
Turning on Row Level Security is step one. What actually protects your data is the policies. A common report from vibe coders: RLS is "enabled", yet a normal signed-in user can still load other people's profiles. These are the usual causes.
1. A policy that just says true
USING (true) means every role the policy applies to can see every row. RLS is on, but it filters nothing.
✗ leaks
create policy "read" on profiles for select using (true);
✓ fixed
create policy "read own" on profiles for select to authenticated using (auth.uid() = user_id);
2. Checking 'signed in' instead of 'owner'
auth.role() = 'authenticated' only proves someone has an account. Anyone can sign up, so anyone can read.
✗ leaks
using (auth.role() = 'authenticated')
✓ fixed
using (auth.uid() = user_id)
3. Locking reads but not writes
A select policy doesn't cover insert or update. Without WITH CHECK, users can write rows owned by someone else, or change user_id on their own row.
✗ leaks
create policy "update" on orders for update using (auth.uid() = user_id);
✓ fixed
create policy "update own" on orders for update to authenticated using (auth.uid() = user_id) with check (auth.uid() = user_id);
4. Trusting user-editable metadata for roles
user_metadata can be changed by the user. A policy that checks it for 'admin' lets anyone make themselves admin.
✗ leaks
using ((auth.jwt()->'user_metadata'->>'role') = 'admin')
✓ fixed
-- keep roles in a separate table users can't write using (public.has_role(auth.uid(), 'admin'))
5. A table nobody turned RLS on for
New tables, join tables and views are easy to miss. One unprotected table is enough to leak data.
✗ leaks
create table invoices (...); -- no RLS
✓ fixed
alter table invoices enable row level security;
How to test it yourself
- Create two test accounts, A and B.
- As A, create some data.
- Sign in as B and try to read, edit and delete A's rows, from the app and straight through the API with B's token.
- Repeat signed out. Anything that comes back is a leak.
Check your schema
Paste your SQL schema into the Deep Audit. It looks for these patterns and runs in your browser, so your code isn't uploaded. ShipAudit AI checks common launch risks; it is not a full security review.