Secure Your Supabase App Before Launch
Supabase gives you a Postgres database, auth, and storage in minutes — which is exactly why so many apps ship with it left wide open. The database is reachable directly from the browser using a public anon key, so the rules you set (or forget to set) are the only thing standing between a visitor and every row in your tables.
1. Turn on RLS for every table
Row Level Security (RLS) is the switch that makes Postgres check a policy before returning any row. With it off, the anon key can read and write everything. Turn it on for every table that holds real data:
alter table public.notes enable row level security;
alter table public.profiles enable row level security;Enabling RLS with no policy denies all access by default — safe, but your app stops reading data until you add a policy. That's the next step.
2. Write a policy that actually scopes rows
A policy decides *which* rows each request can touch. The dangerous shortcut AI tools often generate is using (true) — that returns every row to everyone, which is the same as no security at all (this is the exact pattern behind CVE-2025-48757). Scope rows to the signed-in user instead:
create policy "read own notes"
on public.notes for select
using ( (select auth.uid()) = user_id );
create policy "insert own notes"
on public.notes for insert
with check ( (select auth.uid()) = user_id );3. Keep the service_role key on the server
Supabase gives you two keys. The anon key is safe for the browser. The service_role key bypasses every RLS policy by design — it must never appear in client code or a NEXT_PUBLIC_ variable. If it has ever been pasted into a chat, a screenshot, or a public repo, rotate it in the dashboard.
- Use the anon key in the browser; the service_role key only in server-side code (API routes, edge functions).
- Search your deployed JS bundle for
service_role— it should return nothing. - Rotate any key that has been exposed; treat it as compromised.
4. Lock down public storage buckets
A storage bucket left public can be *listed* — anyone can enumerate and download its files. Set buckets to private unless they truly need to be public (like avatars), and add storage policies the same way you did for tables. LaunchPal's bucket check (after you verify ownership) confirms whether a bucket is openly listable.
Quick checklist
- RLS enabled on every table with real data.
- Each table has a policy scoped to the user — no
using (true). - service_role key is server-only; anon key in the client.
- Storage buckets private by default, with policies for any public ones.
FAQ
Does enabling RLS break my app?
It will block reads until you add a matching policy. Enable RLS and add a scoped policy together, then test that your app still loads its data.
Is the anon key a secret?
No — the anon key is meant to be public and is safe in the browser. Your security comes from RLS policies, not from hiding the anon key. The service_role key, however, must stay server-side.
LaunchPal provides launch readiness checks, not a professional penetration test.