Supabase RLS: the mistake that exposes your database

See how Supabase tables with RLS off or open policies expose your data, what CVE-2025-48757 was, and the SQL to find gaps and add owner-only policies.

Short answer: The mistake is shipping your Supabase publishable (anon) key in the front end while some tables have Row Level Security turned off, or have policies that allow everyone. Anyone who copies that key from your site can then read or change those tables through the API. To fix it, turn on RLS for every exposed table and add policies that limit each row to its owner.

What Row Level Security does

Row Level Security (RLS) is a Postgres feature that decides, row by row, who can read or change data. Supabase builds its API on top of it. Your app talks to the database straight from the browser with a publishable key (called the anon key in older projects), and RLS policies decide what that key can reach.

Supabase's documentation is direct about this: enable RLS on every table in an exposed schema, such as public. Once RLS is on, no data can be reached through the API with the publishable key until you create policies.

Supabase has two kinds of keys:

KeyPrefix (new / legacy)Where it belongsWhat it can do
Publishablesb_publishable_ / anonWeb pages, mobile apps, front-end codeOnly what your RLS policies allow
Secretsb_secret_ / service_roleServer-side code and environment variables onlyBypasses RLS completely

Supabase documents the publishable key as fine to put in a web page or app. That only holds if RLS is doing its job.

The mistake, step by step

  1. An AI tool or a quick tutorial creates tables, and RLS is never turned on for some of them.
  2. Or RLS is on, but a policy reads using (true) for the anon role. That gives every visitor access to every row.
  3. The publishable key ships in your JavaScript bundle, as it's meant to.
  4. Someone opens developer tools, copies your project URL and key, and queries your tables directly. Your app's interface doesn't matter at that point.

A related mistake is worse: putting the secret (service_role) key in front-end code. Supabase says never to put a secret key in a browser, a shipped app or source control, because it bypasses RLS entirely.

CVE-2025-48757: what happened

This pattern got a CVE in 2025. CVE-2025-48757, published May 30, 2025, describes "an insufficient database Row-Level Security policy in Lovable through 2025-04-15" that let remote, unauthenticated attackers read or write database tables of generated sites. It is rated 9.3 (Critical) under CVSS 3.1.

Developer Matt Palmer reported the issue. According to coverage at the time, he scanned 1,645 web apps built with Lovable and found 170 with the same problem. The exposed data included names, email addresses, financial information and API keys.

The CVE record notes that the supplier disputes it, on the grounds that each customer is responsible for protecting its own application's data. Either way, the lesson applies to any app on Supabase, whatever tool built it: the database rules are the real access control.

How to check your project

1. Open the Security Advisor

In the Supabase dashboard, open the Security Advisor. It flags tables in the public schema that have RLS disabled, with the warning "RLS disabled in public," and many findings come with a one-click fix. Supabase has also said it emails project owners weekly about open security issues, so don't ignore those emails.

2. List tables without RLS

Run this in the SQL Editor:

select schemaname, tablename
from pg_tables
where schemaname = 'public'
  and rowsecurity = false;

Every row in the result is a table the API can reach without row checks.

3. Find policies that allow everyone

select tablename, policyname, roles, cmd, qual, with_check
from pg_policies
where schemaname = 'public'
  and (qual = 'true' or with_check = 'true');

A true policy is fine for data that's meant to be public, like a list of blog posts. It's a problem on tables with user data.

4. Test from the outside

Query a private table using only your publishable key, the way a stranger would:

curl "https://YOUR-PROJECT.supabase.co/rest/v1/notes?select=*" \
  -H "apikey: YOUR_PUBLISHABLE_KEY"

If you get rows back without logging in, that table is exposed.

A minimal owner-only policy

This example locks a notes table so each signed-in user can only see and change their own rows. It assumes a user_id column that stores the owner's auth.uid().

alter table public.notes enable row level security;

create policy "Owners can read their notes"
on public.notes for select
to authenticated
using ( (select auth.uid()) = user_id );

create policy "Owners can add their notes"
on public.notes for insert
to authenticated
with check ( (select auth.uid()) = user_id );

create policy "Owners can update their notes"
on public.notes for update
to authenticated
using ( (select auth.uid()) = user_id )
with check ( (select auth.uid()) = user_id );

create policy "Owners can delete their notes"
on public.notes for delete
to authenticated
using ( (select auth.uid()) = user_id );

Two details matter:

  • Wrapping auth.uid() in select is a performance tip from Supabase's docs. It lets Postgres evaluate the function once per query instead of once per row.
  • with check on insert and update stops users from writing rows that belong to someone else.

After you add policies, run the outside test again. Anonymous requests should now return an empty list.

Quick checklist

  • RLS enabled on every table in public
  • No using (true) policies on tables with personal data
  • Separate policies for select, insert, update and delete where needed
  • Secret (service_role) key only in server-side environment variables
  • Security Advisor shows no RLS warnings
  • Outside test returns no private rows

For the rest of a pre-launch review, including keys, public files and logins, use the 10-minute checklist for AI-built apps. Then run the free security header check and see security headers for vibe coders if your grade is low.

If you're selling the app

Buyers increasingly ask about database rules before they pay. GreenGo's Green Light checks scan delivery files for exposed secrets (with Gitleaks), known vulnerable dependencies and stray files like .env. They don't inspect your database policies, and they aren't a security audit, so check RLS yourself first. Getting this right also helps when buyers decide how much your side project is worth.

If you'd rather have someone else fix it, post a job on GreenGo's Fix-It Desk, where developers fix AI-built apps for a 15% fee.

Sources

Updated October 20, 2026.

Questions & answers