Skip to content

Releases: moonrunnerkc/dumpscan

v1.0.0 - reproducible, signed vulnerability claims

Choose a tag to compare

@moonrunnerkc moonrunnerkc released this 03 Sep 22:13

Two scanners run against the same image disagree about 66 to 80 percent of what they find, and neither can tell you why, because neither wrote down what it was looking at. A scan report pins a wall clock timestamp and nothing else: no version for the advisory database, none for the version comparison, none for the suppressions. "The scan was clean on Tuesday" is unfalsifiable by Friday.

dumpscan is built the other way round. Every result is a claim that pins four digests, and anyone holding the claim can re-derive it.

The guarantee

Same input digest, same feed digest, same comparator ruleset digest, same exclusions digest gives a byte-identical findings file, on any OS, any Node 22 or later, any locale, any timezone. Those four digests ride in a signed in-toto predicate alongside the findings Merkle root.

When the answer changes, dumpscan diff names which of the four moved and shows the evidence, down to the JSON pointer:

input       same   sha256:725b0669...
feed        moved  sha256:8fb337fd... -> sha256:93ad3d65...
comparator  same   sha256:c3baa729...
exclusions  same   none

2 findings changed
  changed feed  npm cheeseparser 4.17.20 DUMPSCAN-NPM-0001
  changed feed  npm cheeseparser 4.17.20 DUMPSCAN-NPM-0003

  /affected/0/ranges/0/events/1/fixed  "4.17.21" -> "5.0.0"
  /withdrawn                           "2025-02-19T08:00:00Z" -> absent

That second one is an advisory somebody retracted in February coming back. dumpscan keeps withdrawn findings in the set with a status rather than dropping them, which is the only reason there was anything to compare against. A change that nothing accounts for exits 3, because that is a bug in dumpscan rather than a fact about your code.

What is in 1.0.0

Eight commands. snapshot, scan, replay, verify, diff, prove, explain, publish. All take --json.

Eleven lockfile parsers. package-lock.json v2 and v3, pnpm-lock.yaml v6 and v9, yarn.lock classic and Berry, uv.lock, poetry.lock, Pipfile.lock, requirements.txt when fully pinned, Cargo.lock v3 and v4, go.sum with go.mod, Maven dependency:list, and gradle.lockfile. Each has a golden fixture test.

Five version comparators written from each ecosystem's reference implementation: SemVer 2.0.0, PEP 440, Cargo, Go module ordering including pseudo-versions, and Maven ComparableVersion. Each is property tested for antisymmetry and transitivity and checked against a committed corpus. The hash of those corpora is comparatorRulesetDigest, so a semantics change is visible even at an unchanged package version.

OSV ranges evaluated as event lists, not through a semver library's satisfies. Introduced, fixed, last_affected, and limit, per the OSV schema.

Keyless Sigstore signing with certificate identity, Rekor inclusion, and offline verification against the TUF trust root. verify never re-scans.

Merkle inclusion proofs for a finding against the findings root and for an advisory record against the feed root. An advisory that is not in the set comes back with every leaf, so a verifier can recompute the root and look.

Exclusions and OpenVEX, with excluded findings kept in the set with a status so a suppression is a change someone can see.

explain aligns Grype or Trivy output to the same manifest and buckets every disagreement as an identifier mismatch, a feed difference, a range interpretation, or a suppression. It is a diagnostic and is never signed.

A composite GitHub Action with base-branch comparison and SARIF for code scanning.

@dumpscan/canon (RFC 8785 canonicalization) and @dumpscan/merkle (RFC 6962 trees and proofs) have zero dependencies and are useful on their own.

How the determinism is enforced

  • canon, merkle, versions, match, and predicate are lint-enforced pure. Date.now, new Date, Math.random, process.env, Intl, localeCompare, toLocaleLowerCase, bare .sort(), and the filesystem and network modules are banned in those packages by rule.
  • The JCS implementation is fuzz tested against a second implementation written from the RFC with its own escape table and comparator.
  • pnpm test:determinism runs the fixture corpus twice, under TZ=UTC LANG=C and TZ=Pacific/Kiritimati LANG=tr_TR.UTF-8, and byte compares. CI runs it on ubuntu, macos, and windows and diffs all three.
  • 752 tests. Coverage 94.87 percent overall, 100 percent on canon and merkle. Mutation score 93.81 percent on versions and match, floor 90.

That last one caught a real bug: the published snapshot archive was a zip, and every JavaScript zip writer derives its MS-DOS timestamp from local-time Date methods, so the same snapshot packed in Denver and in Berlin produces different bytes, and packing fails outright west of UTC. It is a gzipped tar with constant headers now.

Install

npm install --global dumpscan
dumpscan snapshot --ecosystems npm,PyPI --out ./snapshot
dumpscan scan package-lock.json --snapshot ./snapshot --sign
dumpscan verify dumpscan.bundle.json --issuer https://token.actions.githubusercontent.com

Node 22.22.2 or later. No native modules.

What it deliberately does not do

No --latest: the caller pins a snapshot digest, because a scanner that picks the newest feed for you cannot make the same claim twice. No reachability, exploitability, or risk scoring. No dropped findings. No guessing at a range it cannot evaluate, which becomes unevaluated with a reason rather than "not affected". No wrapping Grype or Trivy. Lockfiles only in v1: CycloneDX and SPDX bring back the identifier ambiguity that causes cross-tool chaos in the first place.

Known limits

  • Consistency proofs are implemented and tested in merkle but no command emits one. Neither the feed tree nor the findings tree is append-only, so there is no pair of trees a consistency proof would be sound between. ADR 0013.
  • The daily snapshot publishing workflow is covered end to end against a local store but has never run against real OSV, because this repository is new.
  • The launch writeup in docs/launch.md uses this repository's fixtures rather than a public project's lockfiles, and says so. Grype and Trivy were not available in the environment it was written in.

docs/build-guide.md is the specification, docs/decisions/ holds thirteen ADRs, and docs/progress.md is the build log.

dumpscan feed snapshots

Choose a tag to compare

@github-actions github-actions released this 04 Sep 05:28

Content addressed OSV feed snapshots. Each asset is named after its feed digest. index.json maps a date to the digest published that day.