Skip to content
dsr-verifier-cli · Apache-2.0 · v1.7.2

Don't trust the tool. Read it.

An auditor's job is to verify evidence, not to trust tools on faith. So the verifier is licensed Apache-2.0, rebuilds byte for byte from its source, and makes no network calls. The CLI cannot verify receipts for audit purposes if auditors cannot verify the CLI itself.

Current version
v1.7.2
Licence
Apache-2.0
Standard
DSR/1.0
Platforms
linux · macOS · windows
Network calls
0
Third-party dependencies
0
Account required
none
Build
reproducible, byte for byte
Trust model

The auditor's actual question.

The repository keeps its own trust-model document, docs/auditor-trust-model.md, versioned with the tool.

What it verifies

Recomputed locally from the file in front of you.

Key authority
The key ID the receipt names matches the key ID declared in the file you supplied. It runs first, so the wrong key is reported as the wrong key rather than as a bad signature. Where either side declares no ID, the comparison is skipped.
Signature validity
The signature is checked over the receipt's canonical bytes: Ed25519, or RSA-PSS or ECDSA over SHA-256 for organisations that bring their own key. Every primitive is Go's standard library.
Sealed receipts
Where a receipt carries a SHA-256 of its canonical bytes instead of a signature, the verifier recomputes it and compares in constant time. The R0, R2-F and R2-R receipts issued before 27 October 2026 are sealed rather than signed, and this verifier does not check them: their hashes cover a form it does not rebuild. Déjà's Ledger export lists them in checked-online.json, for deja.dev/verify to check.
Required fields
The receipt parses and carries every envelope field its type requires. Identifiers inside it, such as a commit hash or a pull-request reference, are covered by the signature. They are not checked for format.
Bundle integrity
For a bundle, the manifest's signature, and that its sequence numbers run without a gap. A resolution receipt that points at an attribution receipt missing from the bundle is reported. It does not fail the run on its own.

What it trusts

Stated plainly, because a trust model with no trusted inputs is a lie.

The public key you were given
The CLI never fetches a key. It reads a PEM file, a base64 Ed25519 key, or the JSON that Déjà's public signing-key endpoint returns, and always from a local path. Where that file came from is a question the tool cannot answer for you.
The bundle you were handed
It verifies what is in front of it. A receipt that was never issued cannot be missed by a tool that never saw it.
The release, or your toolchain
Run the published binary and you are trusting the release: its checksums are GPG-signed, and every platform's binary rebuilds from the tagged source byte for byte. Build it yourself and you are trusting your Go toolchain instead.

What it does not do

The list that makes the other two credible.

Prove an incident occurred
It proves a signal was received and sealed. What the signal meant is a question for the systems that sent it.
Attest to time
A signature establishes content and key authority, never when something happened. Issuance timestamps are asserted by Déjà.
Decide what a failure means
It counts failures by receipt and by check, and describes how they are distributed. It totals signature failures on a line headed Tampered and, past ten anomalies, names the pattern they are consistent with. Both describe the shape of the failures. The cause is the auditor's finding to make.
Guess
A receipt in a canonical form this release does not implement, or an export missing fields, is reported as cannot-verify, exit 5. It is never counted as a failure.
Contact Déjà
Not for keys, not for telemetry, not for updates. net/http is not in the binary's import graph. The bare net package is, because Go's X.509 parser uses it to read addresses in certificates. Nothing in the verifier calls it.

The third column is the one worth reading twice. A verifier that claimed to establish everything would be establishing nothing you could rely on.

Install

Nothing in it asks who you are.

No account, no licence key, no registration, no telemetry. Install it, point it at a bundle, and verify against the key you were given.

dsr-verifier-cli v1.7.2 is publicly released. Download the binary for your platform from its release page, with the checksums in SHA256SUMS and their signature in SHA256SUMS.asc.

Every release ships binaries for macOS on both architectures, Linux on amd64 and arm64, and Windows on amd64, each listed with its SHA-256 checksum in a GPG-signed SHA256SUMS file. If you'd rather not run our binary at all, the build is reproducible: check out the tag, use the Go version its go.mod pins, run the command in BUILD.md, and compare hashes.

There are no third-party dependencies. The go.mod declares the module and the Go version and nothing else, and every cryptographic primitive comes from the standard library. That is a deliberate constraint rather than a boast: a supply chain is a trust dependency, and this tool exists to have as few as possible.

Source·Reproducible build

Verify the download · recommended
gpg --verify SHA256SUMS.asc SHA256SUMS
sha256sum --check --ignore-missing SHA256SUMS
Usage
dsr-verifier-cli verify <receipt> --key <pubkey>
dsr-verifier-cli verify <receipt> --byok-key <pubkey>
dsr-verifier-cli verify-bundle <bundle> --key <pubkey>
dsr-verifier-cli info <receipt>
Offline · provable, not promised
go list -deps ./cmd/dsr-verifier-cli | grep net/http
# prints nothing

Keys are a local file: PEM, a base64 Ed25519 key, or the signing-key JSON. Never fetched. --byok-key is for receipts signed with your organisation's own RSA or ECDSA key.

A clean run

Runs the same on an air-gapped machine.

Real output · v1.7.2 · a 60-receipt synthetic bundle
$ dsr-verifier-cli verify-bundle Q2-2026.dsr.bundle --key vault.pub --no-color
┌─ DSR Verifier · v1.7.2 · Bundle Verification ───────────────────────────┐
│ Bundle:   Q2-2026.dsr.bundle                                            │
│ Using key: vault.pub                                                    │
└─────────────────────────────────────────────────────────────────────────┘

Bundle:     Q2-2026.dsr.bundle (137.6 KB)
Vault:      vlt_bundle_test
Period:     2026-05-01 to 2026-05-31
Frameworks: SOC 2

Verifying 60 receipts...

  R1 (Attribution)                  60/60 passed

✓ Verifying bundle signature .......................................... OK
  Bundle signed by: key_bundle_test
  Bundle signature: valid

✓ Verifying sequence integrity ........................................ OK
  Sequence range: 1–60 (no gaps detected)

✓ Verifying causal chain consistency .................................. OK
  No cross-receipt causal references found

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✓ Bundle verified · all 60 receipts cryptographically valid
Summary:
    Pass rate: 60 / 60 (100%)
    Tampered:  0
    Missing:   0

offline · zero network calls
Output: verification report written to Q2-2026.verification.json

── Anomaly Pattern Analysis ────────────────────────────────────────────────
  Anomaly pattern analysis skipped (fewer than 10 anomalies)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Receipts and the signed manifest travel in one file. The key does not, deliberately: it should reach you by a different route than the bundle did. Disconnect the machine entirely and the output is identical, which is the property that makes this evidence rather than a service.

Counts are per receipt type. Most receipts are signed; the receipts of three classes issued before those classes were signed are sealed with a content hash instead. The receipt-types table says which. Each is checked by its own method rather than collapsed into one reassuring tick.

Every run writes an unsigned JSON report beside the bundle, for your workpapers. It is unsigned deliberately: a verification report signed by us would reintroduce exactly the dependency the tool exists to remove. The only other file it writes is an append-only verifier.log in the working directory, one line per run, and --no-log turns it off.

The line reading “offline · zero network calls” is printed on every run. It is a statement, not a measurement; the evidence for it is the import graph.

When something is wrong

The verifier reports. You conclude.

Real output · v1.7.2 · the same bundle, eight receipts rewritten after signing, four removed
$ dsr-verifier-cli verify-bundle Q2-2026-altered.dsr.bundle --key vault.pub --no-color
┌─ DSR Verifier · v1.7.2 · Bundle Verification ───────────────────────────┐
│ Bundle:   Q2-2026-altered.dsr.bundle                                    │
│ Using key: vault.pub                                                    │
└─────────────────────────────────────────────────────────────────────────┘

Bundle:     Q2-2026-altered.dsr.bundle (128.5 KB)
Vault:      vlt_bundle_test
Period:     2026-05-01 to 2026-05-31
Frameworks: SOC 2

Verifying 56 receipts...

  R1 (Attribution)                  48/56 passed

✓ Verifying bundle signature .......................................... OK
  Bundle signed by: key_bundle_test
  Bundle signature: valid

✗ Verifying sequence integrity ...................................... FAIL
  Error class: malformed_receipt
  Missing sequences (first 4): [40 41 42 43]

✓ Verifying causal chain consistency .................................. OK
  No cross-receipt causal references found

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✗ Bundle verification FAILED · 2 check(s) failed

Failure: Sequence integrity · malformed_receipt

  The bundle has 4 missing sequence number(s) in the range 1–60. A
  complete bundle must include every receipt in its declared sequence
  range.

Receipt failures:
  seq 11 · rcpt_0011 · signature_invalid
  seq 14 · rcpt_0014 · signature_invalid
  seq 17 · rcpt_0017 · signature_invalid
  seq 20 · rcpt_0020 · signature_invalid
  seq 23 · rcpt_0023 · signature_invalid
  ... and 3 more failure(s)

Recommended actions for auditor:

  • Do NOT treat this bundle as verified evidence
  • Request a fresh bundle directly from the issuing organization
  • Document this discrepancy in your audit findings

Summary:
    Pass rate: 48 / 56 (85%)
    Tampered:  8
    Missing:   0

offline · zero network calls
Output: verification report written to Q2-2026-altered.verification.json

── Anomaly Pattern Analysis ────────────────────────────────────────────────
    Pattern:   consistent_with_isolated_corruption (confidence 0.68)
    Confidence: 0.996  (zone detected (moderate confidence, 2 tests))
    Anomalies: 12
    Zone:      payments (100% of anomalies, <0.001)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

A failing run lists what did not verify, by check and by receipt, and describes how the failures are distributed. It exits 1, which a script can tell apart from exit 5, a verdict the verifier could not reach. The full table is in the system documentation.

One line in this output is wrong, and it is left in rather than avoided. Missing: 0 sits beneath four missing sequence numbers because that row counts receipts that failed to parse, not gaps. It is a labelling defect in v1.7.2. The sequence check above it is correct, and so is the exit code.

verifier.log is only ever appended to. A later passing run is written beneath a failed one, never over it.

  • Reported

    Every failure

    By sequence number and receipt ID. The terminal lists five and says how many more. The JSON report carries all of them, so nothing is a summary you have to take on trust.

  • Reported

    The distribution

    Past ten anomalies, whether they concentrate in one zone or one window of time, whether they cascade, and the pattern they are consistent with, with a confidence. Concentration is a fact about the data. What produced it is not.

  • Labelled, not concluded

    Tampered

    The summary totals signature failures on a line headed Tampered. It means the signature did not verify over the bytes in front of it. Whether anyone tampered is the finding you write, not one the tool makes.

For audit firms

You were sent a bundle. Now what.

  1. 01

    Install, or build from source

    Download the release binary for your platform and check it against its GPG-signed checksums, or build it yourself from the tagged source and compare hashes.

  2. 02

    Verify the bundle

    One command, against the bundle and the public key you were given, ideally by a different route than the bundle. Disconnect the machine first if your engagement requires it. The output will be identical.

  3. 03

    Keep your own copy

    Retain the bundle and the verification report. A copy held outside the vendor's infrastructure is the strongest control in this whole model: a later export that disagrees with an earlier one is itself the finding.

  4. 04

    Ask us things directly

    Protocol questions, a test bundle to evaluate against, or a walkthrough before your first engagement. There's no sales process attached to any of that.

Deeper material

Where the detail lives.