@walwarden/verifier
v0.0.1
Published
Offline Evidence bundle verifier for Walwarden — verifies Manifest signatures and Audit-chain integrity.
Maintainers
Readme
@walwarden/verifier
Offline verifier for Walwarden Evidence bundles.
walwarden-verify checks that every Manifest in a Walwarden Evidence bundle was signed by the publishing organization's Ed25519 key, and that the Audit chain over that bundle matches its on-write algorithm. The CLI is read-only on disk, requires no network, and depends only on Node ≥20 — an auditor can run it on their own machine without trusting any Walwarden surface.
Install + run
# One-shot, no global install:
npx @walwarden/verifier --bundle <evidence-bundle.tgz> --pubkey <pubkey.pem>
# Local install:
npm install -g @walwarden/verifier
walwarden-verify --bundle <evidence-bundle.tgz> --pubkey <pubkey.pem>The publishing organization's public key is at:
https://walwarden.com/.well-known/walwarden-pubkey.pemAfter a key rotation, prior keys remain downloadable at:
https://walwarden.com/.well-known/walwarden-pubkey-{YYYY-MM-DD}.pemWhat it checks
- Every
manifest.jsoninside the Evidence bundle has asignaturefield, the signature is valid Ed25519 over the manifest's canonical JSON form (withsignaturestripped), and the signature verifies against the supplied public key. - For every Manifest whose corresponding artifact bytes are also in the bundle, the bundle artifact's SHA-256 matches the Manifest's
checksumSha256. Bundles without artifacts skip this with aSKIPline — the auditor sees the omission rather than a silent pass. - The Audit chain in
audit_events.jsonis intact:seqstarts at 1 and increases by 1 with no gaps- every row's
prevHashequals the previous row'shash - every row's
hashequalssha256(prevHash || canonicalJson({ at, jobId, kind, payload }))
Output
Default human-readable form, one line per check:
OK: manifest signature valid for backup_job=job_alpha
OK: manifest artifact sha256 matches for backup_job=job_alpha (a3c8f1b6e7…)
OK: audit chain intact through seq=42 (42 audit events)
OK: 1 manifests verified, 42 audit events chain-intactFailure lines are deliberately verbatim so dashboard surfaces and runbooks can quote them without copy-paste drift:
FAIL: audit chain broken at seq=23, computed_hash=<64hex>, stored_hash=<64hex>
FAIL: manifest signature invalid for backup_job=<id>Pass --json to emit one JSON object instead. Stable shape:
{
"ok": true,
"manifestResults": [...],
"chainResult": {...},
"summary": { "manifestsVerified": 1, "manifestsTotal": 1, "auditEventCount": 42, "chainOk": true }
}Exit codes
| Code | Meaning |
|---|---|
| 0 | Every Manifest signature verified and the Audit chain is intact. |
| 1 | At least one Manifest signature did not verify, or the Audit chain diverged. The offending backup_job_id and audit-event seq are named on stdout. |
| 2 | Usage error (missing flag, unreadable file, malformed PEM, etc.). |
Bundle layout
Walwarden Evidence bundle is a .tar.gz containing:
manifests/<backup_job_id>.manifest.json (one or more)
audit_events.json
artifacts/<artifact_key> (optional)Manifest format follows @walwarden/core's BackupManifest (schemaVersion: 1) with a sibling signature: string (base64-encoded raw 64-byte Ed25519 signature). audit_events.json is a JSON array of stored audit-event rows (seq, at, kind, jobId, payload, prevHash, hash).
v0 status
This package is published from the Walwarden monorepo at noncelogic/walwarden. The published tarball includes a pre-built dist/ (emitted by npm run build in this package, wired through the prepack step) so an auditor running npx @walwarden/verifier invokes the compiled JS directly — no TypeScript loader, no workspace dev dependencies, only Node ≥20.
The verifier is zero runtime dependencies by design — an auditor's npx install must be quick and its supply-chain footprint small.
See also
- Walwarden architecture
- Walwarden brand & positioning
- Issue #52 — engineering ticket
- Issue #66 Q-op-2 — operator-owned key generation/rotation
