Supabase RLS Not Enabled
If Supabase Row Level Security (RLS) isn't enabled on a table, anyone who has your project's public anon key can query that table through the API. Your app ships the anon key to every visitor's browser, so "anyone with the key" really means anyone who opened your site. This is the most serious issue we check for in Supabase-backed apps built with Lovable, Bolt and similar tools, and it's also one of the quickest to fix.
The Pro Logica AI App Security Check runs a safe, count-only RLS test on your live app. Run the free check.
What RLS actually does
Supabase exposes your Postgres tables through an auto-generated REST API. Two things decide what a request can touch:
- Grants decide whether a role (
anonfor signed-out visitors,authenticatedfor signed-in users) can run an operation on a table at all. - RLS policies decide which rows that operation applies to. Think of a policy as a
WHEREclause Postgres adds to every query.
Supabase's docs warn that a table in an exposed schema without RLS is readable and writable by any role with a grant on it. On many projects, new tables in public start with select, insert, update and delete granted to anon and authenticated. Turn RLS off and the anon key you ship to the browser can read, and possibly change, every row.
Is the anon key exposed? Yes, and that's normal
People search "supabase anon key exposed" in a panic. The anon (or publishable) key is designed to be public. It identifies your project and maps requests to the anon role. Your security comes from RLS and grants, not from hiding that key.
The key that must never reach the browser is the service_role key (in newer projects, the secret key). It bypasses RLS completely. If it's in your JavaScript, treat it as a full database breach risk: rotate it now and move any code that needs it to a server or edge function. See Exposed API key in frontend.
How to tell if RLS is off
In the dashboard. Open Table Editor or Database → Tables. Supabase marks tables that have RLS disabled. Security Advisor also reports rls_disabled_in_public. If you built with Lovable Cloud, review policies under More → Cloud → Database → RLS policies.
From SQL:
select relname as table_name, relrowsecurity as rls_enabled
from pg_class
where relnamespace = 'public'::regnamespace and relkind = 'r';
From the outside (what our scan does). We find your Supabase URL and anon key in your public JavaScript and list the tables the API exposes. Then, for up to three tables, we send a request with Range: 0-0 and Prefer: count=exact. That asks Postgres for a row count without returning any rows. The answer comes back in the content-range header:
*/0, or a 401/403 error: RLS is filtering or the grant is missing. Anonymous visitors see nothing. That's the safe result, and we don't flag it.- A real number, such as
0-0/4182: an anonymous visitor can see 4,182 rows. We flag it as critical and report the table names and counts only.
We never download, store or show the contents of your tables.
How to fix it: enable RLS and write real policies
Run this in the SQL editor, or better, in a migration so every environment matches. Do it for every table in an exposed schema:
-- 1. Turn on RLS
alter table public.profiles enable row level security;
-- 2. Remove blanket grants, then give back only what's needed
revoke all on table public.profiles from anon, authenticated;
grant select, insert, update, delete on table public.profiles to authenticated;
-- 3. One policy per operation, scoped to the owner
create policy "Users read own profile"
on public.profiles for select to authenticated
using ( (select auth.uid()) = user_id );
create policy "Users update own profile"
on public.profiles for update to authenticated
using ( (select auth.uid()) = user_id )
with check ( (select auth.uid()) = user_id );
A few rules make this hold up:
- Once RLS is on with no policies, the API returns nothing. If your app breaks after you enable RLS, write the missing policy. Don't disable RLS again.
- Always name the role with
to authenticatedorto anon. - Use
using (true)foranononly on data that's truly public, like a product catalog. Never use it on users, orders or messages. - Check views. Views run as their creator by default, which can bypass RLS. On Postgres 15+, create them
with (security_invoker = true). - Test both sides. Supabase recommends pgTAP tests (
supabase test db) that assert both what's allowed and what's denied foranonandauthenticated.
Lovable and Bolt users can ask their builder to do this, but review the SQL it writes. The full report includes a copy-paste prompt written for your builder that says exactly this: enable RLS everywhere, use least-privilege policies keyed on auth.uid(), and don't weaken security to make the demo work.
What we saw in public apps
In our passive check of 18 public showcase apps built with Lovable or Bolt, 7 of 18 had a Supabase anon key in their client JavaScript. That's expected. We deliberately did not run the RLS test on those third-party apps. It only runs when the person scanning confirms they own the app or have permission. So we make no claim about whether their data was open. The test logic was validated separately against a mock PostgREST server: RLS off was flagged, and */0 or 401 was not.
Related
- Lovable security check · Bolt.new security check
- Exposed API key in frontend
- Firebase rules are public, the same problem on Firebase
- Next step: Run the free check on your app · Pro Logica cybersecurity services
FAQ
Does enabling RLS break my app?
It can, until you add policies. With RLS on and no policies, the API returns no rows. That's the safe default. Add a policy for each operation your app actually needs.
What does "supabase rls disabled in public" mean?
It's the Security Advisor warning that a table in the public schema, which the API exposes, has RLS turned off. Enable RLS on that table and add policies.
Is a using (true) policy the same as RLS off?
For that role and operation, effectively yes. Every row is visible. Use it only for data you'd happily publish on your homepage.
Will your scan read my customers' data?
No. We ask for a row count with Range: 0-0 and read only the count header. No rows are downloaded or stored, and the test only runs when you confirm you own the app.
Find out in a minute whether anonymous visitors can read your tables. Run the free AI app security check.
By Pro Logica