Database tables anyone can read
A Supabase table is readable by anyone when Row Level Security (RLS) is off, or when a policy allows every visitor, because your app's anon key is public by design. In our scan of public GitHub repos, 42% of Bolt apps and 15% of Lovable apps shipped at least one table like this. The fix: enable RLS on every table in the public schema and write policies that match rows to auth.uid().
Where it shows up: Supabase (Lovable, Bolt, v0 and most AI builders use it by default)
Last updated
Are you affected? The 60-second check
In the Supabase dashboard, open Advisors → Security Advisor. Every "RLS disabled in public" warning (lint 0013) is a table open to the internet.
RLS can be on and still leak through a loose policy, so test what a stranger sees. Run this against your own project, using only the anon key from your site's code:
An empty list ([]) means you're blocked or the table is empty. Rows coming back from a private table mean anyone can read them. Or let Shippable test every table your app uses in one go: npx shippable-dev scan localhost:5173.
What it is
Supabase apps talk to the database straight from the browser. Two values make that work, and both ship in your site's code: your project URL and the anon key (also called the publishable key). Neither is a password. The anon key identifies your project; it doesn't protect it.
Row Level Security (RLS) is what protects it. RLS is a Postgres feature: rules attached to a table that decide, row by row, what each request may read, insert, update or delete. With RLS off, every row is available to anyone holding the anon key, which means everyone.
AI builders get this wrong in a few predictable ways. Tables created with SQL or migrations start with RLS off, and Bolt and Lovable create tables through migrations, so every new table is a fresh chance to forget. A SELECT policy of USING (true), often added to make a permission error go away, is the same as RLS off for reads. A policy of auth.role() = 'authenticated' lets any signed-up user read every other user's rows, and sign-up is usually open to anyone. And an "owner can update own row" policy on a profiles table that also holds role or plan lets users promote themselves.
The app works perfectly in testing either way, which is why nobody notices.
How often we see it
We ran Shippable's static checks on about 1,460 public GitHub repos built with five AI tools (October 2026). Each figure is the share of that builder's repos where the check fired at least once. A dash means it wasn't among that builder's most common findings. Full method and breakdown: Which AI builder ships the safest apps?

What an attacker sees
Your site's code contains your database address and public key. With those two, anyone can ask your database for every row of an open table, including other users' emails, messages or orders, without ever logging in. No hacking skills are involved; it's a normal API request. This isn't hypothetical: CVE-2025-48757 documented exactly this pattern across apps built with Lovable in 2025, with 170 of the 1,645 apps tested exposing user data.
How to fix it
Paste this into your AI tool:
Security fix: my Supabase tables must not be readable by anonymous users. Enable Row Level Security on every table in the public schema and add policies so each user can only read and write their own rows (match auth.uid() to the owner column). Do not allow anonymous or cross-user access. Tell me which tables are meant to be public.
Or by hand:
- In Supabase, open Table Editor and check each table for the RLS badge. Enable it wherever it's off.
- In Authentication → Policies, make sure no policy uses `true` for reads on private data.
- Tables that really are public, like a product catalogue, can keep a read-only USING (true) policy. Make that a deliberate choice, and never give such a table INSERT or UPDATE for anon.
- Re-scan to confirm every table now refuses anonymous reads.
Lovable
Paste the prompt above into Lovable, then open the Supabase dashboard and confirm every table shows RLS enabled. Run the curl check too: a policy can be on and still be too loose.
Bolt
Bolt writes SQL migrations in supabase/migrations/. Search them for CREATE TABLE and make sure each one is followed by ENABLE ROW LEVEL SECURITY. Search for USING (true) and TO anon, and remove any you didn't mean to add. Then apply the migrations again.
v0, Cursor and hand-written apps
The same SQL applies. If you only call Supabase from server code with the service_role key, RLS isn't protecting those calls at all: keep that key server-only (see leak #2) and still enable RLS so the anon key can't reach the tables.
Mistakes that look fixed but aren't
- RLS is enabled, but a leftover USING (true) policy still allows reads.
- Reads are locked, but INSERT or UPDATE is still open to anon.
- auth.role() = 'authenticated' is used as if it meant "the owner". It means "anyone with an account".
- A view or a SECURITY DEFINER function returns the same rows while skipping RLS (see leak #6).
- The owner can update their own row, and that row holds role, plan or credits.
Check your app
Shippable finds your Supabase address and public key in your site's code, then asks each table for a few rows as an anonymous visitor. If rows come back, the table is open, and you get the table and column names as proof. It never shows the values and never writes anything.
Scan your dev server before you push. No account needed:
Or scan your live app in the browser:
FAQ
Is the Supabase anon key safe to expose?
Yes, it's designed to be public. It's only safe if Row Level Security protects every table, view, function and storage bucket, because anyone can use the key to query your database.
Does Supabase enable RLS by default?
Not for tables created with SQL or migrations: those start with RLS off. The dashboard's Table Editor turns it on for new tables, but AI builders mostly create tables through SQL, so check every table.
What happens if RLS is enabled with no policies?
All access through the API is denied for anonymous and signed-in users. That's safe, but your app stops working until you add policies for the access you intend.
Does the service_role key respect RLS?
No. It bypasses RLS completely, so it must never appear in browser code or in a VITE_ or NEXT_PUBLIC_ variable.
How do I know if someone already read my data?
Check the API logs in the Supabase dashboard for requests to the exposed table that didn't come from your app's normal traffic. If the table held personal data, treat it as exposed and follow your local breach-notification rules.