Published 2026-07-20 · Vibe Code Engineers
Is my Supabase app actually exposed?
Probably not in the way you have been told. If someone has alarmed you about your Supabase key being visible in your app’s code, that key is meant to be visible. The things that genuinely put your data at risk are different, there are five of them, and you can check all five yourself in about five minutes.
First, the thing almost everyone gets wrong
Search for this topic and you will find a great deal of content warning that your Supabase anon key is exposed in your frontend and that this is an emergency. It is not. The anon key — more recently called the publishable key — is designed to be sent to browsers. It identifies your project. It is not a password, and its presence in your JavaScript is the product working as intended.
Supabase documents that the anonymous/publishable API key is designed to be exposed in client applications, and is safe to expose provided row-level security is enabled on every table it can reach.
We are pointing this out even though the opposite claim would be a far easier thing to sell. A lot of the content in this corner of the internet is produced by companies selling scanners, and alarm converts better than accuracy. If a vendor has quoted you for a fix because your anon key is visible, that is a useful signal about the vendor.
What actually matters
Supabase’s security model is straightforward once stated plainly. The public key lets a browser talk to your database. What that browser is then allowed to read or write is decided entirely by row-level security policies you write, table by table. The key is the doorbell. The policies are the lock.
So there are exactly two ways this goes wrong:
- The lock is missing or wrong. A table with row-level security disabled is readable by anyone. Worse, a table with a policy that permits everything looks protected in a dashboard while protecting nothing — and this is the failure that automated scanners cannot detect, because a policy is present.
- Someone has a key that ignores the lock. The service_role key bypasses row-level security completely. It belongs on a server and nowhere else. In a browser bundle, in a client-side environment variable, or in a public repository, it is equivalent to publishing your database password.
This is not a hypothetical failure mode. It has a documented history, including a CVE covering exactly this pattern in AI-generated applications.
Of 1,645 Lovable projects analyzed, 170 (10.3%) had inadequate row-level security, exposing 303 vulnerable endpoints.
A misconfigured Supabase row-level security policy on a widely-publicized AI-built social app exposed roughly 1.5 million API authentication tokens, 35,000 email addresses, and 4,060 unencrypted private messages.
The five-minute check
Run these in order. No tooling, no purchase, no repository access for anyone. Step three is the one that matters most and the one no scanner can do for you.
Work out which key you actually have
Open your app in a browser, view source or open the network tab, and find the Supabase key it sends. If it is labelled anon or publishable, that is the public key and it is supposed to be there. If you find one labelled service_role, or a key marked secret, stop and treat it as an emergency — that key bypasses all access rules.
Confirm row-level security is on
In the Supabase dashboard, open Table Editor and look at each table in your public schema. Any table without row-level security enabled is readable by anyone holding the public key, which is everyone who visits your site. Supabase marks these tables with a warning in the dashboard.
Test whether the policies are correct, not just present
This is the step scanners cannot do. Sign out of your app entirely, open a private browser window, and request a record belonging to a different user directly from your API URL rather than through your interface. If it returns that user's data, your policy exists but is not doing what you think it is.
Check for secrets in your repository
Search your whole repository, including its history, for the strings sk_, service_role, and password. Public repositories are crawled continuously by automated scanners, so a secret that was committed even briefly should be treated as compromised and rotated.
Check your payment webhooks verify signatures
If you take payments, find the code that receives webhooks from your payment provider and confirm it verifies the signature before trusting the message. Without that check, anyone who learns the URL can tell your app an order was paid.
If everything passed
Then your app is very likely fine on this axis, and you should stop worrying about it. That is a real outcome and it is the most common one among people who bother to check. You do not need a scanner subscription and you do not need us.
When it is worth getting a second pair of eyes is when your permission rules have to express something genuinely complicated — shared workspaces, team roles, per-record permissions, or anything where the answer to “who can see this” depends on more than one field. Those are where policies that look right permit more than intended, and where reading the code carefully is the only reliable check.
If something failed
Work in this order: rotate any exposed secret key first, then enable row-level security on exposed tables, then write and actually test the policies. Rotation is the only step that is time-critical. If a service_role key was public and your app holds other people’s personal data, look into your notification obligations early rather than after the technical work.
The rest of the surface — payments, error handling, backups, spend limits — is covered in our production readiness checklist.
Questions about Supabase exposure
Is my Supabase anon key exposed, and is that dangerous?
Your anon or publishable key is designed to be public and is safe to expose in client-side code, provided row-level security is enabled on the tables it can reach. Supabase documents this explicitly. It is not a leak, it is how the product is built to work — the key identifies your project, and row-level security is what actually decides who can read what. Any vendor quoting you for a fix on the grounds that this key is visible has skipped the part that matters.
What actually is dangerous, then?
Two things. First, row-level security that is missing or written permissively — a policy that returns true for everyone provides no protection while appearing in a dashboard as a policy that exists. Second, the service_role key reaching a browser bundle, an environment variable prefixed for client use, or a public repository. That key bypasses row-level security entirely and is equivalent to handing out your database password.
How would I know if someone already read my data?
Usually you would not, which is the uncomfortable part. Supabase logs API requests, and unusual volume against a table is worth looking at, but ordinary reads through a permissive policy look identical to legitimate traffic. If a secret key was public, the responsible assumption is that anything reachable with it was read, and to work out what that means for the people whose data it was.
I found a problem. What do I do first?
Rotate the exposed key before anything else, then enable row-level security on the affected tables, then write and test policies. Rotating first limits the window; everything else can follow in the next hour. If the exposed key was a service_role key and your app handles personal data, work out your notification obligations early rather than after the technical fix.
Do I need to hire someone for this?
Most people who work through the five steps above find their app is in reasonable shape and need to hire nobody. The checks are deliberately written so a non-engineer can run them. Where it gets worth paying for help is when the policies need to express something genuinely complicated — shared workspaces, team permissions, roles that differ per record — because that is where correct-looking rules quietly permit more than intended.
Permission rules more complicated than one owner per row?
That's where correct-looking policies quietly allow more than intended, and where reading the code is the only reliable check.