On September 25, UpGuard reported 16,326 Supabase databases with publicly readable tables. More than half showed signs of personal data. Some had passwords and tokens.
No one broke in. The data was readable through the front door.
What went wrong
Supabase's anon key ships in your browser code by design. Row-level security (RLS) decides what that key can read.
RLS off means anyone with your project URL and key can read the table. Nothing errors, so nobody notices.
UpGuard says many of the apps appear to be built with AI coding agents, though not in every case.
Check yours in 2 minutes
Turn on RLS and add a policy:
alter table public.profiles enable row level security;
create policy "read own profile" on public.profiles
for select to authenticated
using ((select auth.uid()) = user_id);
Then test as an anonymous user. You should get an empty result or an error, never rows:
curl "https://<project-ref>.supabase.co/rest/v1/profiles?select=*&limit=1" \
-H "apikey: <anon-key>" -H "Authorization: Bearer <anon-key>"
Also keep service_role out of client code, and don't store passwords in app tables.
Same pattern, everywhere
Data rarely leaves through a hack. It leaves because an action moved it somewhere and nothing said so: a missing policy, a debug log, a paste into a random web tool.
I wrote up nine of these: Where Developer Data Actually Leaks
Have you audited RLS on your project lately?
Sources: BleepingComputer, TechCrunch
Top comments (1)
The UpGuard number matches what I see from the repo side of the equation. I scan public GitHub repos for committed Supabase credentials as a routine audit — the 100-repo writeup found 15% with committed secrets and ~25% with permissive RLS — and there's one trap worth adding to the "check yours" section:
RLS enabled ≠ RLS secure. The most common pattern in the repos I scan isn't RLS off. It's this:
That passes every "is RLS on?" dashboard check. The curl test at the end of this post returns full rows, same as no RLS. The UpGuard scan flags it identically — world-readable through the front door, with all the paperwork in order.
So the test that matters isn't "is RLS enabled?" — it's "what does anon actually get?" Your curl check is the right one: if it returns rows, the policy is the problem, not the flag. Anyone who found their table on the UpGuard list needs two steps, not one: enable RLS and make sure no policy says
using (true)toanonon data that isn't intentionally public.On the AI-built-app angle: the reason AI-generated apps end up here is that "add RLS" clears the dashboard warning, and agents (and plenty of tutorials) then add the
using (true)policy so the app still works with the anon key — instead of writing a real ownership predicate like(select auth.uid()) = user_id. App works, warning gone, table still open.