Skill: Audit Domain 3 — Database & Data Layer
This skill audits one specific domain. It runs in an isolated subagent context spawned by the audit-orchestrator. The subagent loads this skill and the audit rules, runs against the audit scope, and returns a ~2K-token findings report.
Pre-flight
view @.claude/context/audit-rules.md
If you have findings from previous audit phases (hard stops, Tambon, blind spots), the orchestrator passes them as input. Use them — don't re-discover findings other phases already produced. Specifically:
- Hard stops related to this domain: H1, H7
- Blind spots that route to this domain: B3, B4
If the orchestrator didn't pass you these inputs, do NOT re-run the hard-stops or blind-spots walks. Audit your domain only and trust the orchestrator to stitch.
Scope
Schema design, foreign keys, constraints, indexing, query patterns (N+1, full-table scans), migrations, soft-delete handling, transactions, RLS / row-level access.
Key questions to answer
For each, find the evidence and report it. The questions are the audit's spine — every finding maps back to one of them.
- Are foreign keys defined and enforced?
- Are indexes present on every column queried by WHERE / JOIN / ORDER BY?
- Are queries free of N+1 patterns?
- Are migrations forward-only, or do they get edited after running?
- Is RLS enabled on user-data tables (Postgres), or app-level enforcement provably correct?
- Are soft-delete columns filtered in every read query?
- Are transactions used where multiple writes must be atomic?
- Are RLS policies actually restrictive, or do they use permissive shortcuts that defeat their purpose? (Q8 was added 2026-05-10 after a remediation pass shipped a migration with
using (true)as the "fix" for a priorusing (true)finding. Verification ("migration applies cleanly") missed that the new migration replicated the anti-pattern.)
Vibe-coding specific checks (production-readiness)
Cite the playbook for depth: view ~/.codex/context\production-readiness-playbook.md
- Schema is normalized (related data in separate tables), not one wide table accreted one-column-per-prompt (playbook FM-11, L3).
- Indexes exist on foreign keys and frequently-queried columns (playbook L3, FM-1).
- A migration file exists for every schema change; no schema edits made directly in production (playbook L3).
- Separate dev / staging / production databases (playbook L3).
- Automated backups WITH tested restores — not assumed (playbook L13, L3).
- Connection pooling configured (e.g. Supabase port 6543 pooled vs 5432 direct; pgbouncer; transaction mode for serverless) (playbook L11, FM-1).
Permissive-RLS sweep (mandatory for Q8)
Before producing findings, run ALL of these greps GLOBALLY across every SQL file in the repo (migrations, seeds, RPC definitions, grants files):
# Anti-patterns that defeat RLS:
rg -n "using\s*\(\s*true\s*\)" -g '*.sql'
rg -n "using\s*\(\s*auth\.role\(\)\s*=\s*'authenticated'\s*\)" -g '*.sql'
rg -n "with\s+check\s*\(\s*true\s*\)" -g '*.sql'
rg -n "for\s+(select|insert|update|delete|all)\s+to\s+authenticated\s+using\s*\(\s*true\s*\)" -g '*.sql'
# Bypasses:
rg -n "alter\s+table\s+\w+\s+disable\s+row\s+level\s+security" -g '*.sql'
rg -n "force\s+row\s+level\s+security" -gV '*.sql' # NOT having this on sensitive tables is a smell
# Service-role abuse (RLS-bypass code path):
rg -n "service_role|SUPABASE_SERVICE_ROLE_KEY|getSupabaseAdmin\(\)" -g '*.{ts,tsx,js,jsx,mjs,cjs}'
Every match of the first three patterns is a Critical or High finding (severity depends on the table's data sensitivity). Match means: a row is exposed to ANY authenticated user, regardless of ownership.
Required verification for every RLS fix the audit recommends:
-- Adversarial RLS test: as user A, attempt to read user B's row.
-- Expected: 0 rows returned, NOT an error.
set local role authenticated;
set local request.jwt.claims = '{"sub":"<user_a_uuid>","role":"authenticated"}';
select * from <table> where <ownership_column> = '<user_b_uuid>';
-- if this returns >0 rows, the RLS policy is permissive.
Flag any RLS finding whose recommended fix only says "apply migration" without specifying the adversarial test the migration must pass.
Migration self-violation check
When a migration claims to FIX an RLS issue (filename contains
rls, policy, tenant, isolation, security, or migration commit
message references a prior audit finding), grep that file for the same
permissive patterns above. Findings that fix RLS by writing more
permissive RLS are a regression class — flag with severity High and
include Recommended fix: that explicitly states the adversarial test
must pass post-remediation, not just "migration applies".
Files most likely to have findings
Don't read everything. Read these files first:
- schema files / migrations
- ORM model definitions
- any 'repo' / 'dao' / 'data' modules
- DB connection config
If you exhaust these and the budget allows, expand outward. Otherwise, report what you found and note what you didn't read.
Process
Re-read the rules. R1-R7 apply to every finding. Especially R2 (quote before cite) — for a domain skill running in a subagent, the subagent's context is fresh; don't assume you remember a file from a previous turn.
Walk the key questions. For each question, run the relevant detection commands (greps, file reads, schema lookups). Capture evidence at path:line. Verify by reading the actual code.
Cross-reference orchestrator inputs. If the orchestrator passed hard-stops or blind-spots findings tagged for this domain, include them in your report. Don't re-investigate; just include with the provided evidence.
Triage. For each finding, set severity per the audit rubric and exploitability per R4.
Produce the domain report.
Output format
═══════════════════════════════════════════════════════════════════════
DOMAIN 3: Database & Data Layer
═══════════════════════════════════════════════════════════════════════
▶ FOUNDER VIEW
[2-4 sentences in plain English. Sample tone:]
How is the data stored, queried, and protected? This is where breaches happen and where slowness compounds.
▶ TECHNICAL EVIDENCE
Scope of this domain audit:
Files read: <count>
Files skipped: <count> (reason: outside scope or low-priority)
Findings:
F-3.1 — <one-line title>
Severity: Critical | High | Medium | Low
Exploitability: EXPLOITABLE-NOW | EXPLOITABLE-LOW-EFFORT | BAD-PRACTICE | UNKNOWN
Hard-stop: H<N> if applicable
Blind-spot: B<N> if applicable
Evidence:
<path:line> <one-line description>
What's wrong:
<one paragraph>
Why it matters:
<one sentence>
Recommended fix:
<one paragraph; for full fix prompt, use /audit-fix F-3.1>
Verification after fix:
<command>
F-3.2 ...
Summary:
Total findings: <count>
By severity: <counts>
Most urgent: <which finding ID>
[SECTION COMPLETE: Domain 3]
If the domain has zero findings:
▶ TECHNICAL EVIDENCE
✅ No findings in this domain.
Verification:
<commands run that produced no signal>
Confidence: High | Medium | Low
Reason for low confidence: <if applicable>
Failure modes to refuse
- ❌ Producing findings without path:line citations (R1)
- ❌ Citing a path you didn't read (R2)
- ❌ Re-running hard-stops or blind-spots walks (orchestrator did this)
- ❌ Including findings outside this domain's scope (route them to the right domain instead)
- ❌ Soft-pedaling a Critical to Medium because "it's a small app" (R3)
- ❌ Skipping section completion marker (R6)