opus-gap-audit
Find missing security controls — sibling functions that skip a control their peers call. Wraps gap_analyzer + cascade gate + Opus deep-audit. Run when js-index is complete and user names a control qna
SKILL.md.claude/skills/opus-gap-audit/SKILL.md
GitHubopus-gap-audit
Purpose
Surface "forgotten security control" findings — root-cause class 3 from
wiki/tools/karpathy/source-code-review.md. Given a control function
qname, the skill finds every callsite, then enumerates siblings (same
file + same parent module / same React component file) that do NOT
invoke the control. Survivors are Opus-audited so the verdict
distinguishes real gaps from middleware/SDK-level controls.
Inputs
targets/<name>/db/js_analyzer.db(per-target snapshot frombin/db-isolate.py snapshot).targets/<name>/sources/(postsourcemap-explode).- A control qname. Pick from one of:
- test files — search
*.test.{ts,tsx,js,jsx}for the controls the team explicitly tests (per the spec page, this is the highest-yield starting point). - the wiki target page if one exists (
wiki/targets/<name>.md). js_get_chainsoutput filtered bytaxonomy_id LIKE '%authz%'or'%csrf%'.
- test files — search
Steps
Resolve the control qname. If the user gave a bare name, run
js_get_snippet(qname=<name>)and confirm it's a function or method that the analyzer indexed.Enumerate gaps:
python3 bin/run_gap_audit.py targets/<name> '<control_qname>' \ --max-gaps 50 --cascadeThis:
- finds all callers of the control (via callgraph edges)
- for each caller, lists same-file + same-parent-module siblings
- drops siblings that already call the control
- emits
chains/gap_<slug>.jsonl(gap-shaped chains) --cascaderuns the T1.2 gate over the gaps and writeschains/gap_<slug>_survivors.jsonl+_rejected.jsonl
For each chain in
chains/gap_<slug>_survivors.jsonl, run the Opus gap-audit prompt. The prompt template is intlx/modules/js_analyzer/gap_analyzer.py:gap_audit_prompt()and matches the longer template inwiki/techniques/recon/adjacent-function-gap.md. Invoke via the existing audit infra: build the prompt withgap_audit_prompt(...), then calljs_run_audit(target_folder=...)— the audit loop will accept the gap chain because it's chain-shaped.Write
targets/<name>/opus/gap_<slug>/<sibling_qname>.mdper gap:- Control qname + caller (FN_OK)
- Gap function (FN_GAP) + file + line range
- Sibling files reviewed
- Verdict ∈ {true_positive, false_positive, undetermined}
- Opus reasoning trace
- Proposed PoC (HTTP request or browser steps)
Submit each verdict via
js_submit_finding(chain_id, verdict, proof). Track Opus cost instatus.json.opus_cost_usd(same budget asopus-deep-audit).
Outputs
targets/<name>/chains/gap_<slug>.jsonltargets/<name>/chains/gap_<slug>_{survivors,rejected}.jsonl(Step 2)targets/<name>/chains/gap_<slug>_stats.jsontargets/<name>/opus/gap_<slug>/<sibling_qname>.mdper audited gap- updated
status.json.phases.gap_<slug>
Output JSON schema (gap chain)
{
"id": "gap_<control_slug>_<sha10>",
"kind": "gap",
"control": "<control_qname>",
"fn_ok": "<caller_qname>",
"fn_gap": "<sibling_qname>",
"file": "<file_rel_to_sources>",
"score": <int 0-100>,
"severity": "low|medium|high|critical",
"source": { "qname": "...", "file": "...", "line": N, "taxonomy_id": "gap_<control>" },
"sink": { "qname": "...", "file": "...", "line": N, "taxonomy_id": "missing_<control>" },
"path": ["<sibling_qname>"],
"depth": 1,
"callsite_in_fn_ok": <int>
}
Failure modes
callers_found == 0→ control qname is not invoked anywhere in the indexed code. Verify the qname (usejs_get_snippet) and the index is fresh (status.json.phases.index.ts).- All gaps cascade-rejected as
dead_code→ siblings have no inbound edges. Either real dead code (good — drop them) or the analyzer missed dynamic dispatch wiring. Inspect a few manually. - Opus says every gap is
false_positivewith reason "control runs upstream" → the team really does apply the control at middleware / proxy level. Cache that finding inwiki/targets/<name>.mdso future gap audits know to skip this control class. - Cascade survives a gap but Opus times out → mark
verdict: undetermined, log tostatus.json.errors, surface to user.
Result block
"phases": {
"gap_<slug>": {
"status": "done", "ts": "<iso>",
"control": "<qname>",
"callers_found": N, "gaps_found": M, "max_gaps": K,
"output_file": "chains/gap_<slug>.jsonl",
"cascade": { "total": M, "rejected": R, "escalated": E,
"rejected_dead_code": D, "rejected_sonnet": S,
"gate_cost_usd": Decimal }
}
}