How to check if Row Level Security is on in Supabase

A five-minute check that tells you whether strangers can read your Supabase tables with the public anon key — and the SQL and prompt to fix it.

· 5 min read

Your app works. Users sign up, data saves, the dashboard looks right. And yet every table might be readable by anyone on the internet who opens DevTools.

That is what happens when Row Level Security (RLS) is off in Supabase. It is the most common serious problem in apps built with Lovable, Bolt, Cursor and other AI tools, and from the outside nothing looks broken. This guide shows how to check it yourself in about five minutes, how to fix it, and how to make sure it stays fixed.

Why the anon key makes RLS so important

A Supabase app running in the browser talks to the database directly. To do that, it ships two things inside your JavaScript: the project URL and the anon key (or, on newer projects, the publishable key sb_publishable_…).

That key is public by design. Anyone can copy it from your site. Supabase is built on the assumption that the key is public and that RLS policies decide what each request is allowed to see. The Supabase documentation is explicit: tables in an exposed schema need RLS enabled.

So the security model is:

  • the anon key says "this request comes from your app";
  • RLS says "this particular user may read these particular rows".

Remove the second half and the first half lets everyone read everything.

Why AI-built apps so often ship without it

AI app builders are optimised to make the feature work. When a query fails because of a missing policy, the quickest way to make it work is to turn RLS off or to add a policy that allows everything. The app starts working, the conversation moves on, and nobody comes back.

Three patterns show up again and again:

  1. RLS never enabled on tables created with SQL or migrations.
  2. RLS enabled, but with a policy like using (true) for the anon role, which is the same as no protection for reading.
  3. A view that joins protected tables. Postgres views run with the permissions of their owner by default, so they can bypass RLS on the tables underneath.

Check it yourself

1. List tables without RLS

Open the SQL Editor in your Supabase dashboard and run:

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

Every row with rowsecurity = false is a table without RLS. If any of them hold users, orders, messages or anything personal, that is your first fix.

2. Run the Security Advisor

In the dashboard, open Advisors → Security Advisor. It flags tables with RLS disabled in the public schema and views defined with security definer. It takes one click and catches the view case that the query above misses.

3. Try it as a stranger

The real question is not "is RLS on?" but "what can someone with my public key actually read?". Ask Supabase directly, using the same key your site ships:

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

Replace profiles with each table your app uses. If you get rows back without being logged in, a stranger gets them too. An empty array [] usually means RLS is doing its job for anonymous reads.

Only run this against your own project.

4. Make sure the secret key never reached the browser

The service_role key (or sb_secret_… on newer projects) bypasses RLS completely. If it is in your frontend, the checks above do not matter. Open your live site, then DevTools → Sources, and search all files for service_role and sb_secret_. For the long JWT-style keys you can paste one into a JWT decoder and look at the role claim: anon is fine, service_role is not.

For a more thorough way to search a whole bundle, see how to find API keys in your JavaScript bundle.

How to fix it

Turn RLS on

alter table public.profiles enable row level security;

With RLS on and no policies, the anon key can read nothing from that table. That is a safe default, but your app will probably stop showing data until you add policies.

Add policies that match how the app works

A typical "users see only their own rows" policy:

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

Write separate policies for insert, update and delete. Avoid using (true) unless the data is genuinely public, like a list of blog posts.

Fix views that bypass RLS

On Postgres 15 and later you can make a view respect the caller's permissions:

alter view public.order_summary set (security_invoker = true);

Let your AI do the boring part

If you built the app in Lovable, Cursor or Claude Code, give it a precise brief rather than "make it secure":

Enable Row Level Security on every table in the public schema.
For each table, add policies so that:
- authenticated users can select, insert, update and delete only rows
  where user_id = auth.uid();
- anonymous users cannot read anything, except the tables I list as public: [list].
Set security_invoker = true on every view in the public schema.
Do not use the service_role key anywhere in client code.
Show me the SQL before running it.

Then repeat the stranger test from step 3. The fix is only done when the anonymous request comes back empty.

Keep it from coming back

RLS problems are rarely a one-time mistake. The next time your AI adds a table to make a new feature work, the same thing can happen again, and nothing in the app will tell you.

That is what ScanYourSite watches for:

  • The free check looks through your site's JavaScript for service_role and sb_secret_ keys and tells you which Supabase project the app talks to.
  • On Pro, after you verify the domain, it takes the table names your site's code actually queries and asks Supabase, with your public key, how many rows a stranger could see. It never reads the contents of the rows.
  • With monitoring on, it repeats this after every deploy and writes to you only when something changes.

You can also run all of the above by hand once a week. The important part is that someone checks after every change, not only on launch day.

Check your own site in a minute

Free, no sign-up. You get a prioritised list of what’s wrong and a prompt you can paste back into Lovable, Bolt, Cursor or Claude Code.

Free, no sign-up. The basic check makes only the requests an ordinary browser would.

Supabase