Supabase security checklist
24 checks for a Supabase app going to production, each with the SQL or the setting that proves it, and a straight answer on whether Preflight tests it.
Free scan: GET and HEAD requests only, after you verify you own the domain. The two-account cross-user test is the $29 Proof Report. Updated 25 Oct 2026
In short
- ✓Most Supabase data leaks come from the first eight checks: RLS and the policies on it.
- ✓Every check has a way to verify it, not just advice to “enable RLS”.
- ✓Each item says whether Preflight’s free scan or Proof Report tests it, or not at all.
- ✓Run the checklist after each migration, not once. Policies drift.
This Supabase production security checklist is ordered by how often each problem leaks real data. Work top to bottom. If you only have an hour, do checks 1 to 5 and 9, then test your policies as two users.
01Database and row level security
1RLS is on for every table in exposed schemas
A table without RLS is readable and writable by anyone with your public key, unless grants stop it.
How to check
select tablename from pg_tables where schemaname = 'public' and not rowsecurity;Preflight Free scan counts rows visible signed out on up to 35 tables.
2No
using (true)on private dataFine for a public blog table. On anything per-user, it publishes every row.
How to check
select tablename, policyname, cmd, roles from pg_policies where schemaname = 'public' and (qual = 'true' or with_check = 'true');Preflight Free scan (signed out) and Proof Report (signed in).
3“Signed in” is not used as “owner”
auth.uid() is not nullorto authenticated using (true)lets every user read every row. With open sign-up that is anyone.How to check Read each
qualinpg_policies, then test as two users.Preflight Proof Report: user A tries to read user B’s rows.
4Inserts and updates pin the owner with WITH CHECK
Without it, a user may create rows that claim to belong to someone else.
How to check
with check ((select auth.uid()) = user_id)Preflight Proof Report: forged-owner insert on tables with an owner column.
5Users can’t edit privilege columns
RLS is per row. A user allowed to update their profile can also set
plan,roleorcreditsunless you limit columns.How to check
revoke update on public.profiles from authenticated; grant update (display_name, avatar_url) on public.profiles to authenticated;Preflight Proof Report: tries privilege fields, then sets them back and reads again.
6Policies name a role and wrap
auth.uid()Use
to authenticatedand(select auth.uid()), and index the owner column. Faster, and signed-out requests skip the policy.How to check Review policy definitions; check indexes on
user_id.Preflight Not checked: this is performance and hygiene.
7Grants match what each role needs
Postgres checks grants before RLS. Revoke what
anonnever needs, so a future policy mistake can’t open it.How to check
revoke all on table public.invoices from anon;Preflight Not checked directly; the free scan sees the result.
8No access decisions from
user_metadataUsers can edit their own
user_metadata. Store roles inapp_metadataor a table only admins can write.How to check Search policies and functions for
user_metadata.Preflight Not checked.
02API keys and secrets
9Only the anon or publishable key reaches the browser
It is meant to be public (why). It is the only Supabase key that should be.
How to check Search your deployed JavaScript for
sb_secret_andservice_role.Preflight Free scan: secret keys in the JS bundle.
10No secret behind a public env prefix
NEXT_PUBLIC_,VITE_andEXPO_PUBLIC_values are inlined into the bundle at build time.How to check Grep your env files and build config for those prefixes.
Preflight Free scan finds the result in the bundle.
11A plan to retire the legacy keys
Supabase is replacing the anon and service_role JWTs with publishable and secret keys, which can be rotated one at a time.
How to check Settings → API Keys: create the new keys, switch clients, then deactivate the legacy pair.
Preflight Not checked.
12Other providers’ secret keys stay server-side
OpenAI, Stripe, Resend and similar keys in frontend code get copied and billed to you.
How to check Search the bundle for
sk-,sk_live_,re_.Preflight Free scan checks for more than 15 kinds of secret key.
03Views, functions and Edge Functions
13Views use
security_invokerA view runs as its owner by default and skips RLS on the tables underneath.
How to check
alter view public.my_view set (security_invoker = true);Preflight Not checked separately.
14
security definerfunctions check the callerThey bypass RLS. Check
auth.uid()inside, setsearch_path = '', and revoke EXECUTE from roles that don’t need it.How to check
revoke execute on function public.admin_report() from public, anon, authenticated;Preflight Proof Report calls the functions it finds as user A with user B’s id.
15Edge Functions verify the user
The legacy anon key is a valid JWT, so the built-in JWT check passes with it. Call
auth.getUser()and check ownership in the function.How to check Read each function’s auth code.
Preflight Not run.
04Storage
16Buckets with user files are private
Anyone with the URL can download from a public bucket. Serve private files with short-lived signed URLs.
How to check
select id, public from storage.buckets;Preflight Free scan: bucket list. Proof Report: public object read.
17Object policies scope to the owner’s folder
Use
(storage.foldername(name))[1] = (select auth.uid()::text)onstorage.objects.How to check Read policies on
storage.objects.Preflight Proof Report: user A tries to list and download user B’s file.
18Listing is allowed only where you need it
A SELECT policy on objects also allows listing. Bucket lists answering to anon reveal what you store.
How to check Storage guide.
Preflight Free scan: anonymous bucket list.
05Authentication
19Email confirmation is on, or off on purpose
With it off and sign-up open, one person can create unlimited accounts with any address.
How to check Authentication → Sign In / Providers → Email.
Preflight Free scan reads your auth settings.
20Anonymous sign-ins are off unless you use them
Anonymous users get the
authenticatedrole. Exclude them where needed with theis_anonymousclaim.How to check
using ((select (auth.jwt()->>'is_anonymous')::boolean) is false and (select auth.uid()) = user_id)Preflight Free scan reads your auth settings.
21Leaked-password protection is on
Rejects passwords found in known breaches. Available on Supabase paid plans.
How to check Authentication → Attack Protection.
Preflight Not checked.
06Account and operations
22MFA on every Supabase account with access
Your dashboard login can do everything the service_role key can. Enforce MFA for the organization.
How to check Organization settings → Security.
Preflight Not checked.
23SSL enforcement and network restrictions on the database
Limit direct Postgres connections to the IPs that need them and require TLS.
How to check Database settings → SSL configuration and Network restrictions.
Preflight Not checked.
24Security Advisor clean, and RLS tests in CI
Run the advisor after every migration, and keep pgTAP tests that fail if a policy loosens.
How to check
supabase test dbPreflight Not checked; complements Preflight.
07Common Supabase security mistakes this list catches
- Fixing “permission denied” with
using (true). The error was RLS working. Write the owner policy instead. - Testing in the SQL editor as
postgres. That role bypasses RLS, so every query looks fine. Switch role first. - Calling a 0-row result proof. An empty table and a hidden table look the same. Seed data for both test users.
- Rotating the anon key after a leak. The key was never the problem. The policy was.
- Trusting the dashboard warning count. Security Advisor reads configuration. It can’t tell whether user A can reach user B’s invoice.
08Where Preflight fits
Preflight tests the checks marked above from outside your app, the way a stranger would. The free scan uses only GET and HEAD requests after you verify your domain. The $29 Proof Report signs in as two test accounts you create and tries to cross between them, then lists exactly what it tested and what it skipped. It does not replace the configuration checks marked “Not checked”. Compare the approach with other tools on Compare.
?Questions people ask
What is the most important Supabase security setting?
Row level security on every table your API exposes, with policies that limit each user to their own rows. Checks 1 to 5 cover it.Does Supabase’s Security Advisor cover this checklist?
It covers part of it, mostly configuration: RLS disabled, mutable function search paths, leaked-password protection and similar lints. It doesn’t sign in as users and test what they can reach. Use both.How often should I run a Supabase security check?
After every migration or schema change, and before every release that touches data access. Policies are code and they drift.Is Supabase safe for production?
Supabase gives you the tools: RLS, grants, private storage, key separation. Whether your app is safe depends on how those are configured, which is what this checklist verifies.Which of these checks does Preflight run?
Each item above is marked. The free scan covers signed-out reads, keys in the bundle, auth settings and bucket listing. The Proof Report covers cross-user reads, forged writes, privilege columns, database functions and storage.
What an outside scan can't tell you
Can one user read another user's data?
Preflight signs in as two test accounts you create and tries to read one account's rows and files from the other. That cross-user test is the $29 Proof Report for Supabase apps. Start with the free scan: it checks your live app from outside with GET and HEAD requests only.