Missing RLS in Supabase
Row Level Security is Supabase's primary data protection mechanism. Without it, the publicly available anon key gives anyone full read and write access to your database tables. Missing RLS is the number one vulnerability in Supabase applications.
Scan Your Supabase AppHow It Happens
Supabase tables created through the dashboard or SQL migrations have RLS disabled by default. Developers must explicitly enable it and write policies for each table. This opt-in model means forgetting to enable RLS on even one table creates a complete data exposure. The Supabase client library works identically whether RLS is enabled or not from the developer's perspective. During development, everything works because the developer is authenticated. The problem only becomes apparent when an unauthenticated attacker or a different user tries to access data they shouldn't see. AI code generators like Lovable, Bolt, and Cursor frequently create Supabase tables without enabling RLS. The generated migrations focus on schema correctness and skip security configuration, deploying tables that are wide open from day one. Even when RLS is enabled, incomplete policies are common. A table might have a SELECT policy but no INSERT, UPDATE, or DELETE policies. Or the policy might use a condition that is too permissive, like checking auth.role() = 'authenticated' which allows any logged-in user to access all rows.
Impact
A single table without RLS can expose your entire user base's data. The Supabase anon key, which is designed to be public and is visible in every frontend app, provides the access needed to query unprotected tables. Attackers use automated tools to scan for Supabase projects and test table access through the REST API. A typical attack takes seconds: extract the anon key from the JavaScript bundle, enumerate common table names (users, profiles, orders, messages), and query each one. The impact scales with the sensitivity of the data. User profiles, private messages, financial records, health data, and authentication tokens stored in custom tables are all at risk. Data can be not just read but also modified or deleted.
How to Detect
Query your tables using only the anon key and no authentication. Use curl or the Supabase client initialized with just the anon key. If any data returns, that table lacks proper RLS. In the Supabase dashboard, go to Authentication > Policies and check each table. Look for tables with no policies or with RLS disabled entirely. Pay special attention to tables created by AI tools or rapid prototyping. Vibe App Scanner performs comprehensive RLS testing by probing every accessible table through the public REST API, identifying missing policies, overly permissive conditions, and incomplete operation coverage.
How to Fix
Enable RLS on every table with ALTER TABLE table_name ENABLE ROW LEVEL SECURITY. Run this for all existing tables immediately. Write explicit policies for each CRUD operation using the pattern: CREATE POLICY "name" ON table FOR operation TO authenticated USING ((select auth.uid()) = user_id). Use (select auth.uid()) instead of auth.uid() for better performance. For tables that need public read access (like a blog), create a SELECT policy with TO anon that restricts what unauthenticated users can see. Never leave a table without RLS just because some of its data should be public. Set up automated checks that run during deployment to verify every table has RLS enabled and at least one policy per operation. Integrate this into your CI pipeline to catch missing RLS before it reaches production.
Code Examples
Complete RLS setup for a user data table
CREATE TABLE documents (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid REFERENCES auth.users(id),
title text NOT NULL,
content text,
created_at timestamptz DEFAULT now()
);
-- Forgot to enable RLS
-- Anyone can read all documentsCREATE TABLE documents (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid REFERENCES auth.users(id),
title text NOT NULL,
content text,
created_at timestamptz DEFAULT now()
);
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users read own docs" ON documents
FOR SELECT TO authenticated
USING ((select auth.uid()) = user_id);
CREATE POLICY "Users insert own docs" ON documents
FOR INSERT TO authenticated
WITH CHECK ((select auth.uid()) = user_id);
CREATE POLICY "Users update own docs" ON documents
FOR UPDATE TO authenticated
USING ((select auth.uid()) = user_id);
CREATE POLICY "Users delete own docs" ON documents
FOR DELETE TO authenticated
USING ((select auth.uid()) = user_id);Frequently Asked Questions
Why doesn't Supabase enable RLS by default?
Supabase has started warning about missing RLS in the dashboard, but tables created via SQL or migrations still have RLS disabled by default for backwards compatibility. Always enable it explicitly.
Does the anon key really give full database access without RLS?
Yes. The anon key is designed to be public, and without RLS, it provides full SELECT, INSERT, UPDATE, and DELETE access to any table exposed through the API. This is by design: RLS is the security mechanism, not the key.
How do I check all my tables for missing RLS at once?
Run this SQL in the Supabase SQL editor: SELECT tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public'. Any table where rowsecurity is false needs RLS enabled immediately.
Can I use the service_role key as a workaround for RLS?
Never use the service_role key in frontend code. It bypasses RLS entirely and should only be used in trusted server-side environments. Using it in the frontend is worse than having no RLS because it can't be scoped with policies.
Related Security Resources
Is Your App Vulnerable?
VAS automatically scans for missing row level security and other security issues in Supabase apps. Get actionable results with step-by-step fixes.
Scans from $5, results in minutes.