Here is a fact that sounds alarming and is not: every app built on Supabase puts a database key right in the browser, readable by anyone who opens the developer tools. That key, the anon key, is meant to be public. It is the equivalent of a publishable Stripe key. On its own, it exposes nothing.
What actually protects the data behind that key is Row Level Security: database rules that decide which rows each request is allowed to read. With RLS on, the public key can see only what you permit. With RLS off, the public key can read the whole table. So the interesting question is never whether the key is exposed. It is whether RLS is actually on. We decided to measure it.
What we did
We took a set of real, deployed apps built with a popular AI app builder, pulled from public directories of shipped projects. For each one we did what any visitor's browser already does: we read the app's public anon key out of its bundle, then used it to ask the database for a single row from its tables, with no login. If Row Level Security is doing its job, that request comes back empty. If it is off, the database hands back real rows to an anonymous request.
This is not an attack. It is the exact request the app's own frontend makes, using the exact key the app ships to every visitor. We read one row per table only to confirm whether it was readable, stored none of the returned data, and we are naming no apps.
Want to see exactly what Prbl flags? Watch it scan a demo app, no repo or account needed.
See a live scan →What we found
About half of the apps we tested used Supabase and, as expected, shipped the public anon key to the browser. That part is fine. The finding that matters is the next step: on 4 of the 15 Supabase-backed apps we checked, the database answered. More than one in four returned real rows to a request with no login, which means Row Level Security was not protecting those tables and the data was readable by anyone on the internet who looked.
The rest were configured correctly: the key was public, but every anonymous read came back empty, which is exactly what a properly secured Supabase app looks like. This is an early sample, not a census, and we will keep widening it. But better than one in four with an open table is not a rounding error. The point is not that the tool is unsafe. It is that whether any given app is safe comes down to one setup step that is easy to skip and invisible until someone checks.
Why it happens
Row Level Security is off by default from the app's point of view, because turning it on is not required to make the feature work. The builder creates a table, wires up the client, and the app runs perfectly with RLS off. Nothing in the build or in testing signals a problem, since the gap only appears when someone talks to the database directly with the public key. In normal use that never happens. Once the app is public, it can happen at any time. This is the same shape as most of what we find in AI-generated code: the insecure version works, so it ships.
How to check your own
You do not have to wonder which side of this your app is on. Paste your app's URL into the scanner: it finds the exposed key and, on the checks it runs, tells you whether an anonymous request can actually read your tables, instead of leaving you to guess. Then close the gap with the Supabase RLS checklist: enable Row Level Security on every table, add owner-scoped policies, and confirm an anonymous read returns nothing.
Frequently asked questions
Isn't the Supabase key being in the browser the problem?
No, and this is the part that trips people up. The Supabase anon key is designed to be public and shipped to the browser, exactly like a publishable Stripe key. On its own it exposes nothing. What decides whether your data is safe is Row Level Security: database rules that say which rows each request may read. The key is only a problem when RLS is not configured, because then the public key becomes a full read handle on your database.
How did you check without hacking anything?
We used the app's own public anon key exactly the way the app's own frontend does: we asked the database for a single row from a table, with no login. If Row Level Security is on, that request returns nothing. If it is off, the database returns real rows to an anonymous request, which is the same thing any visitor could do. We read one row per table to confirm readability, stored none of the data, and are naming no apps.
Why do AI builders leave RLS off so often?
Because turning it on is a separate step that is not needed for the app to work. The builder creates the table, wires up the client, and the feature functions perfectly with RLS off, so nothing signals a problem during building. Row Level Security only matters when someone talks to the database directly with the public key, which never happens in normal use and always happens once someone looks.
How do I check my own app?
Paste your app's URL into Prbl's scanner. It finds the exposed key and, on the checks it runs, tells you whether Row Level Security is actually protecting your tables, rather than leaving you to guess. Then work through the RLS checklist: enable it on every table, add owner-scoped policies, and re-test that an anonymous read returns nothing.
Check your app in seconds
If your app was built with an AI tool and talks to Supabase, this is the single most valuable thing to verify before anything else. A scan reads what your app serves and checks whether the door is actually locked. Run a free scan and find out which side of this your app is on.