Critical severity

How to fix Supabase Row Level Security (RLS) disabled on a table

One of your Supabase tables has Row Level Security turned off. The anon key is in your client-side code, which means anyone who views your site source can read — and possibly write — every row. This is the single most common critical finding in AI-built apps. Fix it by enabling RLS on every table, then writing policies that match how your app actually uses the data. Never ship a Supabase app with RLS off on a user-facing table.

Why it matters

Supabase exposes your database over HTTP using the anon key. That key is meant to be public — RLS is what keeps your data safe. With RLS off, the anon key is equivalent to full database access.

How to check

  1. 01In the Supabase dashboard, go to Database → Tables.
  2. 02Look for a shield icon next to each table. A grey or crossed-out shield means RLS is off.
  3. 03Or run: `select tablename, rowsecurity from pg_tables where schemaname = 'public';`
  4. 04Any `rowsecurity = false` row is a bug.

Or let SafeToShip check it for you in 60 seconds:

How to fix it

Supabase SQL

Enable RLS on every public table, then add policies. Example for a `posts` table with a `user_id` column:

-- Turn on RLS
alter table public.posts enable row level security;

-- Allow users to read their own rows
create policy "users read own posts"
  on public.posts for select
  using (auth.uid() = user_id);

-- Allow users to insert rows as themselves
create policy "users insert own posts"
  on public.posts for insert
  with check (auth.uid() = user_id);

-- Allow users to update/delete their own rows
create policy "users update own posts"
  on public.posts for update
  using (auth.uid() = user_id);
create policy "users delete own posts"
  on public.posts for delete
  using (auth.uid() = user_id);

Public-read tables

If the table is meant to be public (like a blog posts list), you still need RLS on and a permissive read policy.

alter table public.posts enable row level security;
create policy "anyone can read posts"
  on public.posts for select
  using (true);
-- No insert/update/delete policy means those are blocked for anon.

AI prompt

Copy-paste into your AI tool

Paste this prompt into Cursor, Lovable, Bolt, v0, or Claude Code and it will walk through the fix for your specific codebase.

My Supabase project has a table where Row Level Security is disabled. First, list every table in the public schema and tell me which ones have RLS off. For each one: determine from my codebase which users should be able to read, insert, update, and delete rows (look at my fetch calls, check if there is a user_id or owner_id column, check if it is public data like blog posts). Then write the `alter table ... enable row level security;` and matching `create policy` statements. Make policies as restrictive as possible while still letting my app work.

FAQ

Frequently asked questions

Should I disable RLS for faster development?
No. Write the policies as you build. Every AI-built app I have seen with RLS off in production had someone pull data they should not have. Turn it on from the start and write policies as you add tables.
Do I need RLS if I only use the service role key?
The service role key bypasses RLS by design. If your app only ever queries through your own backend using the service role key, you can keep RLS off. But you must be sure the service role key never touches the client — and most AI-built apps use the anon key directly from the browser, which requires RLS.
How do I test my RLS policies?
In the Supabase SQL editor, impersonate a user with `set local role authenticated; set local request.jwt.claim.sub = 'USER_ID';` and try your queries. Or use the `supabase-js` client from a test script with a real user's JWT.

Scan your site for this and 50+ other issues

Free scan. Results in 60 seconds. No account required.