back_to_blog

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.

Fix Supabase RLS so Lovable users can't see each other's data

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.