Skip to content

Supabase RLS Security: How to Fix Row Level Security in 10 Minutes

59% of AI-built Supabase apps ship with at least one table that has RLS disabled. Here's how to audit yours and fix it before someone else does.

What is Supabase RLS and why does it matter?

Supabase auto-generates a REST API from your PostgreSQL tables. By default, if you create a table in raw SQL, that table is accessible to anyone who has your anon key — which is public and embedded in every client-side app.

Row Level Security (RLS) is the mechanism that controls which rows each user can read, insert, update, or delete. Without it, your entire database is effectively public.

A 2026 study scanning 1,500+ AI-built apps found that 81% shipped with at least one critical security issue — and missing RLS was the most common finding by far.

How to check if your tables have RLS enabled

Run this query in your Supabase SQL editor:

SELECT
  schemaname,
  tablename,
  rowsecurity
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY tablename;

Any row where rowsecurity = false is a table that anyone with your anon key can read in full. If you have user data, payment records, or any private content in those tables — it's exposed.

The 3 most dangerous RLS misconfigurations

1. RLS disabled entirely

The table exists, RLS is off. Anyone can query it. This is the default when you create tables via SQL or migrations without explicitly enabling RLS.

-- Fix: enable RLS on the table
ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;

2. RLS enabled but no policies

This is actually worse than having RLS disabled. When RLS is on but there are no policies, Supabase denies all access — including your own app. Developers often respond by adding a permissive policy that allows everything:

-- DANGEROUS: this allows anyone to read everything
CREATE POLICY "allow all" ON your_table
FOR SELECT USING (true);

This is the same as having no RLS at all.

3. Policies that don't check auth

A policy that only checks a non-auth column (like a public flag) without verifying the user's identity can still leak data.

The correct RLS policies for common patterns

Users can only see their own rows

ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Users see own profile"
ON profiles FOR SELECT
USING (auth.uid() = user_id);

CREATE POLICY "Users update own profile"
ON profiles FOR UPDATE
USING (auth.uid() = user_id);

Public read, authenticated write

ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Anyone can read posts"
ON posts FOR SELECT
USING (true);

CREATE POLICY "Authors can insert"
ON posts FOR INSERT
WITH CHECK (auth.uid() = author_id);

Admin-only access

CREATE POLICY "Admins only"
ON sensitive_table FOR ALL
USING (
  EXISTS (
    SELECT 1 FROM user_roles
    WHERE user_id = auth.uid()
    AND role = 'admin'
  )
);

How to test your RLS policies

The best way to test is to impersonate an anonymous user and try to query your tables directly:

curl 'https://YOUR_PROJECT.supabase.co/rest/v1/users?select=*' \
  -H "apikey: YOUR_ANON_KEY" \
  -H "Authorization: Bearer YOUR_ANON_KEY"

If you get rows back, your RLS is not working. You should get an empty array [] or a 401 error.

Automate the audit

Manually checking every table is tedious and easy to miss. Vezraa's RLS Tester scans your Supabase project URL, attempts anonymous queries against common table names, and tells you exactly which tables are exposed — in 30 seconds.

Check your Supabase RLS in 30 seconds — free.

Test RLS Free →

Related articles

Supabase RLS Security: Fix Row Level Security | Vezraa