How to secure a Supabase app: the five settings that matter

Supabase isn't insecure. But a Supabase app can be wide open, and almost always for the same reason: the defaults optimize for "it works," not "it's locked down," and the two look identical from the outside. Securing one is mostly a matter of knowing which handful of settings actually matter.
This is the map. Each area below is a place AI-built Supabase apps commonly leak, in rough order of how badly it bites — with the specific deep-dives linked where there's more to say.
1. Row Level Security is the whole game
Your app talks to Supabase from the browser using a public anon key. Everyone who loads the page has it. The only thing standing between that public key and your entire database is Row Level Security: rules that decide which rows each user can touch. RLS off means the public key can read and write everything.
And there's a trap even when it's "on." Supabase enables RLS automatically only for tables you create in the Table Editor — tables created by SQL, a migration, or an AI writing your schema don't get it by default. We cover the whole thing, including the "enabled but still public" policy trap, in is my Supabase database public?
USING (true) is not protection — it means "this rule applies to every row," which lets everyone through. "RLS is enabled" and "my data is private" are separate claims; only real policies make the second one true.2. Never ship the service_role key
Supabase gives you a second key, the service_role key, that bypasses RLS entirely. It exists for trusted server-side code. If generated code ever puts it in the browser to make an admin action "just work," every policy you wrote is moot — that key is a skeleton key to the whole database, handed to every visitor.
3. Storage buckets have their own rules
RLS protects your tables; it doesn't protect your files. Supabase Storage has its own access policies, and a bucket set public — or left with a permissive policy — exposes uploads to anyone with the URL. If your app stores user documents, invoices, or private images, the bucket policies need the same scrutiny as your tables.
4. Edge Functions and RPCs need their own checks
Anything callable — an Edge Function, a database RPC — has to re-check permissions itself. It's an endpoint, and an endpoint that assumes the caller is allowed because the UI wouldn't show the button is guarding nothing. The auth check belongs in the function, not the frontend.
5. Keep secrets out of the bundle
Third-party API keys, payment secrets, anything private belongs in a server-side environment variable, never in code that ships to the browser. This is the same mistake as the service_role key, one layer out — and it's common enough to have its own post: my API key is showing on my website.
Audit the whole thing at once
The fastest way through all five is to turn the model on its own work and make it check each area honestly:
Audit my Supabase app end to end for the security gaps that AI-built apps commonly ship with. Go through each area and tell me specifically whether my project is affected and the exact fix. 1. Row Level Security: list every table in the public schema and whether RLS is ENABLED. Flag any table with RLS off — it's readable/writable by anyone with the anon key. 2. Policies: for each table with RLS on, show the policies and tell me plainly whether they actually restrict rows to the owning user (auth.uid() = user_id) or effectively allow everyone (e.g. USING(true)). 3. Keys: confirm the service_role key is NEVER used in client/browser code — only the anon key. Flag any client init using the service role key. 4. Storage: check my Storage buckets — are any public that shouldn't be, and do bucket policies restrict access correctly? 5. Edge Functions / RPC: are any functions or database RPCs callable without the auth checks they need? 6. Secrets: are any third-party API keys or secrets shipped in the frontend bundle instead of server-side? For each finding, give me the table/bucket/file, the risk in one sentence, and copy-pasteable SQL or code to fix it.
Supabase's own Row Level Security guide is the reference for the policy side.
The half you can check from outside
Everything above lives in your database and code. The other half — the certificate, the security headers, the cookies — lives on the public URL and is testable in seconds without touching anything:
free tool · no loginSecurity headers checkScan your app's live URL for the headers an app builder leaves unset — clickjacking, sniffing, and downgrade protection. No login.Built with a specific tool? The same gaps show up host-by-host in Lovable, Bolt.new, and v0 apps — and the cross-cutting version is vibe coding security risks.
Lock the database down today. Know if it changes tomorrow.
The audit above secures your Supabase app now. Tell Me When Down watches it after — SSL that lapses, a URL that starts erroring, a site that goes down — free, no card, so a quiet change doesn't become a public one.
spot something wrong or out of date? [email protected] — we'll fix it