Your AI-Built Supabase Is One Curl Away From a Leak. Eight Checks for Tonight.

The catch: tables created in the dashboard's Table Editor get RLS by default. Tables created with SQL don't, and SQL is how an agent creates tables when it writes your migrations.
In late January 2026, researchers at Wiz looked at Moltbook, a social network for AI agents whose founder had posted that he didn't write a single line of its code. With the key from the site's JavaScript and RLS missing, they could read and write the platform's data, 1.5 million API keys and 35,000 email addresses included. Moltbook fixed it within hours. Most founders won't get a polite email from a security firm first.
One warning: the dashboard's SQL editor runs as the postgres role by default, which skips RLS. Everything looks normal from there, and that's how these holes survive.
1. Run the curl yourself
Open your site logged out and, in the Network tab, copy the apikey header from any request to supabase.co. Then ask for a table the way a stranger would:
curl "https://<project-ref>.supabase.co/rest/v1/<table>?select=*&limit=5" \
-H "apikey: <the key from your site>"An empty array or a permission error is what you want. Rows mean anyone on the internet can read that table. Try every table with personal data in it.
2. RLS is on for every table
select tablename from pg_tables
where schemaname = 'public' and not rowsecurity;Every name returned is a table the public key can reach with no rules. Turn RLS on, then write the policies. Until you do, the app breaks loudly instead of leaking quietly. That's the better failure.
3. No policy says "everyone"
select tablename, policyname, roles, cmd, qual, with_check
from pg_policies
where schemaname = 'public' and (qual = 'true' or with_check = 'true');USING (true) lets every row through. With roles set to {public}, that includes anonymous visitors. With {authenticated}, it includes anyone who signs up. Agents write these to make an error go away. Fine on published posts, not on users or payments.
Also look for policies that read user_metadata. Users can edit their own, so a role stored there is one anyone can give themselves.
4. The service key never reaches the browser
The legacy service_role key, and any secret key starting with sb_secret_, skips every policy you have. It belongs on the server.
grep -rnE "(NEXT_PUBLIC|VITE)_[A-Z_]*(SERVICE_ROLE|SECRET)" . --exclude-dir=node_modules
grep -rlE "sb_secret_|<last 20 characters of your service_role key>" .next/static dist 2>/dev/nullThe first line finds a key behind a browser prefix. The second searches the built bundle for the key itself (use the end of a legacy key, since anon and service keys start alike). Supabase rejects secret keys sent from a browser, which stops mattering once someone pastes one into curl.
Get the eight checks for your project
Give ChatGPT your tables and buckets, and it will write each check as a query or command for your project.
“|”
5. Views and functions play by different rules
A view created by the postgres role runs with that role's rights and skips RLS, unless it was created with (security_invoker = true) on Postgres 15 or later. A function marked SECURITY DEFINER runs as its owner, and in the public schema it can usually be called over the API. Each one this query returns needs its own check of who's calling.
select proname from pg_proc
where pronamespace = 'public'::regnamespace and prosecdef;6. Buckets are private unless they really are public
select id, public from storage.buckets;A public bucket serves any file to anyone with the URL. Fine for product images, not for invoices or ID scans. Private buckets need policies on storage.objects and hand files out through signed URLs.
7. Edge Functions check who is calling
Edge Functions verify a JWT by default, but the legacy anon key is itself a valid JWT, so that only proves the caller has your public key. Each function that touches user data has to work out which user is calling and whether they may. Also search supabase/config.toml for verify_jwt = false.
8. The Security Advisor is clean, and stays clean
The dashboard's Security Advisor flags most of the above, from tables without RLS to policies that let everyone in. Run it now, and again after every migration an agent writes.
You'll also see advice to restrict CORS. CORS is a rule browsers follow. curl doesn't.
If something failed tonight
If the curl returned rows, turn RLS on for that table now, even if the app breaks until the policy exists. A service key in the browser is a leaked key: replace it, or for legacy keys, move to the new ones and switch the old ones off. The rest can wait until morning, not until next sprint.
Failed two or more of these?
We audit and fix AI-built apps at a flat price. Rescue audit and plan from $500: a written audit in 48 hours and a flat-price plan for the fix. Send your email and we'll set up a 30-minute call within 24 hours.
The eight checks on one card
- The anonymous curl returns nothing private.
- No public table has RLS off.
- No
USING (true)on private data, no trust inuser_metadata. - No service key in the browser or the bundle.
- Views use
security_invoker; definer functions check the caller. - Only public buckets are public.
- Every Edge Function knows its user.
- The Security Advisor is clean after each migration.
This isn't only a Supabase problem. At Oria, a platform built fast with AI coding agents, organizations could see each other's data too. The stack changes. The missing check doesn't.
Related reading
- From Enrichlead to Moltbook: a short tour of the vibe-coded graveyard: Moltbook and other public failures
- The 30-minute vibe-coded app audit: run it yourself first: the wider self-check, beyond the database
- We inherited an AI-generated codebase. Here is what we found: cross-organization access in a real rebuild
Built on Supabase with Lovable or Bolt? Start there. The service: Vibe Code Rescue.
Enjoyed this article? Share it with others
Related Posts

The 30-Minute Vibe-Coded App Audit: Run It Yourself First
Twenty-five yes-or-no questions about your repo, logins, data, payments, secrets and error tracking. Most need a dashboard, not a terminal. Thirty minutes and one score tell you whether you need help.

What a $500 Code Audit Report Looks Like (Sample Inside)
Before you pay $500 for an audit, you should see what comes back. Here's a sample built from a real AI-built codebase, details changed: six findings with severity, a keep, fix or replace verdict for each, and a plan with one fixed price per phase.

Anatomy of a Vibe-Coded SaaS: Eleven Things You Can Grep For Before Lunch
You don't need to read every file of a vibe-coded SaaS to know how it was built. Eleven greps, about thirty minutes: six show how the project was run, five find damage that breaks production or leaks data. Commands included.