Supabase RLS: The Mistakes Every Guide Skips
The (select auth.uid()) performance trap, the “RLS disabled in public” warning, and the policy patterns that show up in CVE-2025-48757 — beyond the standard tutorial.
CVE-2025-48757 (disclosed May 2025) found 10.3% of analyzed Lovable apps shipped with Supabase tables readable by anyone holding the anon key. The misconfiguration patterns below are what we keep finding.
RLS is disabled by default — and that's how CVE-2025-48757 happened
Supabase creates tables with RLS off by default. In May 2025, security researcher Matt Palmer disclosed CVE-2025-48757: 303 endpoints across 170 Lovable projects (10.3% of the 1,645 analyzed) had Supabase tables readable by unauthenticated requests using the public anon key. If your stack is “AI builder + Supabase” — Lovable, Cursor, Bolt, Replit — assume your RLS is misconfigured until you've verified otherwise.
Not sure if your RLS is configured correctly?
Our Starter Scan tests your live project from the outside — the same way an attacker would. Paste your live app URL and we'll extract the Supabase config from your frontend.
RLS Policy Testing
Tests every table for read/write access
Storage Buckets
Checks upload/delete permissions
RPC Functions
Finds unprotected server functions
CRUD Permissions
INSERT, UPDATE, DELETE checks
Edge Functions
Tests unauthenticated access
Auth Config
CAPTCHA and signup settings
Starter Scan results in 2-3 minutes. Point it at your live app URL — VAS extracts your Supabase config from your frontend automatically.
Quick Start: Enable RLS in 2 Minutes
1Enable RLS on Your Table
-- Enable RLS on the table
ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;2Create a Policy for Authenticated Users
-- Users can only see their own data
CREATE POLICY "Users can view own data"
ON your_table
FOR SELECT
TO authenticated
USING ((select auth.uid()) = user_id);
-- Users can only insert their own data
CREATE POLICY "Users can insert own data"
ON your_table
FOR INSERT
TO authenticated
WITH CHECK ((select auth.uid()) = user_id);
-- Users can only update their own data
CREATE POLICY "Users can update own data"
ON your_table
FOR UPDATE
TO authenticated
USING ((select auth.uid()) = user_id);
-- Users can only delete their own data
CREATE POLICY "Users can delete own data"
ON your_table
FOR DELETE
TO authenticated
USING ((select auth.uid()) = user_id);Important: Use (select auth.uid()) instead of auth.uid() for better performance. The select wrapper prevents re-evaluation on every row.
3Verify It Works
-- Test as anonymous user (should return 0 rows)
SELECT * FROM your_table;
-- Or use the Supabase Dashboard:
-- Authentication → Policies → Test your policiesManual testing catches simple cases, but misses edge cases like write access, storage buckets, and RPC functions. For a thorough check, run a Starter Scan against your live app.
Common RLS Patterns
User-Owned Data
Most common pattern - each user can only access their own rows:
CREATE POLICY "Users own their data"
ON posts
FOR ALL
TO authenticated
USING ((select auth.uid()) = author_id);Public Read, Private Write
Anyone can read, but only owners can modify:
-- Anyone can read
CREATE POLICY "Public read access"
ON posts
FOR SELECT
TO anon, authenticated
USING (true);
-- Only owners can write
CREATE POLICY "Owners can modify"
ON posts
FOR UPDATE
TO authenticated
USING ((select auth.uid()) = author_id);Team-Based Access
Users can access data belonging to their team:
CREATE POLICY "Team members can access"
ON projects
FOR ALL
TO authenticated
USING (
team_id IN (
SELECT team_id FROM team_members
WHERE user_id = (select auth.uid())
)
);Common RLS Mistakes
Forgetting to enable RLS
Creating policies without enabling RLS does nothing. Always run:
ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;Using auth.uid() without select wrapper
This causes performance issues - the function re-evaluates for every row:
-- Bad: USING (auth.uid() = user_id)-- Good: USING ((select auth.uid()) = user_id)Creating service_role policies
Service role bypasses RLS automatically. Adding a policy for it creates warnings:
-- Bad: Creates multiple_permissive_policies warning-- Good: Just use TO authenticated (service_role bypasses RLS)Only testing SELECT policies
Most developers test if users can read other users' data but forget to test INSERT, UPDATE, and DELETE. An attacker who can't read your data can still modify or delete it if write policies are missing.
Our Starter Scan checks for all of these mistakes automatically.
Check Your App for RLS MistakesRLS Security Checklist
Want to check all of these automatically?
Run a Starter Scan — $9Frequently Asked Questions
Why use (select auth.uid()) instead of auth.uid() in Supabase RLS?
Wrapping auth.uid() in a SELECT subquery tells Postgres to evaluate the function once per query instead of once per row. On a 100K-row table that's the difference between a 5ms policy and a 5-second policy. Supabase's own performance docs recommend this pattern. The same applies to auth.role(), auth.jwt(), and current_setting(). If you have the auth_rls_initplan warning on a Supabase project, this is the fix.
What does “RLS disabled in public” mean in the Supabase dashboard?
It's the warning banner Supabase shows when one or more tables in the public schema have RLS turned off. Because tables in public are exposed via the auto-generated REST API, any table without RLS is readable (and often writable) by anyone holding your project's anon key. The fix is one line per flagged table: ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;. This is the misconfiguration that CVE-2025-48757 exploited at scale.
Is RLS enabled by default in Supabase?
No. Tables created via SQL or the Table Editor have RLS off by default. Supabase ships an “Enable RLS on new tables” project-level toggle, but you have to turn it on yourself. This default is exactly why 10.3% of analyzed Lovable apps in CVE-2025-48757 were leaking data through the anon key.
How do I enable RLS automatically on new tables?
In the dashboard: Authentication → Policies → toggle “Enable RLS on new tables.” This forces every newly-created table in the public schema to start with RLS enabled. It doesn't add policies for you — you still need to write the access rules — but it removes the default “open to the world” state. Highly recommended on every new Supabase project.
How do I disable RLS in Supabase (and when should I)?
ALTER TABLE your_table DISABLE ROW LEVEL SECURITY; turns it off for a table. The only legitimate case is read-only public reference data — country lists, public product catalogs — and even then a FOR SELECT TO anon, authenticated USING (true) policy is the safer move. Never disable RLS to fix a permission error; that error is RLS doing its job.
Where is “Authentication → Policies” in the Supabase dashboard?
Left sidebar → Authentication → Policies. This screen lists every RLS policy across your project grouped by table, surfaces the project-level “Enable RLS on new tables” toggle, and shows the schema-level RLS status. If a table has RLS enabled but no policies, you'll see “no policies” — meaning the table is locked even for authenticated users.
How can I test if my Supabase RLS is actually secure?
Manually: in the SQL Editor, switch your role to anon and try SELECT * FROM your_table on each table. Programmatically: Vibe App Scanner reads your Supabase config from your live app frontend and tests every table for read/write access plus storage buckets, RPC functions, and edge functions. Starter Scan is $9, results in 2-3 minutes. Or use the free RLS Checker tool for a quick sanity check.
Related
Free RLS Checker
Test a single table's policies — free, no signup
CVE-2025-48757 breakdown
The Lovable + Supabase RLS bypass in detail
Is Supabase Safe?
Honest security analysis of Supabase
Lovable Statistics
Funding, users, and the CVE-2025-48757 impact
Supabase Security Incidents
Documented breaches and exposures
Complete RLS Tutorial
In-depth guide with policy examples
Stop Guessing. Scan Your Supabase Project.
Paste your live app URL. We'll extract your Supabase config from your frontend and report RLS policies, storage buckets, RPC functions, and more in 2-3 minutes.
Run a Starter Scan — $9One-time payment. No subscription required.
Last updated: May 21, 2026