← All posts
·8 min read·GuardLayer

The Lovable RLS Vulnerability (CVE-2025-48757), Explained

LovableSupabaseRLSCVEAI

CVE-2025-48757 is what happens when an AI builds a working app on top of a Supabase database it never locks down. In May 2025, security researcher Matt Palmer disclosed that Lovable-generated apps were shipping with missing or insufficient Supabase Row Level Security — so anyone holding the public anon key (the one that ships in every browser bundle by design) could read, and in many cases write, tables full of user data. His analysis found 303 endpoints across 170 production Lovable apps leaking this way. No login, no exploit chain. The fix is one line of SQL per table — and you can check your own app in about a minute.

If you've shipped anything with Lovable, Bolt, v0, or Cursor on top of Supabase, this is the single most important thing to verify before you call the app "done."

What is CVE-2025-48757?

CVE-2025-48757 is a critical vulnerability class in apps generated by Lovable, the AI app builder, when paired with a Supabase backend. The official CVE record describes it plainly: "An insufficient database Row-Level Security policy in Lovable through 2025-04-15 allows remote unauthenticated attackers to read or write to arbitrary database tables of generated sites."

The CVE record on NVD carries a CVSS 3.1 base score of 9.3 (Critical), vector AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:N — network-reachable, no privileges required, no user interaction. Note the I:L: this isn't a read-only leak. The same hole that lets an attacker pull rows also lets them write to tables with no policy blocking it.

Two honest caveats, because this is a security brand and the details matter. The 9.3 is the CNA (MITRE) score — at time of writing NVD had not published its own re-enriched assessment, so treat the 9.3 as the assigning authority's number, not an independent NVD rescore. And the CVE is formally marked "Disputed": Lovable's position is that each customer is responsible for securing their own application's data. That dispute is, in a sense, the whole point. The platform generates the app, but you own the security boundary, the same way you would for code a junior wrote at 2am. There's no patch to install — the mitigation is something you do to your own database.

How did 170 apps leak the same way?

The root cause is structural, not a few sloppy users — and it comes down to how Supabase exposes data. Every table in the public schema is reachable through an auto-generated PostgREST API, and the only credential needed to hit it is the anon key — which is designed to be public and ships in your client-side bundle, along with your project URL. Neither is a secret. The single thing standing between an anonymous visitor and the rows in a table is Row Level Security.

When you create a table without RLS — or with a policy that doesn't actually scope rows to the right user — that table becomes a public API endpoint. Anyone can run this from a laptop:

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

And get back every row.

Here's why the AI builder walks straight into it: Lovable, like every tool of its kind, optimizes for "it works in the preview." It generates a schema with create table, wires up a polished frontend, the dashboard query returns rows, and the demo looks perfect. A missing RLS policy is invisible in that demo — the app behaves identically whether the table is locked or wide open. The security boundary is the one part of the build that produces no visible result, so it's the first thing left undone.

Matt Palmer's analysis put a number on it: 303 endpoints across 170 production Lovable apps — roughly 10% of the projects he scanned — exposing data to unauthenticated requests, including emails, names, and third-party API keys and access tokens the apps had stored. Just the documented Supabase API doing exactly what it was told, against tables nobody locked.

This is the same failure mode behind the broader wave of AI-built app data leaks — CVE-2025-48757 is simply the best-documented instance of it.

Note that the CVE covers "insufficient" RLS, which is really two distinct failures. An app leaks either because RLS was never enabled on a table the model created, or because the AI generated a policy that looks like access control — typically using (true) — but doesn't actually filter rows by user. That second one is the using (true) trap, and it leaks just as badly as no policy at all.

The one-line-per-table fix

You don't disable anything, and you don't wait for a vendor patch. You enable RLS and write the policy that grants the access your app genuinely needs.

-- Keep the gate ON for every table in public.
alter table public.profiles enable row level security;

-- Grant exactly what the app needed: a user sees only their own row.
create policy "Users read own profile"
  on public.profiles
  for select
  to authenticated
  using ( (select auth.uid()) = user_id );

auth.uid() returns the authenticated user's UUID and null for the anon role, so anonymous callers fail the comparison and get nothing back. Wrapping it as (select auth.uid()) lets Postgres evaluate it once per query instead of once per row — Supabase's own recommended pattern for RLS performance, with no change to the security behavior.

If a table is genuinely meant to be world-readable (a public blog_posts table, say), still keep RLS on and write an explicit read-only policy — for select ... using (true) to anon, authenticated. That documents the intent and still blocks the anonymous writes the I:L in the CVE vector is about. The difference from a disabled table is enormous: this grants SELECT only, on purpose, while a disabled table grants INSERT, UPDATE, and DELETE too, by accident.

A 60-second self-check on your own app

Run this before you tell anyone your Lovable app is finished.

1. Ask Postgres which public tables have RLS off right now. Paste this into the Supabase SQL editor:

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

Anything this returns is reachable by anon this second. If you didn't intend that table to be public, it's exposed.

2. Test it like an attacker would. Grab your project URL and anon key from Supabase settings, pick a table that should be private, and try to read it with nothing but the public key:

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

If you get rows back without logging in, that table is leaking to the entire internet. An empty array or a permission error is what a properly locked table looks like.

3. Audit your policies, not just the RLS flag. A table can have RLS on and still leak via a using (true) policy. List your policies and confirm each filters by auth.uid() where it should:

select tablename, policyname, qual
from pg_policies
where schemaname = 'public';

Catch this before you push, not after

The self-check above is a great one-time audit. The problem is that your next AI prompt — "add a comments feature" — creates a new table, and you're back to square one without realizing it. The hole is silent by definition: no error, no failing test, no deploy warning.

That's the gap GuardLayer closes. It statically scans your Next.js + Supabase code and migrations on every push — flagging tables created without RLS, disable row level security statements, using (true) policies, and service-role keys exposed to the client — and posts the findings on your pull request before the change can merge.

To be straight about scope: GuardLayer reads your source statically. It finds the missing or insufficient RLS in the code you're about to ship; it does not run taint analysis, watch your app at runtime, or live-probe your deployed endpoints. CVE-2025-48757 is precisely the kind of pattern that shows up plainly in code — which is why catching it pre-push is the cheapest place to catch it. Run a free scan on your repo and find out what your AI builder left open.

FAQ

Is the code Lovable generated secure? The frontend usually works fine. The risk is the database boundary: Lovable creates Supabase tables and a polished UI, but whether each table has a correctly scoped Row Level Security policy is on you. CVE-2025-48757 found 303 endpoints across 170 Lovable apps leaking because that policy was missing or too loose. Run the self-check above before trusting any AI-built Supabase app.

What exactly is CVE-2025-48757? A critical vulnerability class (CVSS 9.3, the CNA/MITRE score) in Lovable-generated apps through 2025-04-15, where insufficient Supabase RLS let unauthenticated attackers read or write arbitrary database tables using only the public anon key. Disclosed by Matt Palmer in May 2025. It is formally "Disputed" by the vendor on shared-responsibility grounds.

Isn't the anon key secret? How would anyone reach my tables? No. The anon key is public by design and ships in your browser bundle, along with your project URL. RLS is the protection layer — not the key. With RLS off or misconfigured, the anon key is all an attacker needs. There's no patch to wait for: the fix is enabling RLS on every public table and scoping policies with using ( (select auth.uid()) = user_id ).

Does enabling RLS break my app? No — it changes "everyone can read this table" to "only the right user can." If a query starts returning empty after you enable RLS, that's the signal you were missing a policy. Add a scoped one rather than disabling RLS to make the error go away.

I already shipped a leaking table. What now? Enable RLS immediately, add the correct policies, and treat the data as exposed for the entire window it was open. Rotate any third-party API keys that were stored in exposed tables, and check your Supabase logs for anomalous PostgREST traffic against that table. </content> </invoke>

Catch this before it ships — free

GuardLayer scans every push for this and 19 other Next.js + Supabase issues, with the exact fix inline.

Keep reading