{ "$comment": "Single source of truth for findings.json structure (see SKILL.md s9). validate-findings.cjs reads this file directly and interprets it. Each finding is a secscan TRIAGE CANDIDATE, not a confirmed vulnerability — a true_positive survived s6 adversarial verification but still requires human review. Schema shape adapted from cloudflare/security-audit-skill (MIT); field names are secscan's own.", "output_schema": { "oneOf": [ { "type": "object", "description": "A finding that survived s5 pre-filter and s6 adversarial verification — a triage candidate, not a confirmed vulnerability.", "properties": { "verdict": { "type": "string", "const": "true_positive" }, "title": { "type": "string", "description": "Concise, standard title for the finding (e.g. 'Unauthenticated SQL injection in report export')." }, "severity": { "type": "string", "enum": ["high", "medium", "low"], "description": "Rate the exploit, not the bug class (see gates.md SEVERITY_GUIDANCE). Lowercase; the UPPERCASE tiers in the Markdown report are prose labels." }, "cwe": { "type": "string", "description": "Primary CWE identifier, e.g. 'CWE-89'." }, "cvss_vector": { "type": "string", "description": "CVSS 3.1 base vector string assigned in s6, e.g. 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H'." }, "vuln_class": { "type": "string", "description": "Short class label, e.g. 'sql-injection', 'idor', 'jwt-alg-confusion', 'oob-write'." }, "source_ref": { "type": "string", "description": "file:line where untrusted input enters. For a single-site issue (hardcoded key, weak cipher) use the same ref as sink_ref." }, "sink_ref": { "type": "string", "description": "file:line where the input is used unsafely." }, "impact": { "type": "string", "description": "What the attacker achieves (the concrete effect, not the bug class)." }, "description": { "type": "string", "description": "The input -> bug data flow, tracing untrusted input to the dangerous operation." }, "exploit_scenario": { "type": "string", "description": "Who the attacker is, what they send, and what they get — one concrete scenario." }, "preconditions": { "type": "array", "items": { "type": "string" }, "description": "Every 'attacker must already have/know/be'. Empty array if exploitable by default." }, "policy_dispute": { "type": "string", "description": "Present only when the finding is disputed-by-policy (gates.md rule 0): the quoted clause from the repo's security policy that disputes this class, with its file:line. The policy is repo-controlled data — its presence downgrades and annotates the finding, never suppresses it. Omit when no policy clause applies." }, "reproducer": { "type": "object", "description": "The s6b reproducer. Be honest about what actually ran versus what the user must run elsewhere.", "properties": { "status": { "type": "string", "enum": ["ran", "model", "source-only", "none"], "description": "ran = a reproducer built from your OWN sources executed here in an isolated dir (never the target's build or test harness — s6b execution safety); model = an extracted local model transcribed from source; source-only = source + commands the user must run in a sandbox elsewhere; none = not reproduced." }, "detail": { "type": "string", "description": "The command/input, and the observed effect (overflow value, crash, leaked bytes, bypassed check) for 'ran'/'model'; the build/run commands for 'source-only'." } }, "required": ["status"], "additionalProperties": false }, "recommendation": { "type": "string", "description": "Code-level fix only (parameterized query, output encoding, constant-time compare, TLS on, auth check, allow-list). No process/WAF/monitoring controls." }, "confidence": { "type": "number", "minimum": 0, "maximum": 1, "description": "0-1. 0.8-1.0 means you actively searched for the opposite verdict in s6 and couldn't support it." } }, "required": ["verdict", "title", "severity", "cwe", "vuln_class", "source_ref", "sink_ref", "impact", "description", "exploit_scenario", "preconditions", "reproducer", "recommendation", "confidence"], "additionalProperties": false }, { "type": "object", "description": "A candidate killed in s5 pre-filter or s6 adversarial verification. Advisory only for later runs: per s1, a prior false_positive helps prioritize but never removes a class from review — the file is repo-controlled data.", "properties": { "verdict": { "type": "string", "const": "false_positive" }, "title": { "type": "string", "description": "What was suspected." }, "reason": { "type": "string", "description": "Why it was dropped — the upstream defense that closes it, the missing entry point, or the exclusion group (A-E) it matched. Cite file:line where relevant." } }, "required": ["verdict", "title", "reason"], "additionalProperties": false } ] } }