Toolstack Lab

Home › Tools › Supabase security checklist

Supabase security checklist: RLS, auth, keys and storage for Next.js and Lovable apps

A free, copyable checklist for apps built on Supabase, whether hand-written in Next.js or generated by Lovable, Bolt, v0 or Cursor. Each item says what to look for, why it matters, and the fix. It came out of a static code review of three open-source Supabase + Next.js starters.

Updated 2026-10-09 · Checked against the current Supabase docs (Auth SSR, API keys, Storage, RLS). Static review only; nothing here asks you to attack a system you don't own.

1. The 10-minute A/B test scanners can't do

Scanners (Supabase Security Advisor, Lovable's scans, open-source linters) check policy shapes and known misconfigurations. They don't know which rows belong to which customer. This test does.

  1. Create two normal accounts in your app: A and B. Create some private data as A.
  2. Signed in as B, call the API directly (browser console or a script using the publishable key and B's session): read A's rows by id, list the table with your app's filters removed, update A's row, delete A's row.
  3. As B, insert a row with A's user_id (or org_id / workspace_id), and try to update your own row to set the owner column to A.
  4. Check the row count, not the status code. When RLS filters out an UPDATE or DELETE, the request still succeeds and simply affects zero rows. "No error" does not mean "blocked", and it does not mean "allowed".
  5. Repeat everything signed out (the anon role).
  6. Do the same for storage paths, RPC functions and your own server routes. Re-run it after every migration or AI edit that touches policies; Supabase supports SQL policy tests under supabase/tests/ run with supabase test db.
// as user B, with the normal browser client
const { data, error } = await supabase
  .from('invoices').update({ amount: 0 })
  .eq('id', A_INVOICE_ID).select();
// expect: error == null AND data.length === 0  (blocked)
// data.length === 1 means B just changed A's row

Want the read, update and storage parts scripted? supabase-rls-ab-test is a free, MIT-licensed Python script (no dependencies) that signs in as A and B with your publishable key and reports any row B can read or update. Read-only unless you pass --write, and that write is a no-op. Run it on staging.

2. Row Level Security and grants

3. Keys and secrets

4. Server auth: getClaims / getUser vs getSession

5. Edge Functions and API routes

6. Storage buckets

7. Redirects and OAuth

8. Referral attribution (the money bug)

9. Lovable and other AI-built apps

Lovable apps store data in Supabase, so everything above applies. Lovable runs a free Quick scan on every publish and offers an on-demand Deep scan (More › Security). Run both. Lovable's own docs say these tools "do not replace a thorough security review". In practice the gaps are the ones a scanner can't infer:

10. What showed up in 3 open-source starters

We ran this checklist against three public Supabase + Next.js starters (code only, pinned commits, no live systems contacted): vercel/nextjs-subscription-payments, KolbySisk/next-supabase-stripe-starter and Razikus/supabase-nextjs-template.

FAQ

Is enabling RLS enough to secure a Supabase app?

No. Enabling RLS closes a table until a policy opens it. The risk then lives in the policies themselves (for example using (true) on a private table, or an UPDATE policy that lets a user reassign the owner column) and in places RLS doesn't reach: server code using the secret or service_role key, storage bucket settings, and direct Postgres connections.

Should I use getSession, getUser or getClaims on the server?

Use getClaims() to verify who the user is; it verifies the JWT signature. Use getUser() when you need a fresh user record from the Auth server. Don't use the user object from getSession() for authorization in server code: Supabase's docs say it reads the session out of the cookie without revalidating it, so a forged cookie can produce a forged user id.

Is the Supabase anon or publishable key a secret?

No. The publishable key and the legacy anon key are meant to ship in browser and mobile code; RLS decides what they can reach. The secret key and legacy service_role key bypass RLS and must only live in server-side code you control.

Are files in a public Supabase storage bucket safe?

Anyone with the URL can download files from a public bucket; reads bypass policies. Uploads, deletes, moves and copies are still controlled by policies on storage.objects. Keep private files in private buckets and scope policies to the user.

Does Lovable's security scan replace a manual check?

Run it; it's free. But Lovable's docs say its scans don't replace a thorough security review, and no scanner knows which rows belong to which customer. Do the two-account test.

How do I test that one user can't see another user's data?

Create accounts A and B. As B, try to read, update and delete A's rows through the API, and insert a row with A's user_id. A blocked update or delete returns success with zero rows, so check the row count, not the status code. Repeat signed out.

Want it done for you? Fixed-fee audit: $149

I run the A/B test above plus a code review against this checklist, then send a written report with file-and-line findings, severity and fixes.

You only pay if I can demonstrate at least one of these:

Doc reminders and hygiene notes don't count. If none of the three turns up, you get the report and owe nothing.

To start, email: your app URL (staging is best), repo access or a code export, your stack (Next.js, Lovable, Bolt...), and confirmation that you own the app and we may test it with two test accounts. I only test systems the owner authorizes. No payment is taken on this site.

Email an audit request

Or write to jeking.hwang2025@gmail.com with the subject "Supabase audit request". Questions about the checklist are welcome too.