Home / Fixes / RLS off

Row Level Security is off — or it is on and doing nothing

If the anon key can select every row, you do not have permissions. You have a public database with extra typing.

Symptoms

Why Lovable, Bolt, and v0 do this

The generator’s job is a demo that renders a table. The fastest path is: create the table, turn on RLS because the docs said to, then add USING (true) so the select does not come back empty. Preview looks finished. The anon key, which is in your Vite env on purpose, can now read the whole table from any origin that can load your JS.

Some stacks skip ENABLE ROW LEVEL SECURITY entirely. On Supabase, a table without RLS is reachable with the anon key as soon as it exists. There is no “I’ll add policies later” mode that stays private.

The actual fix

Do this in the SQL editor, then re-test with the anon key, not the service role. The service role bypasses RLS; a green test with it proves nothing.

alter table public.profiles enable row level security;

drop policy if exists "public read profiles" on public.profiles;
drop policy if exists "Enable read access for all users" on public.profiles;

create policy "profiles_select_own"
  on public.profiles
  for select
  to authenticated
  using (auth.uid() = id);

create policy "profiles_update_own"
  on public.profiles
  for update
  to authenticated
  using (auth.uid() = id)
  with check (auth.uid() = id);

Match id (or user_id) to the column that stores auth.users.id. If your generator used user_id, write auth.uid() = user_id.

Split SELECT / INSERT / UPDATE / DELETE. A FOR ALL USING (true) is how people get a “fixed” app that anyone can delete.

Paid flags, invoices, and admin columns do not belong in a table the browser updates. Let a server webhook (service role, server only) set status = 'paid'.

Grep the repo for USING (true) and for ENABLE ROW LEVEL SECURITY. Both hits matter. The first is theatre; the second missing is an open door.

How to know you are done

  1. Two real users. User A cannot select user B’s row with the anon key.
  2. Logged-out select('*') returns zero rows (or only rows you explicitly made public).
  3. The service role is not in src/. See the service_role page.

Starter SQL with a profiles + orders example ships in the $39 kit as supabase/rls-starter.sql.

$49 to run this against your tree

Zip or GitHub URL in. Ranked HTML/PDF in 24h. No call. First 10 audits are $49, credited if you continue to triage. Email contact@shipready.local.

Start an audit See a sample report