Findings, proof & reporting
The end of the loop: what a run concluded, turned into something a triager — or a client — can act on.
Findings
nullhawk findings ./engagement # list: severity, confidence, status
nullhawk findings ./engagement --actionable # only the ones worth acting on now
nullhawk findings ./engagement --show <finding-id> # the full claim and its evidence
nullhawk findings ./engagement --triage <finding-id> --status false-positive
Storage refuses a claim that fails its own validation. Re-running a check updates the claim
rather than duplicating it, and leaves your triage decision alone — but confidence follows the
evidence currently attached, so a re-run without --verify can take a Confirmed finding
back down to what the stored comparison actually supports. A findings list that un-dismissed
things is a list nobody reads.
Proof of concept
nullhawk poc ./engagement <finding-id>
Compiles a finding into steps someone can run, built from the exchanges it already cites and
nothing else. Credentials become placeholders named after the identity (the same one in every
step), so a reader supplies two values and runs the whole thing — curl where curl can express
the request, a stated reason where it can't.
Reports
nullhawk report ./engagement --format markdown --output report.md
nullhawk report ./engagement --format html --output report.html
nullhawk report ./engagement --format json
nullhawk report ./engagement --format sarif --output findings.sarif # Pro (for CI)
nullhawk report ./engagement --actionable --severity high
A report quotes the request and response behind every claim; a citation the project can't resolve is printed as missing, with the reason, and counted in the caveats. Scope, identities, and coverage come first, so a clean run reads as a record of what was tested, not a clean bill of health. Credentials are redacted with the length of what was removed.
The HTML report escapes everything and loads nothing — no script, no external stylesheet, no CDN font. It quotes bodies from the application you just attacked, some of which exist because that app reflects input; an unescaped or phone-home report would deliver the payload it documents, on the client's machine. Review quoted bodies before sharing.
Snapshots & retest (Pro)
Record what the engagement looks like now and compare two moments, so a retest shows what was fixed, what regressed, and what's new:
nullhawk snapshot take ./engagement --label "initial"
# ... retest later ...
nullhawk snapshot diff ./engagement --since "initial"
A snapshot stores claims, scope, identities, and declared objects as copies, so a later
run can't rewrite its own past. diff never says fixed: a claim that stopped appearing is
reported as gone, with the reason — and only one of the reasons is about the application at
all (the other two are "nothing re-tested it" and "the check changed"). Because findings carry
the detector and version that produced them, a regression isn't confused with a rewritten
check.