Every table in the public schema must have Row Level Security enabled with at least one policy per operation. Without RLS, the Supabase API exposes every row to every request — any browser with your anon key or any logged-in user can read, modify, or delete data belonging to other users. A single table missing RLS can leak your entire user base's private data or let one user overwrite another's records.
Without RLS, any user with the anon key or an authenticated session can read, insert, update, or delete every row in the table via the Supabase client or a direct PostgREST request. A single unprotected table can expose your entire user base's data. This is the #1 most common Supabase security vulnerability.
BeforeMerge scans your pull requests against this rule and 3+ others. Get actionable feedback before code ships.
Supabase exposes every table in the public schema through PostgREST (the auto-generated REST API) and Realtime. If a table does not have Row Level Security enabled, any user with the anon key or an authenticated session can perform any operation on any row — regardless of whether your application code restricts access.
This is not a theoretical risk. It is the single most common vulnerability in Supabase projects.
Every table in the public schema must have:
ALTER TABLE <table> ENABLE ROW LEVEL SECURITY;CREATE TABLE public.documents (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
owner_id uuid REFERENCES auth.users(id),
title text NOT NULL,
content text
);
-- No RLS! Any user with the anon key can read/write all documents.CREATE TABLE public.documents (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
owner_id uuid REFERENCES auth.users(id),
title text NOT NULL,
content text
);
ALTER TABLE public.documents ENABLE ROW LEVEL SECURITY;
-- Users can only see their own documents
CREATE POLICY "users read own documents"
ON public.documents FOR SELECT
USING (owner_id = auth.uid());
-- Users can only insert documents they own
CREATE POLICY "users insert own documents"
ON public.documents FOR INSERT
WITH CHECK (owner_id = auth.uid());
-- Users can only update their own documents
CREATE POLICY "users update own documents"
ON public.documents FOR UPDATE
USING (owner_id = auth.uid())
WITH CHECK (owner_id = auth.uid());
-- Users can only delete their own documents
CREATE POLICY "users delete own documents"
ON public.documents FOR DELETE
USING (owner_id = auth.uid());USING (true) grants access to everyone, defeating the purpose of RLS. Always scope policies to the authenticated user with auth.uid().The most reliable detection is querying the database directly:
SELECT schemaname, tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public'
AND rowsecurity = false;Any rows returned are unprotected tables. Run this in CI after migrations:
# Fails if any public table lacks RLS
result=$(psql "$DATABASE_URL" -t -c "
SELECT count(*) FROM pg_tables
WHERE schemaname = 'public' AND rowsecurity = false;
")
if [ "$result" -gt 0 ]; then
echo "FAIL: $result tables without RLS"
exit 1
fiFor code review, search migration files for CREATE TABLE and verify a matching ENABLE ROW LEVEL SECURITY exists.
ALTER TABLE public.<table> ENABLE ROW LEVEL SECURITY;pg_tables CI check above to prevent regressions