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 →