Every Supabase app ships a database key to the browser. That key, the anon key, is meant to be public, so its presence is not the problem. The problem, when there is one, is that Row Level Security was never turned on, which means that public key can read your whole database. You cannot tell which situation you are in by looking at the app, because it works the same either way. You have to check, and checking is quick.
Step 1: find your Supabase URL and anon key
Open your deployed app, open the browser developer tools, and look in the network tab or the page source for two values: a URL like https://xxxx.supabase.co and a long token that starts with eyJ. Both are already public in your app's bundle. Confirm the token is the anon key and not the service_role key, which should never be in the client at all; you can tell them apart here.
Want to see exactly what Prbl flags? Watch it scan a demo app, no repo or account needed.
See a live scan →Step 2: ask the database for a row, with no login
Now use that public key exactly the way an anonymous visitor could. Pick a table you expect to hold private data, for example profiles or users, and request a single row:
curl "https://xxxx.supabase.co/rest/v1/profiles?select=*&limit=1" \ -H "apikey: YOUR_ANON_KEY" \ -H "Authorization: Bearer YOUR_ANON_KEY"
Read the response. There are three outcomes, and only one is a problem:
- An array with a row in it (like
[{"id":1,...}]) means RLS is off. Anyone can read this table. This is the exposure. - An empty array (
[]) means RLS is protecting the table. This is what you want. - A 404 or "relation does not exist" means that table name is not there; try another (Supabase often hints a real table name in the error).
Run it against every table that holds user or private data. If any of them returns rows, that data is readable by the whole internet.
Step 3: close it
For any table that returned rows, enable Row Level Security and add a policy that scopes access to the row's owner, then re-run the check and confirm it now returns []. The full walkthrough is in the Supabase RLS checklist. Do it for every private table, not only the one you caught.
This is not a rare problem. When we ran this exact check across real deployed apps built with AI tools, more than one in four of the Supabase-backed ones had at least one table an anonymous request could read. The write-up is here.
Frequently asked questions
Is it bad that my anon key is in the browser?
No, that is intended. The Supabase anon key is designed to ship to the client, like a publishable Stripe key. Finding it in your bundle is expected and not a problem on its own. The question that matters is whether Row Level Security is configured, because the anon key can only read what your policies allow. This guide checks exactly that: whether the public key can actually read your data.
What does it mean if a table returns rows to my test?
It means Row Level Security is not protecting that table, so anyone with your public key, which is everyone who visits your site, can read it. That is a real exposure. It may be intentional for genuinely public data like a list of published blog posts, but for anything user-specific or private it is a leak you want to close by enabling RLS with owner-scoped policies.
What if the table returns an empty array?
An empty array means the request was allowed but returned no rows, which is what a properly protected table looks like to an anonymous request: RLS filters everything out. Empty is good. You want your private tables to return [] to a logged-out request. If they return actual rows, RLS is off or too permissive.
I found a table that is open. What do I do?
Enable Row Level Security on that table and add a policy that scopes rows to their owner, for example allowing a select only where the row's user_id matches the authenticated user. Then re-run the check and confirm an anonymous read now returns nothing. Do this for every table that holds user or private data, not just the one you found.
Or let the scanner do it
The manual check works, but it only covers the tables you think to try. Paste your app's URL into Prbl and it finds the exposed key and checks whether your tables are readable for you, then tells you exactly which are open. Run a free scan and find out which side of this your app is on.