Is your Supabase RLS off? A 5-minute check

Updated Oct 22, 2026 · For apps built with Lovable, Bolt, Cursor, Replit or v0 on Supabase

Short answer: if Row Level Security (RLS) is off on a table in your public schema, anyone can read that table using the anon key that already ships in your site's JavaScript. Here's how to check every table and fix it.

Why this matters

Supabase apps talk to the database straight from the browser, using your project URL and the anon key. That key is meant to be public. What protects your data is RLS: rules on each table that decide which rows a request may see or change. AI coding tools often create tables that work fine while you test as the owner, without the policies that stop strangers.

Step 1: list tables with RLS off

In the Supabase dashboard, open the SQL Editor and run:

select schemaname, tablename, rowsecurity
from pg_tables
where schemaname = 'public'
order by rowsecurity, tablename;

Any row where rowsecurity is false is a table without RLS. The dashboard's Security Advisor also flags these as "RLS disabled in public".

Step 2: see what a stranger sees

Run this from your own terminal against your own project, using the anon key from your frontend:

curl "https://YOUR-PROJECT.supabase.co/rest/v1/profiles?select=*&limit=5" \
  -H "apikey: YOUR_ANON_KEY" \
  -H "Authorization: Bearer YOUR_ANON_KEY"

If real user rows come back, any visitor can do the same. Repeat for each table that holds personal or paid data. Only test projects you own.

Step 3: the two-account test

RLS can be on but too loose, for example a policy of using (true). Sign up a second test account, log in as it, and try to load the first account's records by changing an id in the URL or request. If you can see them, a policy needs tightening.

Step 4: fix it

Turn RLS on, then add owner-only policies. For a table with a user_id column:

alter table public.profiles enable row level security;

create policy "Users read own rows" on public.profiles
  for select to authenticated using (auth.uid() = user_id);

create policy "Users update own rows" on public.profiles
  for update to authenticated
  using (auth.uid() = user_id) with check (auth.uid() = user_id);

Write one policy per operation (select, insert, update, delete) and name the role, so it's clear who each rule covers. Once RLS is on, anything without a matching policy is denied, so add the policies your app actually needs, then re-run steps 2 and 3 to confirm.

Common questions

Is the Supabase anon key a secret?

No. The anon (publishable) key is meant to be in your frontend. It is safe only when Row Level Security is enabled with correct policies on every table in an exposed schema, because anyone can use that key to call your database API.

What happens if RLS is off on a table?

Anyone who has your project URL and anon key, both visible in your site's JavaScript, can read and often change every row in that table through Supabase's REST API.

I enabled RLS and now my app shows no data. Why?

With RLS on and no policies, Supabase denies all access through the API. Add policies that allow exactly the access each user needs, such as reading only rows where user_id matches auth.uid().

Should I ever put the service_role key in my frontend?

Never. The service_role key bypasses RLS entirely. Keep it on the server only, and rotate it right away if it was ever shipped to a browser or committed to a repo.

Want a second pair of eyes?

LaunchSafeAI checks your live app for open tables, leaked keys and broken auth, shows you the exact request that proves each issue, and re-tests after the fix. Start with a free scan.