PreflightHow it worksPricingSampleGuides

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

  1. 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.

  2. 2No using (true) on private data

    Fine 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. 3“Signed in” is not used as “owner”

    auth.uid() is not null or to authenticated using (true) lets every user read every row. With open sign-up that is anyone.

    How to check Read each qual in pg_policies, then test as two users.

    Preflight Proof Report: user A tries to read user B’s rows.

  4. 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.

  5. 5Users can’t edit privilege columns

    RLS is per row. A user allowed to update their profile can also set plan, role or credits unless 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.

  6. 6Policies name a role and wrap auth.uid()

    Use to authenticated and (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.

  7. 7Grants match what each role needs

    Postgres checks grants before RLS. Revoke what anon never 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.

  8. 8No access decisions from user_metadata

    Users can edit their own user_metadata. Store roles in app_metadata or a table only admins can write.

    How to check Search policies and functions for user_metadata.

    Preflight Not checked.

02API keys and secrets

  1. 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_ and service_role.

    Preflight Free scan: secret keys in the JS bundle.

  2. 10No secret behind a public env prefix

    NEXT_PUBLIC_, VITE_ and EXPO_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.

  3. 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.

  4. 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

  1. 13Views use security_invoker

    A 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.

  2. 14security definer functions check the caller

    They bypass RLS. Check auth.uid() inside, set search_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.

  3. 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

  1. 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.

  2. 17Object policies scope to the owner’s folder

    Use (storage.foldername(name))[1] = (select auth.uid()::text) on storage.objects.

    How to check Read policies on storage.objects.

    Preflight Proof Report: user A tries to list and download user B’s file.

  3. 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

  1. 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.

  2. 20Anonymous sign-ins are off unless you use them

    Anonymous users get the authenticated role. Exclude them where needed with the is_anonymous claim.

    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.

  3. 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

  1. 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.

  2. 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.

  3. 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 db

    Preflight 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.