Is Supabase Safe? 55% of Supabase Apps We Scanned Had an Open Table
- Focus
- Supabase
- Risk
- Critical
- Stack
- Supabase
- Detection
- Ubserve Runtime Simulation

Supabase is SOC 2 Type 2 compliant and safe to build on. Of 117 Supabase apps we scanned, 55% had at least one table a logged-out visitor could read.
The anon key in your frontend bundle is supposed to be there. Supabase says so. The question that decides whether your app is safe is what that key can reach, and for most of the apps we scan the answer is too much.
Secure your vibe-coded app with Ubserve
- ✓Takes less than 60 seconds
- ✓100+ security checks run through your app
- ✓Plain English explanations for each issue
- ✓AI fix prompts for every issue
Supabase is safe to build on. Supabase's security page states it is SOC 2 Type 2 compliant and HIPAA compliant, and Postgres sits underneath it. Your app is the variable. Across 117 apps where our scanner detected a Supabase anon key in the frontend, 64 of them, or 55%, had at least one table a logged-out request could read.
The anon key is not your problem
Almost every article about Supabase security opens with a warning about keys in your bundle. That warning is misdirected, and it sends people to fix the wrong thing.
Supabase's API key documentation is explicit that the publishable or anon key is what browsers and mobile clients are supposed to use. It identifies the project. It is not a secret. You can read it out of any Supabase app's JavaScript in about ten seconds, including apps built by people who know exactly what they are doing.
What matters is the security model around it. Supabase's Row Level Security documentation states that once RLS is enabled, no data is accessible through the API using a publishable key until you create policies. That is the entire model in one line. The key gets a request to the door. Row Level Security decides which rows open.
So the useful question is not "is my anon key visible." It is "what can my anon key read." Those two questions give different answers in 55% of the Supabase apps we have scanned.
What we found across 1,141 apps
These numbers come from Ubserve's production scan history: 1,889 scans across 1,141 distinct apps, taking the most recent scan of each app. One detail about method matters for reading them. Our scanner can only test a Supabase table when it finds the project URL and anon key in the frontend, because that is exactly what an outsider would use. So the 117 apps with a detectable anon key are the Supabase apps we could actually test.
| Finding (latest scan of each app) | Apps |
|---|---|
| Supabase anon key detected in the frontend | 117 |
| Table readable by a logged-out request | 56 |
| Table exposed without auth, rated critical | 12 |
| Supabase JWT key flagged in the frontend | 8 |
| Service-role key, or its variable name, visible in the frontend | 7 |
| Service-role key reference in a connected GitHub repository | 4 |
Of the 117 apps with a detectable anon key, 64 had at least one table readable or exposed without authentication. Every one of those 64 also shipped the anon key, which is precisely the point: the anon key is how a stranger reaches the table.
Two things stand out.
The first is the ratio. 64 of 117 is not an edge case, it is the more common outcome. If you have shipped a Supabase app without testing a logged-out request against your tables, the base rate says you are more likely than not to have one open.
The second is how rare the thing everyone warns about actually is. Seven apps had a service-role key, or the name of the variable holding one, visible in the frontend. The failure mode that dominates is not a leaked admin credential. It is a policy that was never written, or was written wrong.
The tables left open most often
This is the list we have not seen published anywhere else. Across the apps with an open table, these are the table names we found readable by a logged-out request, limited to names that appeared on at least two apps.
| Table name | Apps where it was readable | Of those, rated critical |
|---|---|---|
profiles |
37 | 5 |
users |
17 | 5 |
subscriptions |
12 | 0 |
orders |
8 | 0 |
payments |
6 | 1 |
transactions |
6 | 2 |
customers |
4 | 1 |
invoices |
2 | 0 |
accounts |
2 | 0 |
An app can appear in more than one row, so these do not add up to 64.
Why profiles leads by so much. A very common pattern in Supabase tutorials and starter templates is a profiles table keyed to the auth user, with a select policy that lets everyone read it so avatars and display names load on public pages. That is a reasonable decision on day one. Then the table grows. An email column gets added for notifications, a plan column for billing, a role column for an admin page, and the policy that made sense for avatars now publishes all of it.
Billing tables are the second story. Subscriptions were readable on 12 apps, orders on 8, payments and transactions on 6 each. These are the tables that tell a stranger who your customers are and what they pay you.
If you only test one table today, test profiles. If you take payments, test the billing tables next.
The three policy mistakes we see most
Enabling RLS is the easy half. With RLS on and no policies, nothing is accessible, which is safe and also a broken app. So people write policies, and the policies are where it goes wrong.
1. Checking that someone is logged in, not that the row is theirs
This is the single most common one. A policy like this looks correct and passes every test you will run as a logged-in user:
create policy "users can read orders"
on orders for select
using (auth.uid() is not null);
It says any authenticated user may read any row in orders. On an app with open signup, being authenticated means nothing: an attacker creates an account and reads every customer's orders. Firebase apps make the identical mistake with request.auth != null, which our Firebase Security Rules guide covers. The fix compares the row to the caller:
create policy "users can read their own orders"
on orders for select
using (auth.uid() = user_id);
2. A USING clause on an update with no WITH CHECK
On an UPDATE policy, USING decides which rows you may target and WITH CHECK decides what the row is allowed to look like afterwards. Write only USING and a user can hand ownership of their row to someone else, or flip a role column to admin. Both clauses belong on any update policy that touches a column a user should not control.
create policy "users update their own profile"
on profiles for update
using (auth.uid() = id)
with check (auth.uid() = id);
3. Trusting user_metadata
user_metadata is writable by the user it belongs to. A policy that reads a role or a tenant id out of it is asking the attacker for their own permissions. Roles belong in a table you control, or in a custom access token hook.
Our Supabase security checklist works through all fifteen checks, including Realtime subscriptions. If you are hitting policy errors right now rather than auditing, why Supabase RLS stops working covers the errors people actually hit, including new row violates row-level security policy.
Three places RLS does not reach unless you tell it to
A table with correct policies can still leak through something built on top of it.
Views
A Postgres view runs with the privileges of the role that owns it by default, so a view over a protected table can return every row regardless of the policies on that table. On Postgres 15 and later, create the view as security invoker so the caller's policies apply:
create view public_orders
with (security_invoker = true)
as select id, status, created_at from orders;
Security definer functions
A function declared security definer runs as its owner and reads past RLS. If it is exposed as an RPC that the anon role can call, it becomes a way around every policy you wrote. Keep functions as security invoker unless you genuinely need elevated access, and when you do, check auth.uid() inside the function before touching any data.
Storage buckets
File access is governed by policies on storage.objects, separately from your tables. A public bucket serves every file to anyone who has, or guesses, its URL. Invoices, exports and ID uploads belong in private buckets with policies scoped to the owner.
The service role key, briefly
It is rare in our data, but it is the worst single thing that can happen, so it is worth stating precisely.
Supabase's docs say a secret key authorizes access through the service_role Postgres role, which has the bypassrls attribute, and warn never to use a secret key in the browser or expose it to customers. bypassrls means every policy above becomes irrelevant. One leaked service-role key is equivalent to publishing your whole database.
Separately from the frontend findings, 4 apps had a service-role key reference in a connected GitHub repository. That is where a .env file goes when it is committed before being added to .gitignore, and deleting the file later does not remove it from history. If a key has ever been exposed in either place, rotating it is the only fix, and our guide on what to do when an API key is exposed covers the order to do it in.
Test it yourself in two minutes
You do not need a scanner for the first check. Take your project URL and anon key out of your own frontend bundle, which is where an attacker would get them, and ask your API for a table as a logged-out visitor. Start with profiles:
curl "https://YOUR-PROJECT.supabase.co/rest/v1/profiles?select=*&limit=5" \
-H "apikey: YOUR_ANON_KEY"
Read the response carefully:
- An empty array
[]means RLS is on and no policy grants anonymous select. This is what you want. - Rows of real data means anyone on the internet can read that table right now.
- A permission error means RLS is on with no applicable policy, which is also fine.
Repeat it for every table that holds user or billing data. In the apps where we found an open table, it was often not the table the developer would have guessed.
Then test the authenticated case, which most people never do. Log in as user A, take that session token, and request user B's row. If it comes back, you have mistake number one from the list above.
If you did not write the policies yourself
Most founders shipping on Supabase did not write their policies by hand. Lovable, Bolt or an AI agent wrote them, or a contractor did. You do not need to read SQL to find out whether you are exposed. You need three answers, and you can ask for them in plain language:
- "For every table that holds customer data, does the select policy compare the row to the logged-in user?" A policy that only checks someone is signed in is the most common failure behind the numbers on this page.
- "Which tables can a logged-out visitor read?" For most apps that should be a short list, and
profilesshould not be on it if it holds emails, plans or roles. - "Where is the service role key used, and has it ever been committed to the repository?" It should appear only in server code and deployment settings.
If the answers come back vague, run the two-minute test above yourself. It needs no access to the codebase, only the project URL and anon key that are already public in your app.
What to do in the next hour
- List your tables in the Supabase dashboard and mark every one that holds personal, billing or internal data.
- Run the logged-out curl test against each marked table, starting with
profiles,usersand anything billing-related. - Run the cross-user test with two real accounts against the same tables.
- Rewrite any policy that checks
auth.uid() is not nullso it compares the row to the caller. - Add
WITH CHECKto every update policy. - Check your views for
security_invokerand your RPC functions forsecurity definer. - Make sensitive storage buckets private with owner-scoped policies.
- Search your repository history for
service_role, and rotate anything you find.
So is Supabase safe for your app?
- Prototype or internal tool? Yes. Turn RLS on anyway so you do not build habits you will have to unlearn.
- SaaS with real customer data? Yes, and the platform's compliance is genuinely useful when a customer asks. It tells you nothing about whether your policies are right, so run the tests above before you answer that question.
- Health, financial or otherwise regulated data? Supabase states HIPAA compliance, and you will still need a signed agreement and your own controls. An auditor will read your policies, not Supabase's certificate.
- Built the app with Lovable, Bolt, Cursor or v0? Treat RLS as unconfigured until proven otherwise. AI tools reliably generate a working schema and a policy that checks
auth.uid() is not null, because that is the shape that makes the app work on the first try. This is not hypothetical: NVD lists CVE-2025-48757, rated 9.3, for insufficient Row Level Security in Lovable-generated sites through April 15, 2025, which let unauthenticated attackers read or write arbitrary tables. Lovable disputes it, on the grounds that each customer is responsible for their own app's data. Our Lovable security checklist covers the Lovable-specific steps.
The verdict
Supabase is safe. The default posture of an app built on it is not, and the gap between those two statements is where the Supabase breaches you have read about live.
If you do one thing after reading this, run the curl command against profiles and your billing tables as a logged-out visitor. In our data that single test finds a real problem in more than half of the Supabase apps we could test, and it is the test nobody runs, because the app works fine when you are logged in as yourself.
For the same analysis applied to where you deployed it, see is Vercel safe or is Netlify safe, and for the pattern across every platform we scan, how vibe-coded apps get hacked.
About the author

I'm Samuel, known online as Mr. Ballaz. I build Ubserve, a security scanner for apps built with AI tools like Cursor, Bolt, Lovable, and Supabase. Before Ubserve, I did manual security audits by hand — checking auth, exposed keys, and RLS policies one by one. Ubserve is that manual audit, automated, running in under 60 seconds instead of days.
Related resources
FAQs
Is Supabase safe to use?+
Is it safe to expose the Supabase anon key in frontend code?+
What is the difference between the anon key and the service role key?+
Does enabling RLS make my Supabase app secure?+
Turn this resource into a real security check.
Review the guidance, then run the free scan to see whether this issue is actually exploitable in your app, no signup required.



