Supabase row level security checklist for AI-built apps
Last updated
Enable row level security on every table in an exposed schema, write policies that compare rows to auth.uid(), add both USING and WITH CHECK clauses for writes, make views security_invoker, review SECURITY DEFINER functions, lock down storage buckets, keep the service_role key on the server, and test everything with only the anon key.
Why RLS is the whole security model
Supabase exposes your tables through an HTTP API, and every front end ships the anon (or publishable) key so the browser can call it. That key is public by design. Row level security policies decide which rows each request may read or change, so a table without RLS, or with a policy that returns true for everyone, is readable by anyone who copies the key from your site.
AI coding tools often “fix” a permission error by loosening a policy until the error goes away. The app works again, and every user can now see every other user’s data. Re-check policies after any change an AI agent makes to the database.
The checklist
- Enable RLS on every table in the public schema (and any other schema exposed through the API). Supabase’s Security Advisor flags tables where it is off.
- Give every table at least one policy; RLS with no policies blocks everything, which is safe but usually not what you want.
- Scope read policies to the signed-in user, for example user_id = auth.uid(), or to a membership table for team data. Never use a policy that is simply true for sensitive tables.
- For INSERT and UPDATE, add a WITH CHECK clause so users cannot write rows that belong to someone else.
- Create views with security_invoker = true (Postgres 15 and later), otherwise a view runs with its owner’s rights and skips RLS on the tables behind it.
- Review every SECURITY DEFINER function: it runs with the creator’s rights, so it must check auth.uid() itself.
- Make storage buckets private unless every file in them is meant to be public, and add policies on storage.objects for uploads and downloads.
- Keep the service_role or secret key only in server code and Edge Function secrets. It bypasses RLS completely.
- Test with only the anon key and no session: request each table through the REST API and confirm you get no rows that belong to other users.
- Repeat the test after every schema change, and add it to your deploy checklist or CI.
Common mistakes and the safe pattern
| Mistake | What happens | Safe pattern |
|---|---|---|
| RLS disabled on a table | Anyone with the anon key reads and writes every row | alter table … enable row level security, then add policies |
| Policy using (true) | Same as no RLS for that operation | using (auth.uid() = user_id) |
| UPDATE policy without WITH CHECK | Users can move rows to another user_id | with check (auth.uid() = user_id) |
| View over a protected table | View returns all rows | create view … with (security_invoker = true) |
| service_role key in the front end | Full database access for every visitor | Server-only secret; rotate it if it ever shipped |
| Public storage bucket for uploads | Files readable by URL | Private bucket and signed URLs |
Questions
- Can Defexity test my RLS policies?
- From the live URL, Defexity finds service_role keys and database credentials in your public JavaScript. With GitHub connected it flags migrations that create tables without RLS or grant writes to the anon role. Proving a policy leaks data needs requests with your anon key, which should only be done on a project you own.
- Is the anon key safe to expose?
- Yes, when every table has correct RLS. It identifies your project; it does not grant access by itself.
- Does the publishable key change anything?
- Supabase’s newer publishable and secret keys replace anon and service_role. The rule is the same: publishable keys ship to browsers and rely on RLS, secret keys stay on the server.
Related Defexity checks
- SEC-016: Secret key exposed in public HTML or JavaScript
- SEC-017: Database credentials exposed to the browser
- CODE-040: Supabase table without row level security (code check, with GitHub)
- CODE-042: Write access granted to the anon role (code check, with GitHub)
Sources
Defexity reports potential risks it can see from outside your app. It is not a guarantee of security and not legal advice.