Vulnerability scanners mostly answer a question nobody asked: what does the manifest say is installed? Manifests drift. ZTVS answers what is actually here, and can I prove it.
The problem#
Three claims sit between a scan and the truth, and conventional tooling treats all three as facts:
- The manifest describes the artefact — until a dependency is vendored by hand or a binary is patched in place.
- The scanner ran as advertised — unverifiable once the report leaves the machine that produced it.
- The advisory data is complete — someone else's database, usually unattributed in the output.
The approach#
Every finding carries the evidence that produced it — source, path, line or layer digest — as its justification. Packages resolved from a lockfile are verified against the filesystem, and disagreements are themselves reported.
Reports are signed. Verification is a separate command, on purpose:
$ ztvs verify report.json --attestation report.json.att
✓ signature valid
✓ scanner ztvs 0.0.2
✓ advisories snapshot 2026-08-17T04:00:00Z
✓ subject digest matches report
Someone who distrusts me can run that. That is the entire point.
Suppression is a decision, not a flag#
Accepted risks are declared in policy, with a reason and an expiry, and stay visible in the report:
suppress:
- where:
advisory: ZTVS-2026-0044
reason: "Not reachable — the vulnerable codec path is never constructed"
review_by: 2026-11-30
Both fields are required. A suppression without a reason is rejected at parse time; one whose date has passed becomes a finding again.
Trade-offs#
ZTVS reports fewer things than a manifest scanner, because it declines to report what it cannot evidence. Teams used to long lists read that as the tool missing things. It is the tool refusing to guess.
The scanner is also slower — verifying against disk costs more than reading a
lockfile. --no-verify exists, and using it puts you back where you started.
Written in Rust. Docs are in the ZTVS app.