Review checklist
This is the checklist to run down — by eye, on any diff, yours or an AI’s — after the tools in Verification Tools have run and come back clean. It fits on one printed page on purpose: a checklist nobody actually opens during a review is worthless. Every line links the catalog entry that explains why, so this page stays a list, not a re-run of the course. The groups follow the same order as How to read Supabase code you did not write, plus a final Tooling group covering how each of the other seven actually gets verified.
flowchart LR A["Schema & migrations"] --> T["Partial: tsc/deno check catch nothing semantic -- only a live query or gen types re-run does"] R["RLS & roles"] --> M["Accepted, not rejected -- pg_policies + a real anon/service_role curl shows it"] U["Auth & sessions"] --> M C["Client calls"] --> T2["tsc catches a typo'd method name, never a skipped error check"] E["Edge functions"] --> D["deno check only -- tsc is not run against edge functions at all"] RT["Realtime"] --> Q["pg_publication_tables / relreplident query -- nothing fails loudly"] S["Storage"] --> M O["Ops & pooling"] --> H["Mostly human review -- key provenance, prepare:false"]
Schema & migrations
Section titled “Schema & migrations”- Does every
uuidprimary key default togen_random_uuid(), notuuid_generate_v4()— or iscreate extension "uuid-ossp"genuinely present in an applied migration? See the AI Bug Catalog, entry 1. - Does any FK meant to own its parent’s lifecycle (
references auth.users(id), most commonly) have a deliberateon delete cascadeoron delete set null— not the implicitno action? See entry 2. - Did the most recent PR that renamed or dropped a column also regenerate and commit
database.types.tsin the same PR?tscpassing is not evidence this was done. See entry 3.
RLS & roles
Section titled “RLS & roles”- Does every table with RLS enabled have a policy for every command the client actually calls (
select,insert,update,deleteare independent)? See the AI Bug Catalog, entry 4. - Does every
for update(orfor all) policy have an explicitwith check, or does it rely onusingbeing silently reused? See entry 5, proven with a real row-hijack. - Is
auth.uid()/auth.role()/auth.jwt()wrapped in(select ...)in every policy, not called bare? See entry 6, proven with a real ~10xexplain analyzedifference. - Does every
security definerfunction setsearch_path = ''? See entry 7, proven with a real schema-injection leak — the single highest-value line on this page. - Does
select count(*) from pg_policies where tablename = '<table>'return more than zero for every table with RLS enabled? See entry 8, proven with a real, silent, zero-policy empty table.
Auth & sessions
Section titled “Auth & sessions”- Does every server-side authorization check use
getUser()/getClaims(), nevergetSession()? See the AI Bug Catalog, entry 9, proven with a real tampered-token bypass ofgetSession(). - Does refresh-retry logic have an explicit branch for
error_code === 'refresh_token_already_used', distinct from a transient failure? See entry 10. - Does any policy or client code read a custom JWT claim (
auth.jwt() ->> '...') that is cross-checked against a real, registered Auth Hook inconfig.toml’s[auth.hook.custom_access_token]? See entry 11.
Client calls
Section titled “Client calls”- Does every
supabase-jscall destructure and checkerror, not justdata? See the AI Bug Catalog, entry 12. - Does every
.single()call have an adjacentPGRST116check, or use.maybeSingle()where zero rows is expected? See entry 13. - Is
createClient()called exactly once, at module scope, not per request or per component? See entry 14, proven with a real “Multiple GoTrueClient instances” warning. - Does any client combine
global.headers.Authorization(frozen at construction) with a laterauth.setSession()call? See entry 15, proven with a real frozen-header data leak.
Edge functions
Section titled “Edge functions”- Does every
importinsidesupabase/functions/**/index.tsusejsr:/npm:/a relative path, never a bare specifier? See the AI Bug Catalog, entry 16, proven with a realBOOT_ERROR503. - Which key does
createClient()receive inside each function — cross-checked against whether that function’s job actually needs to bypass RLS? See entry 17. - Does any secret live in a plain
supabase/functions/<name>/.envinstead of a.local-suffixed file orsupabase secrets set? See entry 18, proven against this exact CLI’s own.gitignore.
Realtime
Section titled “Realtime”- Is every table a
postgres_changessubscription targets a confirmed member ofsupabase_realtime(select * from pg_publication_tables where pubname = 'supabase_realtime')? See the AI Bug Catalog, entry 19, proven with a real silent-miss. - Does every private channel topic have a matching
select(andinsert, for broadcasting) policy onrealtime.messages? See entry 20, proven with a realUnauthorizedrejection. - Is
replica identity fullset on any table whose Realtime consumers readpayload.old’s non-key columns? See entry 21, proven with a realold: nullvs full-row comparison.
Storage
Section titled “Storage”- Does every storage policy check
(storage.foldername(name))[1]against the caller’s own id, not justbucket_id? See the AI Bug Catalog, entry 22, proven with a real cross-user folder write. - Does any bucket used with
upload(..., { upsert: true })have aselectpolicy, in addition toinsert/update? See entry 23, proven with a real upsert-specific 403. - Is a signed URL’s
expiresInever exceeded by how long the URL is actually cached or reused? See entry 24, proven with a real post-expiryInvalidJWT.
Ops & pooling
Section titled “Ops & pooling”- Does
SERVICE_ROLE_KEY/SUPABASE_SERVICE_ROLE_KEYappear anywhere a client bundle could ship it? See the AI Bug Catalog, entry 25 — the single highest-consequence line on this page. - Does any
postgres()/driver connection through a transaction-mode pooler (:6543-style port) setprepare: false? See entry 26, doc-sourced and honestly not reproduced under this course’s own low-concurrency local test. - Does any
pg_netcaller readnet._http_responsewith a real wait/retry loop, not immediately in the same statement? See entry 27, proven with a real zero-rows-then-populated race.
Tooling
Section titled “Tooling”- Is
supabase db lintrun before trusting a migration is free of unindexed policy columns, missingsearch_path, or an unwrappedauth.uid()? See Verification Tools. - Is
deno check— nottsc— run against everysupabase/functions/**/index.ts, knowingtscis never run against edge functions at all in this course’s own harness? See Verification Tools. - Does CI run this course’s own harness (
npm run verify,--run,--apply) against every fence, not just a human skim? See Verification Tools.
Mental Model
Section titled “Mental Model”Eight groups, same order as the reading-order lesson:
Schema & migrations -> extension deps, FK delete behavior, generated-type driftRLS & roles -> policy-per-command, using/with check, auth.uid() wrapping, search_path, RLS-enabled-zero-policiesAuth & sessions -> getUser() not getSession(), refresh reuse, custom claimsClient calls -> error checked, .single()/PGRST116, singleton, frozen headersEdge functions -> jsr:/npm:, which key, secrets file namingRealtime -> publication membership, private-channel policy, replica identityStorage -> folder-scoped policy, upsert needs select, signed URL expiryOps & pooling -> which key reaches this code, prepare:false, pg_net is async
Run this AFTER the tools in Verification Tools come back clean -- the checklist andthe tools cover different failure classes, and most of this catalog's 27 entries passevery tool cleanly right up until the checklist line that actually names the gap.