Supabase Row Level Security is not enabled
This table answers unauthenticated read requests from anyone on the internet.
Why it matters
This table answers unauthenticated read requests from anyone on the internet. The anon key needed to query it is published in your own JavaScript, so finding it takes no skill — every row can be downloaded in bulk. This is the single most common critical failure in Supabase-backed apps, because RLS is off by default and nothing in the client warns you.
How ShipReady detects it
Tests whether a Supabase project is readable by anyone. The project URL and anon key are read from the page's own JavaScript — both are published there by design. The anon key is then used exactly as a browser would use it. ──────────────────────────────────────────────────────────────────────────── WHAT THIS USED TO ASK, AND WHY IT MISSED THE APPS IT EXISTS FOR ──────────────────────────────────────────────────────────────────────────── The probe list was twelve hardcoded names — users, profiles, orders, posts. A bakery app whose tables are `bakery_orders` and `pickup_slots` was therefore invisible, however wide open it was, and the report said nothing. That is the whole finding class this check exists for: CVE-2025-48757 documents Lovable+Supabase apps shipping tables with RLS off or set to USING (true), readable by anyone holding the anon key the front end publishes. Resources are now DISCOVERED rather than guessed, from three sources, and each finding records which one found it: client-code `.from("bakery_orders")`, `.rpc("claim_slot")` and `/rest/v1/...` paths in the site's own bundles — the strongest signal, because the application itself names the resource it depends on. postgrest-schema the project's own OpenAPI document at /rest/v1/, which PostgREST publishes to any holder of the anon key. This is schema METADATA — path names — not data. common-name the original twelve, kept as a supplement so a project whose bundle is unreadable is no worse off than before. ──────────────────────────────────────────────────────────────────────────── NO ROW IS EVER RETRIEVED ──────────────────────────────────────────────────────────────────────────── The old probe was `GET ...?select=*&limit=1` and it stored the returned row in `evidence.observed_response`. It proved the point by holding a copy of the customer's data — an email address, an address, whatever the first row held — in our logs and our database. The probe is now `HEAD` with `Prefer: count=exact`. A HEAD response carries no body at all; PostgREST answers with `Content-Range: 0-0/1523`, which is the count and nothing else. So the finding says "1,523 rows are readable with a key published in your own JavaScript" and the scanner has not read one of them. Returning zero rows is still not a finding. A table an anon key can reach but whose rows are all filtered is what correctly configured RLS looks like, and it is indistinguishable from an empty table — so neither is reported. WRITE ACCESS IS NOT TESTED, deliberately. Establishing it means attempting an insert against a stranger's production database, and a table whose columns are all nullable would take the row. There is no non-destructive proof available from outside, so the finding states that read access was tested and write access was not, rather than implying both. The probe sequence is memoised on the discovered project, so a project referenced by ten pages is tested once rather than ten times.
Detection is deterministic. ShipReady reports this only when it observes the condition directly, and prefers to miss a real problem over inventing one. Rule version 1.3.0.
How to fix it
This is the prompt ShipReady puts in your report — written to be pasted straight into Cursor, Claude Code, or whichever assistant built the app.
Row Level Security (RLS) is either missing or overly permissive for the affected table. Anyone with the anon key can read its contents. Enable RLS and define strict policies.
Frequently asked questions
- What does "Supabase Row Level Security is not enabled" mean?
- This table answers unauthenticated read requests from anyone on the internet.
- How serious is it?
- ShipReady rates this critical. Fix before launch. On its own, this is enough to compromise the application.
- How do I fix it?
- Paste the fix prompt on this page into Cursor, Claude Code or your AI editor. It is the same prompt ShipReady puts in your report.
- Can I check my own site?
- Yes — ShipReady scans up to ten pages of any public site for free and reports this alongside every other check. The free report lists every issue it finds and shows full evidence and a fix prompt for the critical and high-severity ones; medium and low findings are counted and unlock on Pro.
Related checks
Run this check on your site
ShipReady checks this and 193 other things across up to ten pages of your site, with an AI-ready fix for each. Free, no signup.