Skip to content
All guides

Supabase says RLS is disabled: what it means and how to fix it

By Obscura Security · Updated · Supabase, Lovable, Bolt

Short answer

With RLS off, anyone who has your app's public Supabase key (which is in your frontend code) can read, change or delete every row in that table. Turn RLS on for every table in the public schema, then add a policy for each thing your users are allowed to do.

If Supabase’s dashboard shows RLS disabled next to a table, or the Security Advisor lists “RLS Disabled in Public”, that table is open to anyone who looks.

Why this happens so often

A Supabase app built with Lovable, Bolt or a Next.js starter usually talks to the database straight from the browser. To do that, it ships your project URL and your anon key (newer projects call it the publishable key, starting sb_publishable_) inside the frontend code. Anyone can open the browser’s developer tools and copy both.

That’s how Supabase is designed, and it’s fine, because Row Level Security is what decides who can see which rows. With RLS off, the key is all it takes. There’s no login and no hacking skill involved, just one web request.

Tables made in the Table Editor have RLS switched on by default. Tables made with SQL, including SQL that an AI assistant wrote for you, don’t. So a table can quietly end up open when you ask the AI to “add a table for orders”.

In 2025, a researcher checked 1,645 apps built with Lovable and found 170 of them leaking data through missing or too-open RLS (CVE-2025-48757), including home addresses, financial details and API keys.

How to check your own app

  1. In the Supabase dashboard, open Advisors → Security Advisor. Any table listed under “RLS Disabled in Public” is exposed.
  2. Or open Table Editor: each table shows whether RLS is on.
  3. To see what an outsider sees, ask your own project for a table using only the public key:
curl "https://YOUR-PROJECT.supabase.co/rest/v1/orders?select=*" \
  -H "apikey: YOUR_ANON_KEY" \
  -H "Authorization: Bearer YOUR_ANON_KEY"

If that returns rows, anyone on the internet can get them too. Only run this against your own project.

The fix

1. Turn RLS on for every public table

alter table public.orders enable row level security;

Do this for every table in the public schema. The moment it’s on, the table is locked: nobody using the public key can read or write it until you add policies. That’s the safe default.

2. Add a policy for each thing users should be able to do

Most apps want “people can see and edit their own rows”. If each row has a user_id column holding the owner’s ID:

create policy "Read own orders"
  on public.orders for select
  to authenticated
  using ( (select auth.uid()) = user_id );

create policy "Create own orders"
  on public.orders for insert
  to authenticated
  with check ( (select auth.uid()) = user_id );

create policy "Update own orders"
  on public.orders for update
  to authenticated
  using ( (select auth.uid()) = user_id )
  with check ( (select auth.uid()) = user_id );

using decides which existing rows a user can touch. with check decides what they’re allowed to write. Leave out with check on an update policy and a user could change user_id to hand a row to someone else.

For data that really is public, like a list of blog posts, a read-only policy is fine:

create policy "Anyone can read published posts"
  on public.posts for select
  to anon, authenticated
  using ( published = true );

3. Test it from outside again

Run the same curl request. A locked table returns [] for the anonymous key. Then sign in as a test user and confirm you see your own rows and nobody else’s.

Mistakes to avoid

  • using (true) on anything private. It switches RLS on but lets everyone through, and the dashboard will show RLS as “enabled”.
  • Trusting a column the user can set. A policy like using (is_admin = true) on the user’s own profile is useless if users can update their own profile. Keep roles in a table users can’t write to.
  • Fixing the table but not the views. Views run with their creator’s rights by default and can skip RLS. On Postgres 15+, create them with (security_invoker = true).
  • Asking the AI to “fix the RLS error” without reading the result. A common shortcut it takes is a policy that allows everything. Read every policy it writes.

Common questions

Is it safe that my Supabase anon key is in my frontend code?
Yes, as long as RLS is on for every table. The anon (or publishable) key is meant to be public. It only becomes dangerous when a table has RLS off, because then the key alone is enough to read and change that table.
I turned on RLS and now my app shows no data. What happened?
RLS blocks everything until you add a policy that allows it. Add a select policy for the rows each user should see, and insert, update and delete policies for what they can change.
Does Supabase warn me about this?
Yes. The Security Advisor in the Supabase dashboard flags every public table with RLS disabled. Check it after every schema change, including ones an AI tool made for you.
Do I need RLS if only my server talks to the database?
If your public key is never shipped to the browser and nothing calls the Data API directly, the risk is lower. Most Lovable, Bolt and Supabase starter apps do call it from the browser, so assume you need it.

Written by Obscura Security, an Australian practice that checks web apps for problems like this one. If you want a second pair of eyes on yours, email [email protected].