Supabase Security

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.

Full CVE-2025-48757 breakdown →

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

Scan Your App for RLS Issues — $9

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 policies

Manual 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 Mistakes

RLS Security Checklist

RLS is enabled on ALL tables containing user data
Every table with RLS has at least one policy
Policies use (select auth.uid()) not auth.uid()
No service_role key in frontend code
Policies specify TO authenticated or TO anon
Tested as anonymous user — cannot read data
Tested as anonymous user — cannot write/delete data
Storage buckets have proper access policies
RPC functions require authentication
INSERT policies use WITH CHECK, not USING

Want to check all of these automatically?

Run a Starter Scan — $9

Frequently 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 → AuthenticationPolicies. 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.

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 — $9

One-time payment. No subscription required.

Last updated: May 21, 2026