Is your Supabase database actually private?
Only if Row-Level Security is on for every table. Supabase turns your Postgres database into an HTTP API that your app calls with a public "anon key", a key anyone can read from your site’s network tab. That is by design, and it is safe only if RLS is protecting your tables. Without RLS (and Supabase ships new tables with it off), that public key is a master key: anyone can read, and often write or delete, your entire database. This is the exact flaw behind CVE-2025-48757 (170+ apps) and the Moltbook breach (1.5M tokens exposed). Veilguard checks your Supabase setup and hands you the exact policy to lock it.
The anon-key myth
Founders panic when they see the anon key in their bundle and assume it is a leak. It is not, the anon key is meant to be public. The real question is whether your data is protected behind it, and that protection is RLS. With RLS off, the anon key reads everything. With RLS on and correct policies, the same public key is harmless.
So the fix is never "hide the key" (you can’t, it ships in the browser). The fix is "turn on RLS and scope it to the owner." Everything below is about getting that one thing right.
The security holes in Supabase apps
- RLS disabled (Critical): the table is fully open to the anon key. Anyone can query yourproject.supabase.co/rest/v1/tablename directly.
- Permissive policy USING (true) (Critical): RLS is "on" but the policy allows everyone, so there is no protection at all.
- Auth-only policies (Critical): USING (auth.uid() is not null) lets any logged-in user read everyone’s rows. The policy must match the owner: auth.uid() = user_id.
- service_role key exposure (Critical): the admin key bypasses ALL RLS, catastrophic if it reaches the browser.
- Public Storage buckets (Warning): uploaded files, invoices, and photos readable by anyone.
- SECURITY DEFINER views: views that run with elevated rights and quietly skip RLS.
How to check your Supabase project
In your dashboard: Table Editor, any table marked "RLS disabled" is exposed; Authentication → Policies, every public table needs RLS on with an ownership-scoped policy. From the outside: open your app, press F12 → Network, find the supabase.co request, and note the anon key is right there, then confirm RLS actually blocks an unauthorized read.
The fastest way: paste your app’s URL into Veilguard and it actively tries to read your tables the way an attacker would, then tells you exactly which are open. Connect Supabase read-only for a full policy-by-policy audit.
Turn on RLS: the exact policy
For every table that holds user data, enable RLS and add a policy that scopes each user to their own rows. Run this in the Supabase SQL editor, replacing the table and column names with yours, and repeat for insert, update, and delete.
alter table orders enable row level security;
create policy "read own orders"
on orders for select
using ( auth.uid() = user_id );
-- repeat for insert / update / delete, scoped to the owner, on every tableBefore you launch on Supabase
- 1.RLS on every table, each policy scoped to the owner (auth.uid() = the user id column).
- 2.No service_role key anywhere in client code, keep it server-side only.
- 3.Storage buckets private unless a bucket is public by intent.
- 4.No USING (true) policies left in place.
- 5.Views created with security_invoker so they respect RLS. Not technical? Paste this into your builder: "Enable RLS on every Supabase table with ownership-scoped policies for select/insert/update/delete, and confirm RLS is on for all public tables."
Frequently asked
Do I need row-level security if I only use the anon key?
Yes, especially then. The anon key is designed to be public and ship in the browser; RLS is the only thing standing between that key and your data. With RLS off, the anon key can read and write every table.
What is the most common Supabase mistake?
Row-level security that is off or effectively open: a table with RLS disabled, a policy that reads USING(true), or one that only checks auth.uid() is not null. All three let the wrong people read your data.
Is it a problem that my anon key is visible in the browser?
No, that is expected, it is an identifier, not a secret. What matters is whether RLS protects your tables behind it. The key you must never expose is the service_role key, which bypasses RLS entirely.
Will you give me the fix, not just the problem?
Yes. Every finding comes with the exact RLS policy to paste into the Supabase SQL editor, or a prompt you can hand to your AI tool to apply it.