Supabase turns row level security on for you, if you create the table in the dashboard. Create it with SQL and it's off, and the key that reads it is already in the browser bundle. A code generator has no dashboard to click. It writes SQL. That's one way a scan of Lovable-built apps found passport records readable without logging in.
What Happened
German security firm Crissy Field took 159,041 Lovable projects from Common Crawl, per Cybernews. 34,545 ran on Supabase. 24,664 of those returned records with no authentication. Email addresses were readable on 7,403 sites. One service provider exposed over 1,000 passport records; one forum, over 200,000 chat messages and 80,000 profiles.
Nothing was broken into. The researchers loaded each app without logging in and made read-only requests with the publishable key the site already ships. That key is supposed to be public. In Supabase's model, the database policy is the authorization layer -- the docs say that until RLS and a policy exist, "anyone holding your project's publishable key can read and write every row in it."
And it isn't a Lovable quirk. UpGuard found 16,326 Supabase databases with readable tables in September, on ordinary sites. Lovable's own answer, as reported, is that this is a configuration problem common across the industry. That's true, and it's the point.
The Default Belonged to the Human Path
The same docs page has both halves: "The Table Editor enables row level security for you when you create a table in the Dashboard." And: "When you create a table with SQL, enable it yourself."
That safe default works only for a person clicking a button. A migration file doesn't click anything. Neither does an agent writing one. So the protection exists on the path a human takes and is missing on the path generated code takes -- and more and more tables are coming from generated code.
This is why I think developer-first security is in its most dangerous period right now, not its least. AI is producing far more code than before, and it doesn't yet reliably apply the right thing. My bet is that in 3-4 years it will. Today, a guardrail that lives in a UI protects nobody who doesn't use the UI.
The fixes so far stay on the human side too. On Nov 12 Lovable started requiring a review before publishing with unresolved critical findings -- but users can publish anyway, and only Enterprise can hard-block, according to Privacy Affairs. A warning you can click past is a preference, not a control.
Put the Default in the Database
First, find what's open. This runs in the Supabase SQL editor or any psql session:
select schemaname, tablename
from pg_tables
where schemaname = 'public' and not rowsecurity;
Every row is a table the publishable key can read, as long as the API role has access to it, which is the normal setup. Then make the database turn RLS on for every new table, whoever writes the SQL. This is adapted from the event trigger in Supabase's docs (theirs adds logging and error handling):
returns event_trigger
language plpgsql
security definer
set search_path = pg_catalog
as $$
declare
cmd record;
begin
for cmd in
select * from pg_event_trigger_ddl_commands
where command_tag in ('CREATE TABLE', 'CREATE TABLE AS', 'SELECT INTO')
and schema_name = 'public'
loop
execute format('alter table if exists %s enable row level security',
cmd.object_identity);
end loop;
end;
$$;
create event trigger ensure_rls on ddl_command_end
when tag in ('CREATE TABLE', 'CREATE TABLE AS', 'SELECT INTO')
execute function rls_auto_enable;
After this, create table in public comes out with RLS on, whether a person, a migration or an agent ran it. It covers new tables in public only, so fix the ones the first query found by hand, and keep in mind views don't go through RLS by default. It's a default, not a lock: anyone with enough privilege can still turn it off afterwards.
Then look for policies that let everything through, because RLS with a policy of true is the same as off, for the roles and commands that policy covers:
select tablename, policyname, roles, cmd
from pg_policies
where schemaname = 'public'
and (qual = 'true' or with_check = 'true');
Existence isn't correctness. When Matt Palmer reported CVE-2025-48757 in 2025, his complaint was that Lovable's scanner checked that a policy existed, not that it was right.
The Tradeoff
RLS on with no policy denies the public key everything. Turn it on for an app that works today and the app breaks until someone writes real policies, and policies are code that someone has to get right. That's the cost of moving the boundary to where it actually is. Some public tables (a product catalog) really should be readable -- write that down as a policy, on purpose.
Longer term, agents need defaults built for how agents work, in the layer they actually write to. Today that layer is the database.