Skip to main content
AC
Quality & Security4.7 KBMIT licensed

supabase-codebase-audit

Original, written for TechTide client work

Systematic six-domain audit of an existing Supabase codebase covering RLS, edge functions, realtime, type safety, dead code, and CI/CD. Use when onboarding to a Supabase project, before a refactor or rebuild decision, after an incident, or for a periodic health review. Not for gating a single diff before ship (use rls-security-gate) or writing new migrations (use supabase-schema-discipline).

  • supabase
  • codebase
  • audit

SKILL.md

Supabase Codebase Audit

Audit with commands, not impressions. Every finding needs evidence you can paste: a grep hit or a SQL result. No evidence, no finding.

When to run

  • Onboarding to an existing Supabase project.
  • Before a major refactor or a build-vs-rebuild decision.
  • After a security incident.
  • Periodic security and health review.

The six domains

Work them in order. Read references/audit-commands.md for the exact grep and SQL per domain, run them verbatim, and paste outputs as evidence.

#DomainWhat it surfacesHighest-severity signal
1RLS policiesPermissive or missing policiesUSING (true) on a PII table: CRITICAL
2Edge function healthMonoliths, type escapes, open endpointsverify_jwt = false without documented justification
3Realtime publicationsWAL decode waste, dead subscriptionsPublished table with zero frontend subscribers
4Type safetyas any, : any, @ts-nocheck censusas any in data-fetching code: data integrity risk
5Dead code and triggersOrphan cron jobs, duplicate triggersCron job with zero successes
6Deployment and CI/CDMissing pipelines, committed secretsLive secret key in the repo: CRITICAL, rotate now

Domain notes:

  • Domain 1: cross-check three ways: policies containing true, tables with no policy at all, tables with RLS disabled. A table passing one check can fail another.
  • Domain 2: functions over 500 lines are monolith candidates. Count @ts-nocheck, as any, and raw console logging per function.
  • Domain 3: the waste runs both directions. Published tables nobody subscribes to burn WAL decoding. Frontend subscriptions to unpublished tables are dead code that looks alive.
  • Domain 4: prioritize fixes in data-fetching code first, then handlers, then UI.
  • Domain 5: red flags are duplicate triggers on one table, triggers missing WHEN clauses, and scheduled jobs that never succeed.
  • Domain 6: any hit on a live secret pattern is an immediate stop: rotate the key before continuing the audit.

Output format

One table, sorted by severity:

FindingSeverityEvidenceRecommended fix
<description>CRITICAL / HIGH / MEDIUM / LOW<grep output or SQL result><specific fix>

Severity rules: CRITICAL means exploitable now or data loss in progress. HIGH means exploitable with effort or silent corruption. MEDIUM means debt with a trigger condition. LOW means hygiene.

Good vs bad

Good finding: "USING (true) SELECT policy on public.profiles (migrations/0042.sql:18), table holds emails. CRITICAL. Fix: replace with auth.uid() = user_id, add admin path via has_role."

Bad finding: "RLS coverage seems inconsistent and could be improved." No table, no file, no severity, not actionable. Delete findings like this, they pad the report and bury the real ones.

Verification

Do this: after writing the report, re-run the Domain 1 commands from references/audit-commands.md. Expect every CRITICAL and HIGH RLS finding in the report to correspond to a current command output, and every command output row to appear in the report. If a finding has no reproducing command output, cut it. If an output row has no finding, the audit is incomplete, add it.

Completion checklist

  • [ ] All six domains executed with commands, none skipped
  • [ ] Every finding carries pasted evidence
  • [ ] Severity assigned by the rules above, not by feel
  • [ ] Any committed secret escalated for rotation immediately, not just reported
  • [ ] Report sorted by severity with a specific fix per finding

Any box unchecked: not done. Fix or say so.

Footguns

  • Auditing migrations only: migrations show intent, the live database shows truth. Run the SQL against the live schema, policies drift after hotfixes applied in the dashboard.
  • Grepping src/ but not supabase/functions/: edge functions are where @ts-nocheck and secrets hide most often.
  • Reporting a committed secret without rotating it: the report itself now documents a live credential. Rotate first, then write it up.
  • Counting any hits without reading them: a hundred hits in generated types is noise, three in a payment fetch is the story.

Reference files

More in Quality & Security

All skills