October 19, 2026
Your AI Coding Agent Just Exposed Your Supabase Database—Fix It Now
UpGuard found 16,326 Supabase databases anyone could read. Supabase’s 30 October change won’t close the ones already open. Here are five…

By Junaid Ahmed Zama
6 min read
UpGuard found 16,326 Supabase databases anyone could read. Supabase's 30 October change won't close the ones already open. Here are five checks to run this week.
UpGuard's researchers asked roughly 300,000 websites the same question: is there a table called "users", and will you show it to me? They asked using only the key each site already ships to every visitor's browser. 16,326 databases answered.
_💥 _Master AI & Tech Skills 💰 One Plan. Multiple Courses. More Savings. 🔥 Get Up to 50% OFF | ⏰ Limited-Time Offer 👉 Enroll Now & Start Learning
Over half showed signs of personal data. Ecommerce and restaurant sites were the most likely to expose customer details and to have a payment system wired in. One was a valet company's customer database with phone numbers for more than 100,000 customers and about 78,000 licence plates.
All of them ran on Supabase, the hosted Postgres database that UpGuard says Claude Code recommends more than any other. When TechCrunch asked, Supabase's chief information security officer, Bil Harmer, said its projects are "secure by default" and that security is shared with customers. Both things are true. The problem is which default your app actually got, and on 30 October Supabase changes that default again in a way that won't help the tables already open. Below is how the leak works and five checks that take about ten minutes.
Two locks, not one
A Supabase app in the browser talks to the database directly, through an auto-generated REST API. It authenticates with a publishable key (the older name is the "anon" key) that sits in your JavaScript bundle where anyone can read it. Supabase's docs are clear that this is fine by design.
What protects your data is two layers inside Postgres.
The first is grants: whether a role such as anon (a visitor who isn't signed in) or authenticated (a signed-in user) may run select, insert, update or delete on a table at all. On existing projects, Supabase's docs say a new table in the public schema gets all four, for both roles, automatically.
The second is row-level security (RLS): policies that decide which rows each role can touch, such as "a user can read only orders where user_id is their own ID". The docs put the consequence plainly: a table without RLS is readable and writable by any role with a grant on it.
So a table with the default grants and RLS switched off is a public spreadsheet. Your publishable key is the URL.
Why the agent path skips the second lock
Create a table by clicking in Supabase's Table Editor and RLS is switched on for you. Create it with SQL, through a migration, the CLI or the Management API, and it isn't. UpGuard's report singles this out, because the programmatic path is how coding agents build Supabase backends.
An agent asked to "add a products table and an orders table" writes create table statements and runs them. The app works immediately, because the grants are already there. Nothing fails, so nothing prompts a fix. If the agent does switch RLS on without writing policies, every query returns an empty list, and the quickest way to make the bug go away is a policy that says using (true), which lets every row through.
That's the pattern behind the Moltbook breach in February. Wiz found the site's publishable key in its JavaScript and, with one curl request, read API tokens for its AI agents. The exposure ran to 1.5 million tokens and 35,000 email addresses, and anonymous visitors could also edit posts. The founder had said publicly that he didn't write a line of the code.
What 30 October changes, and what it doesn't
Supabase has seen this coming. In a breaking-change notice on 28 April, it said agents, CLI scripts and AI platforms now create tables without a human reviewing the change, and announced a fix: new tables in public stop getting automatic grants. That became the default for new projects on 30 May. On 30 October it applies to every existing project.
After that date, a new table is invisible to the API until someone runs an explicit grant. That closes the first lock by default, which is a real improvement.
It doesn't touch the tables you already have. Supabase's notice says existing tables keep their current grants and stay reachable. Every one of those 16,326 databases looks the same on 31 October as it does today.
There's a second gap, and this one is my read rather than Supabase's. When a grant is missing, the API returns error 42501 with a hint containing the exact statement to fix it, for example GRANT SELECT ON public.your_table TO anon;. Supabase designed the hint so agents can correct themselves. An agent that copies it without also enabling RLS and writing a policy rebuilds the original leak one table at a time.
So don't wait for the date. Check now.
The five checks
Run these against your own project only. They need the SQL Editor in your Supabase dashboard and a terminal.
1. Run the Security Advisor. Open Advisors → Security Advisor in the dashboard, or run supabase db advisors from the CLI. The findings that matter here are 0013 (RLS disabled in public), 0024 (permissive RLS policy), 0010 (security definer view), 0023 (sensitive columns exposed) and 0025 (public bucket allows listing). Good looks like none of those, or only ones you can explain.
2. List every table, its RLS status and what visitors can do. Paste this into the SQL Editor:
select c.relname as table_name,
c.relrowsecurity as rls_enabled,
has_table_privilege('anon', c.oid, 'select') as visitor_can_read,
has_table_privilege('anon', c.oid, 'insert') as visitor_can_insert
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public' and c.relkind = 'r'
order by rls_enabled, table_name;select c.relname as table_name,
c.relrowsecurity as rls_enabled,
has_table_privilege('anon', c.oid, 'select') as visitor_can_read,
has_table_privilege('anon', c.oid, 'insert') as visitor_can_insert
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public' and c.relkind = 'r'
order by rls_enabled, table_name;Any row with rls_enabled = false and visitor_can_read = true is open to the internet. Good looks like RLS on for every table, and visitor access only on tables meant to be public, such as a product catalogue.
3. Find the policies that let everything through.
select tablename, policyname, roles, cmd, qual, with_check
from pg_policies
where schemaname = 'public'
and (qual = 'true' or with_check = 'true');select tablename, policyname, roles, cmd, qual, with_check
from pg_policies
where schemaname = 'public'
and (qual = 'true' or with_check = 'true');using (true) is fine for data that is meant to be public and nothing else. On an orders, customers or profiles table it means RLS is on in name only. Good looks like every private table filtering on the signed-in user, such as (select auth.uid()) = user_id.
4. Knock on your own door. Do what UpGuard and Wiz did, from outside, with your publishable key:
curl "https://YOUR-PROJECT.supabase.co/rest/v1/orders?select=*&limit=1" \
-H "apikey: YOUR_PUBLISHABLE_KEY"curl "https://YOUR-PROJECT.supabase.co/rest/v1/orders?select=*&limit=1" \
-H "apikey: YOUR_PUBLISHABLE_KEY"Repeat for each private table. Good looks like [] or a 42501 permission denied error. Bad looks like a customer's row.
5. Check the side doors. Three things bypass a correct policy:
- Views. A view runs with its creator's rights and skips RLS unless it was created
with (security_invoker = true), per Supabase's RLS docs. The Security Advisor's 0010 finding lists the risky ones. - The secret key. Your secret key (
sb_secret_..., or the legacyservice_rolekey) bypasses every policy. Search your built frontend for it, for examplegrep -rn "sb_secret_" .next/static dist build, and check that no environment variable holding it has a public prefix such asNEXT_PUBLIC_. - Storage buckets. A public bucket that allows listing lets anyone enumerate your files, including uploaded invoices or ID photos. Advisor finding 0025 flags it.
If you fail a check
Fix in this order, because the first steps close the door and the later ones make sure it stays closed.
- Switch RLS on for every exposed table:
alter table public.orders enable row level security;. With no policies, the API returns nothing, so your app may break briefly. That is the safe failure. - Trim the grants. Revoke what visitors don't need (
revoke all on table public.orders from anon;) and grant back only what each role uses. - Write one policy per operation, scoped with
to authenticatedand filtered on(select auth.uid()). The RLS guide has a template forselect,insert,updateanddelete. - Test it. Supabase now recommends a test file per table that asserts what
anonandauthenticatedcan and can't do, run withsupabase test db. - If the secret key ever shipped, rotate it. Create a new one under Settings → API Keys, move every server to it, then delete the old one.
If a check showed real customer data was readable, treat it as an incident rather than a bug: look at your API logs for unfamiliar requests and find out what your local data-protection rules require you to tell people.
The habit that prevents the next one
The exposures UpGuard and Wiz describe share a shape: a table went live before anyone decided who should read it. The fix is to make that decision part of creating the table.
Put the grant, the enable row level security line and the policies in the same migration as the create table, every time. Tell your coding agent to do the same: add it to your rules file, or install Supabase's agent skill, which Supabase says handles grants correctly. Then run supabase db advisors before every deploy, the way you'd run tests.
Supabase's defaults are getting better. Your agent's habits are the part you control.
Which of the five checks did your last AI-built project fail, and what did the agent say when you asked it why?
Thank you for being a part of the community
Before you go:
👉 Be sure to clap and follow the writer ️👏️️
👉 Follow us: Medium
👉 CodeToDeploy Tech Community is live on Discord — Join now!
Disclosure: This post includes affiliate and partnership links.