Supabase Row Level Security is off, or has no policy
What you'd see: Your app works, and the Supabase dashboard shows "RLS disabled" or "No policies" next to your tables.
§ 01 — What's actually happening
Supabase gives your frontend a public anon key and talks to Postgres directly. The only thing standing between that key and your entire table is Row Level Security — per-row rules Postgres enforces on every query. With RLS off, the anon key is a read (and often write) key for everything in the table. Anyone can open DevTools, copy the key out of your JavaScript bundle, and query your database from their own terminal.
Why an AI tool writes it this way
RLS blocks queries by default, so a half-configured policy makes the app appear broken — data stops loading. The fastest way to make the error go away is to turn RLS off, and that is very often what gets suggested when you paste the error into a chat window. The app immediately works, and nothing ever tells you what you just opened up.
What it costs you
Complete disclosure of every row: customer names, emails, messages, orders, uploaded documents. This is the single most common critical finding we see in Supabase applications, and it is also the easiest one for an outsider to discover — the key is sitting in your published JavaScript.
§ 02 — Before & after
Every user can read every other user's data
Illustrative code, written for this page — never a client's project.
AI-generated
- RLS disabled, so the anon key can read the entire table
- Filtering happens in the frontend, where the user controls it
- Anyone can re-run the same query without the filter
Human-reviewed
- RLS on, with an explicit policy per operation
- Postgres enforces ownership on every query, whatever the client sends
- A dropped
.eq()in the frontend leaks nothing
§ 03 — Check your own
How to tell in two minutes
You don't need us to run these. If any of them come back the wrong way, you have this problem.
- In the Supabase dashboard open Table Editor. Any table showing "RLS disabled" is fully readable with your public key.
- A table can have RLS enabled and still be wide open if a policy uses
using (true). Read the policy, not just the toggle. - Check every operation. It is common to see a SELECT policy with no INSERT, UPDATE or DELETE policy — or the reverse.
- Search your codebase for
service_role. That key bypasses RLS entirely and must never appear in frontend code or in any variable prefixedNEXT_PUBLIC_orVITE_.
Found it in your project? Fixing this one properly usually takes an engineer under an hour. Finding the other nine takes longer — which is what the free health check is for.
Get my free health check →§ 04 — Related
Other things we find
Stripe webhook accepts unverified events
Your webhook route reads the request body and acts on it directly.
Read the fix → CRITICAL Exposed credentialsSecret keys shipped to the browser
You have an environment variable named something like NEXT_PUBLIC_OPENAI_API_KEY or VITE_STRIPE_SECRET_KEY.
Admin routes protected only by hiding the button
Your admin area is protected by something like {user.isAdmin && <AdminPanel />}.