Is your Supabase app safe?
Is Supabase safe to hold real user data? Yes, and it is built to be queried straight from a browser. That design is exactly why two settings decide everything.

Logos are the property of their respective owners, shown to indicate compatibility.
In short
- Supabase is safe to hold real user data, and it is built to be queried straight from a browser. That is exactly why the rules on your tables do all the work.
- The publishable key belongs in your app. sb_secret_… and service_role bypass every rule you have written.
- Tables made in the Table Editor get Row Level Security automatically. Tables created by running SQL do not.
Supabase gives you a real Postgres database with an API already in front of it, which is why an app can be talking to live data twenty minutes after you start. The thing that surprises people later is that the same API is reachable by anyone, from anywhere, with credentials that are printed inside your app.
Here is the part that guide after guide gets wrong: that is not a flaw, and hiding it is not the fix. Supabase is designed to be queried directly from a browser. The protection was always meant to live somewhere else, and knowing where is most of what there is to know about running a Supabase app safely.
Why your database is on the internet on purpose
Because your app talks to it from your visitor's browser, not from a server you run.
Think of it as a shop. Your app is the shopfront, handed in full to every visitor. Your database is the stockroom, and with Supabase, the stockroom has its own front door onto the street, deliberately, so your shopfront can fetch from it without a delivery service in between. That door is what saves you from writing a backend.
Which means the door is public, and its address is in your app because your app has to knock on it. There is no arrangement where that address is secret. So the question was never "can strangers reach my database". They can, by design. The question is what it hands them when they ask.
Is the Supabase key in my app supposed to be there?
One of them is. The other never is, and they look almost identical.
The publishable key (sb_publishable_… in new projects, anon in older
ones) says which project a request belongs to. It carries no privileges of its
own, which is why Supabase's own documentation describes it as safe to bundle
into web pages and mobile apps. Finding it in your app is not a finding.
The secret key, sb_secret_… or the older service_role, is the opposite.
It bypasses your rules entirely and reads and writes every row in every table,
from anywhere. It belongs on a server, in an edge function, in a worker.
Nowhere a browser can reach.
They sit next to each other on the same dashboard page and are the same shape. If one has ever been pasted into frontend code, rotate it in the dashboard before editing anything, because deleting the line does not close the door.
That page is Settings, then API Keys, and it holds four values rather than two.
The setting that decides what the door hands out
Row Level Security: a per-table switch that decides, row by row, who may read what. It is the whole of why a publishable key is safe.
With it off, that key returns the entire table to anyone who asks. With it on and a policy written, the database itself filters every request, including requests that never went through your app.
Two details decide whether you actually have it, and neither is visible from your app:
How the table was created. Supabase enables Row Level Security automatically for tables made in the dashboard's Table Editor. Tables created by running SQL do not get it and must be enabled deliberately. Migration files, scripts and AI-generated schemas all create tables by running SQL, so a project built that way can have a row of unprotected tables that look exactly like the protected ones.
What the policy says. Enabled means closed until a policy opens it. A policy that permits everyone opens it all the way while the dashboard still reports the table as having Row Level Security enabled, the state that looks protected and is not.
Storage is separate again. A public bucket is listable and downloadable by anyone with your project address, whatever your table rules say.
How to check your own Supabase project in about ten minutes
Authentication → Policies. Read the whole list. Any table showing Row Level Security as disabled is readable by anyone. For the tables that show it enabled, open the policy and read what it actually permits.
Settings → API. Confirm the key your app ships is the publishable one. If a
secret key has ever been in frontend code, rotate it here first.
If your dashboard shows keys beginning sb_publishable_ and sb_secret_
rather than anon and service_role, that is
the newer format and the same rule decides which
of the two belongs in your app.
Storage → Buckets. Check which are public, and whether that is what you intended for the files inside them.
Then look from the outside. Everything above is what your dashboard says is configured. What an anonymous request actually gets back is a different question, and it is the one our free scan answers. It queries the way a stranger would, without reading any of your rows, and gives you a grade in about 20 seconds: check what your app exposes.
What to do
- Your Supabase API is public on purpose. The protection is the rules on your tables, never the secrecy of the address.
- The publishable key belongs in your app.
sb_secret_…andservice_rolenever do, and a leaked one ignores every rule you have written. - Tables made in the Table Editor get Row Level Security automatically. Tables created by running SQL do not.
- Enabled is not restricted. Read the policy, not just the switch.
- Storage buckets have their own settings, so locked tables say nothing about your files.
Open Authentication → Policies and read down the list once. The tables marked disabled are the ones to start on, and if you would rather work through the whole surface as a list, the 10-minute security checklist is the short version. Whatever you find, knowing you can put the data back matters as much as locking it, which depends entirely on what you are backing up.
If you would rather it did not depend on you remembering, here is how automatic Supabase backups work.
What can actually go wrong with Supabase
None of these mean you did something wrong. They're the usual gaps when you're moving fast. Here's what's worth checking:
Row Level Security (RLS) turned off
RLS is Supabase's rule for who can read or change each row of a table. With the public "anon" key (which is meant to be in your app), anyone can query your database directly. RLS is what stops them from seeing rows that aren't theirs. If it's off, a table can be fully readable, or even editable, by anyone. This is the single most important Supabase setting, and Reeve checks it by counting rows, never by reading them.
A leaked service_role key
Supabase gives you two keys. The "anon" key is public by design and safe in the browser. The "service_role" key bypasses all your security rules and must live only on a server. If it ever ends up in your app's front-end code, someone can do anything to your data. Reeve decodes the keys it finds and tells you exactly which one is exposed: safe "anon" gets a green check, "service_role" is a red alert.
Public storage buckets
Files people upload live in Supabase Storage "buckets." A bucket set to public means anyone can list and download what's inside, so private uploads can become visible to everyone. Reeve checks whether your buckets are listable; it never downloads anyone's files.
Your API open to any website (CORS)
Supabase gives your database a web address (via PostgREST). Combined with an over-permissive sharing setting, another website could call your data from a visitor's browser. Reeve checks whether your endpoints answer to strangers and to other sites, without ever using them to change anything.
Tables and endpoints exposed without rules
Every table you create is reachable through Supabase's API; that's by design, and RLS is meant to guard it. But a new table added in a hurry, before its rules are set, can be briefly (or lastingly) open. Reeve checks what's actually reachable from the outside.
Keys or config left in the deployed app
Beyond Supabase's own keys, apps often carry other settings and secrets in their front-end code or an exposed .env. Reeve reads your app's loaded code and checks for reachable config files, then tells you which values are safe to be public and which aren't.
What Reeve is, and what it isn't
Reeve is a free, read-only check from the outside, like an inspector trying the doors without stepping inside. It catches the common, high-impact mistakes fast. It is not a full security audit, and a clean grade isn't a guarantee; it means the obvious doors are shut. Everything Reeve does is passive: it counts rows rather than reading them, and never downloads your files.
Supabase gives you real security tools (a Security Advisor and database linter that flag RLS and exposure problems right in the dashboard), and they're genuinely worth using. What Reeve adds: most people building on Supabase live in their app builder, not the SQL editor, and the advisor speaks developer. Reeve checks your whole app from the outside, the way an attacker would reach your data, and explains what it finds in plain words. And if you'd rather not watch it yourself, we can.
Want it handled, not just checked?
Reeve Care keeps watching your app, backs up your data, and helps you fix what breaks, so you can keep building instead of worrying.
Learn about Reeve CareFAQ
What is Supabase RLS and do I really need it?
RLS (Row Level Security) decides who can see or change each row in your tables. Because your app ships a public "anon" key that can query the database directly, RLS is what keeps one user's data from being visible to everyone. Yes: for any table with real data, you need it on. Reeve checks whether it is, without reading your data.
Is the Supabase anon key safe to expose?
Yes. The "anon" key is designed to live in your front-end, and on its own it only does what your RLS rules allow. The key that must never be exposed is the "service_role" key, which ignores all rules. Reeve decodes the keys in your app and tells you which is which.
What happens if my service_role key leaks?
The service_role key bypasses every security rule, so anyone who has it can read, change or delete all your data. If it's in your front-end code, treat it as compromised: rotate it in the Supabase dashboard and move it to a server. Reeve flags an exposed service_role key as a critical issue.
Can Reeve check my Supabase without my database password?
Yes. Reeve only uses what your app already exposes publicly: the same anon key and endpoints any visitor's browser uses. It never needs your database password, never logs in as an admin, and never reads or downloads your rows or files.
Why does Supabase let a browser talk to my database at all?
Because that is the product. Supabase puts an API in front of Postgres so your app can query data without you writing and hosting a backend, which is what makes it fast to build on. The safety does not come from hiding that API, which cannot be hidden. It comes from Postgres refusing to return rows the visitor is not entitled to.
Does enabling Row Level Security on a table make it private?
It makes it closed until you write a policy, which is the safe starting point. But a policy that permits everyone reopens it while the dashboard still shows the table as having Row Level Security enabled. Enabled and restricted are two different states, and only one of them is visible at a glance.
Which tables are most likely to be unprotected?
The ones created by running SQL rather than by clicking in the Table Editor: migration files, scripts, and schemas written for you by an AI builder. The Table Editor turns Row Level Security on automatically. SQL does not, and nothing flags the difference afterwards.
Is my Supabase storage covered by Row Level Security?
Storage has its own separate settings, so a locked-down database tells you nothing about your files. A bucket marked public can be listed and downloaded by anyone who knows your project address, whether or not your app ever links to what is in it.