Row Level Security is not optional
Because the Supabase client talks to your database directly, a table without RLS enabled is readable and writable by anyone with your public anon key - which ships in the browser. This is the single most damaging and most common Supabase mistake in generated apps.
- Enable RLS on every table
A table with RLS off is fully exposed through the public API. There is no "private by default".
- Write policies that check ownership
A policy must compare the row to auth.uid(), not merely allow any authenticated request. "Authenticated" is not "authorized".
- Cover all operations
Separate policies govern select, insert, update, and delete - confirm each is restricted, not just select.
Keys, service role, and storage
Supabase has two keys, and confusing them is dangerous. The anon key is public by design; the service role key bypasses RLS entirely and must never reach the client.
- Never expose the service role key
It ignores RLS. It belongs only in trusted server-side code, never in the browser bundle or client env.
- Secure storage buckets
Storage has its own access policies. Confirm buckets are not public unless intended, and enforce per-path ownership.
- Validate writes in policies
Use policy checks to constrain what a user can insert or update, not just which rows they can see.
Scale and cost on Postgres
Supabase is Postgres, so the usual database scale rules apply: index the columns you filter and sort on, paginate large reads, and avoid query patterns that fan out per row. Generated apps often skip indexes, which is invisible until the table grows.
The pre-launch checklist
- Enable RLS on every table
No table should be reachable through the public API without it.
- Write ownership-based policies, not "is authenticated"
Compare rows to auth.uid().
- Add policies for insert/update/delete, not just select
Restrict every operation.
- Keep the service role key server-side only
It bypasses RLS - never ship it to the client.
- Lock down storage buckets and paths
Per-path ownership; no accidental public buckets.
- Index filter/sort columns and paginate reads
Keep Postgres fast as data grows.
Run this checklist on your repo, automatically
PeakStack scores every commit for security, scalability, and cost - with the exact line and a fix.
Request accessFAQ
Why are AI-generated Supabase apps often insecure?
Because Supabase exposes the database to the client, and security depends entirely on Row Level Security. Generated apps frequently ship with RLS disabled or with policies that only check authentication - leaving every row readable or writable by any user.
Is the Supabase anon key safe to expose?
Yes - the anon key is public by design and ships in the browser. Your security comes from RLS, not from hiding it. The service role key is the dangerous one: it bypasses RLS and must stay server-side only.
How do I verify my Supabase policies are correct?
Confirm RLS is enabled on every table and that each policy checks ownership (auth.uid()) for select, insert, update, and delete. PeakStack flags missing RLS and permissive policies on every commit.