2026-08-20 · 8 min read
Fix Supabase RLS so Lovable users can't see each other's data
The usual Lovable + Supabase leak: RLS off, policies that use true, or queries that skip auth.uid(). How I find it and close it before launch.

Most Lovable apps I inherit almost work until a second person signs up. Then user B sees user A's chats, invoices, leads, whatever. That's rarely a Lovable UI bug. It's Supabase Row Level Security that's missing, turned off, or written as USING (true).
#What broken RLS looks like in practice
- The table exists, the frontend selects from it, and every logged-in user gets every row.
- Auth works and you see a session, but RLS was never enabled on the table.
- A policy exists but uses true, or it checks a column the insert never sets (like user_id).
- The Lovable client uses the anon key correctly, but you only tested the table with the service role in the SQL editor. That bypasses RLS.
#Step 1: Confirm RLS is actually on
In the Supabase SQL editor, check the table. If rowsecurity is false, no frontend change will protect you.
select relname, relrowsecurity
from pg_class
where relname = 'leads';
alter table public.leads enable row level security;#Step 2: Own every row with auth.uid()
Every user-owned table needs a column tied to the signed-in user, usually user_id uuid references auth.users(id). Lovable inserts must set that column from the session, not from a free-text field the client makes up.
-- Example: each user only sees their own leads
create policy "leads_select_own"
on public.leads
for select
to authenticated
using (auth.uid() = user_id);
create policy "leads_insert_own"
on public.leads
for insert
to authenticated
with check (auth.uid() = user_id);
create policy "leads_update_own"
on public.leads
for update
to authenticated
using (auth.uid() = user_id)
with check (auth.uid() = user_id);
create policy "leads_delete_own"
on public.leads
for delete
to authenticated
using (auth.uid() = user_id);#Step 3: Test with two real accounts, not the SQL editor
Create user A and user B in Auth. Sign in as A in the Lovable app, insert a row, then sign in as B. If B can read A's row, the policy is wrong or the insert saved the wrong user_id. Don't trust "it worked when I ran select * as postgres."
#Common Lovable-specific traps
- Generated CRUD that never passes user_id. Fix the insert payload or add a database default: user_id uuid default auth.uid().
- Using the service role key in the browser (never). Keep the service role on the server only.
- Realtime channels subscribed without filters. Still enforce RLS. Channel names are not security.
- Admin dashboards that need all rows need a separate role and policies, or a server route with the service role. Don't use USING (true) for everyone.
#When to stop DIY and get it audited
If you already have production users or multi-tenant orgs (org_id plus membership tables), stop patching one policy at a time. I audit the schema, rewrite policies, and verify with role-based tests before you ship more features on a leaky base. That's most of the Lovable + Supabase rescue work I do.