feat(arsenal): parse scanner JSON into structured findings (#42)
The generic adapter factory returned raw stdout only (`adapter-tools.ts` success path), so 35 catalogued scanners with `parserStatus: 'planned'` never populated the structured `ToolResult.findings` channel — their output was dropped on the floor and the agent could neither verify nor chain on it. The whole downstream pipe already exists and is honest-by-construction: the agent loop stamps `provenance:'tool'` + the raw output, the operator materialises each finding with output-evidence, and the live gate (`evidence/gate.ts`) marks it verified. Add `src/arsenal/parsers.ts` — `parseToolOutput(toolId, rawStdout) → ToolFinding[]` for the five scanners that already emit machine-readable JSON via their existing arg templates (zero template changes): nuclei (-jsonl), httpx (-json), dalfox (--format json), ffuf (-of json), katana (-jsonl). Parsers are pure, dependency-free, and NEVER throw: empty/garbled/unknown input yields `[]`, never a fabricated finding (same discipline as stub-honesty / no-phantom-tools). The factory attaches parsed findings while ALWAYS keeping the raw stdout as `output` — the evidence of record the gate checks. - nuclei/dalfox emit CWE, which the tool channel dropped: add optional `cwe?: string[]` to `ToolFinding` and thread it through the operator's finding materialisation. - flip `parserStatus: 'planned' → 'structured'` for httpx, dalfox, katana (nuclei/ffuf were already structured). Adds `src/__tests__/parsers.test.ts` + fixtures: per-scanner field mapping, the honesty contract (empty/garbled/adversarial → [], no throw), a factory-wiring check that a minted adapter returns findings while preserving raw stdout, and an end-to-end assertion that every parsed finding PASSES `gateLiveFinding` with provenance='tool'. The minted-adapter path is gated behind T3MP3ST_FULL_ARSENAL, so the benchmarked default arsenal is untouched. Supply-chain JSON tools (semgrep/gitleaks/trivy/grype) and secret scanners (need redaction) are a clean follow-up.
This commit is contained in:
1 parent
0f3213b310
commit
f8c283377c
11 files changed
+423
-4
No files matched your search
@@ -25,6 +25,7 @@
|
||||
|
||||
import type { ToolAdapter } from './catalog.js';
|
||||
import type { CustomTool, ToolContext, ToolResult } from '../types/index.js';
|
||||
import { parseToolOutput } from './parsers.js';
|
||||
|
||||
// =============================================================================
|
||||
// INJECTED DEPENDENCIES
|
||||
@@ -470,7 +471,16 @@ export function adapterToCustomTool(adapter: ToolAdapter, deps: AdapterToolDeps)
|
||||
};
|
||||
}
|
||||
|
||||
return { success: true, output: result.stdout };
|
||||
// Populate the structured `findings` channel from the raw stdout when a parser is wired for
|
||||
// this tool (parseToolOutput → [] otherwise). The raw stdout is ALWAYS kept as `output` — it
|
||||
// is the evidence of record the agent loop stamps onto each finding and the live gate checks;
|
||||
// the parser summarises it, never replaces it. A parse yielding nothing leaves findings unset.
|
||||
const findings = parseToolOutput(adapter.id, result.stdout);
|
||||
return {
|
||||
success: true,
|
||||
output: result.stdout,
|
||||
...(findings.length ? { findings } : {}),
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
Reference in new issue
Block a user