The Audit and Audit + Agent Benchmark packages include an Evidence Pack: the report, the captured evidence behind it, and the signatures and third-party timestamps that let any recipient check the files and when they were committed. This article shows how to verify one yourself with one command, what each check establishes, and what a tampered pack looks like when it fails.
Audit reports are PDFs you take on trust
A security audit report usually arrives as a PDF, and readers judge it by where it came from: the auditor's website, a GitHub repository, or an email from the vendor. Trail of Bits, Zellic, NCC Group, Cure53, OpenZeppelin and OtterSec all publish strong work this way. In each case, where the file came from is what a reader relies on.
That channel fails in documented ways:
- Least Authority found an altered copy of one of its reports on a look-alike domain and concluded that report integrity should be checkable through technical means, not only by reputation.
- CertiK has described fraud that uses its brand for fake audit claims.
- In 2026, allegations that Delve fabricated SOC 2 and ISO evidence (which Delve denies) turned attention to whether the evidence behind an attestation exists at all.
A report that travels beyond the team that commissioned it, to customers, insurers and procurement, needs a check that works without trusting whoever forwarded it.
What you should be able to check
A reader holding an audit report should be able to answer three questions without contacting the auditor:
- Is this the evidence the auditor delivered, unaltered? Every log, script, result file and the report itself, byte for byte.
- Was it recorded in the order claimed? The harness first, then the runs, then the findings and the report.
- When did each stage exist, according to someone other than the auditor? An auditor's own clock is a claim, not evidence.
An IsoKron Evidence Pack answers all three on your own machine, with open-source tools.
What is in an IsoKron Evidence Pack
The pack is a directory. Every file in it is listed in a signed checksum file, so nothing can be added, removed or changed without the check failing.
| File | What it holds |
|---|---|
bundle/ | The evidence bundle: manifest.json (signed), events.jsonl (the audit's hash-chained event log) and blobs/, where each artifact is named by its own SHA-256 |
anchors/NN-<milestone>.receipt.json | One anchor receipt per milestone (harness frozen, runs completed, report issued), countersigned by IsoKron's anchor service |
anchors/NN-<milestone>.<tsa>.tsr | RFC 3161 timestamp tokens over the SHA-256 of each receipt, one from Sectigo and one from DigiCert |
auditor-key.pem | The auditor public key for this audit. Trusted only when its fingerprint matches the one in your signed statement of work |
anchor-keys.json | The anchor service's published key list |
tsa-roots/ | The pinned timestamping CA certificates, for the openssl route |
claims.json | The claims this pack makes |
COMPLETION.txt | The completion letter, one page of values to compare with the copy emailed to you separately: manifest SHA-256, signing key, final anchored root, timestamp times, claims |
VERIFY.md | The one-command check, and the raw shasum / openssl / jq commands with this pack's own file names |
SHA256SUMS, SHA256SUMS.sig | A SHA-256 for every other file, and the auditor key's Ed25519 signature over that list |
The file layout and every check are specified in the open-source kronicle-lite evidence bundle documentation.
How it works
Three standard mechanisms, layered so that each covers what the one before cannot.
1. Hash, chain and sign. Every artifact (a raw MCP request and response log, a reproduction script, a results file, the report) is stored under its SHA-256. Each audit event records those hashes and links to the previous event's hash, forming a hash chain. The auditor key signs the Merkle root over the chain and a manifest of every file. Change one byte anywhere and a hash stops matching.
2. Anchor each milestone. At each stage of the audit (harness frozen, runs completed, report issued) the auditor signs a checkpoint over events 1 to k and sends only that root, the range and the signature to IsoKron's anchor service at anchor.isokron.ai. The service countersigns it and returns a receipt. No evidence leaves the auditor's machine.
3. Timestamp every receipt independently. The SHA-256 of each receipt goes to two public RFC 3161 timestamp authorities, Sectigo and DigiCert. Each returns a signed token stating that this digest existed no later than its time. The authorities receive a hash, never the receipt or the evidence.
A hash chain on its own can be rebuilt by whoever holds the signing key, with any timestamps they like. The anchors fix each stage's root, and the independent timestamps fix when that root existed. To rewrite history after the fact, the auditor would need new tokens from both authorities, and those tokens carry the later date.
Each anchor covers every event before it, so a later stage can only extend an earlier one. Changing any event changes every root from that point on, and no longer matches the receipts the authorities already stamped.
Verify it yourself
The check runs locally and needs Node.js 20 or later; npx fetches kronicle-lite from npm on first use, and the pack never leaves your machine. RFC 3161 tokens are verified in-process, so openssl is optional. Nothing is uploaded anywhere. The same command, the anchor service's genesis key fingerprint and the pinned timestamp authority CAs are also on our verification page.
Step 1: Get the pack and the key fingerprint
Download the sample pack: isokron-sample-evidence-pack.tar.gz (SHA-256 16754c842b8e016d77aab07b67ebcfe2a907f47130141c83334e259b734ac130). It is an IsoKron sample built for this article: a three-run audit of a fictional MCP server, with synthetic data and live anchors and timestamps. For your own audit, the pack is part of the delivery.
Take the auditor key fingerprint from your signed statement of work, agreed before the audit starts. Never take it from the pack: anyone who can edit the pack can also replace the key inside it. The fingerprint is the SHA-256 of the key's public key info (SPKI). You can compute it from the pack's key file to see what you are about to compare:
openssl pkey -pubin -in auditor-key.pem -outform DER | shasum -a 256Step 2: Run one command
npx -y kronicle-lite@0.10.1 verify-audit PACK --key-fingerprint <from your statement of work>PACK is the pack directory. kronicle-lite is open source (Apache-2.0) and published on npm. Pinning the version means every reader runs the same published release.
The sample pack's auditor key fingerprint is d70b311bf6e25c0c20ec3b4645f806f28b8712e212142720eeb51d2c6fa5072b. On the anchor service, a chain's ID is the same SHA-256 of the auditor key, so this value is also the address of the sample's verification page. Here is the full output:
$ npx -y kronicle-lite@0.10.1 verify-audit isokron-sample-evidence-pack --key-fingerprint d70b311bf6e25c0c20ec3b4645f806f28b8712e212142720eeb51d2c6fa5072b
IsoKron Evidence Pack …/isokron-sample-evidence-pack
audit isokron-sample-2026-11 (kronicle-lite-evidence-bundle/2)
auditor key ed25519-d70b311bf6e25c0c spki sha256 d70b311bf6e25c0c20ec3b4645f806f28b8712e212142720eeb51d2c6fa5072b (pinned by --key-fingerprint)
service keys genesis 3dca1ecf66c2993e534ff08fe350dc9368ca52042e38b20a9d58cd7c2b7b3367@2026-11-01T02:29:25.705Z (compiled pin); key list from anchor-keys.json: intact
key list first seen for this genesis; recorded in …/anchor-keys.seen.json (later lists are checked against it)
TSA CAs shipped timestamping CA pins (Sectigo R46, DigiCert G4 TimeStamping), validity checked at the current time
Claims
PASS integrity 5 event(s), 0 stub(s), payloads match their schemas, kronicle-lite-evidence-bundle/2; 5 artifact(s) hash to their names; manifest sha256 45905afc920359eaac8516bd57f0a056b6d216267d08b80110d804df29000587; 24 file(s) match; signed by ed25519-d70b311bf6e25c0c
PASS chain seq 1..5 linked, root e5f727cfe1462aa4a7c7dd3f3d82c0c87eaf5c221450a0ac398560f33e71a863
PASS receipts 3 anchor(s) countersigned by anchor-ed25519-3dca1ecf66c2993e (key chain intact to genesis 3dca1ecf66c2993e…); roots match the bundle
PASS independent_timestamps 6 of 6 RFC 3161 token(s), each receipt stamped under ≥ 2 pinned TSA CAs (digicert, sectigo)
PASS harness_frozen_before_runs harness 654b903b60950240… frozen, committed 2026-11-09T14:07:20.000Z (01-harness-frozen, RFC 3161); 3/3 run(s) name that harness, carry the freeze receipt digest 95c3b7bc5171184cefbe39607ece34534ab3a6e3dd3cb4f9c56f8eec9ffea9c8 and are committed by later timestamped anchors (02-run-completed)
PASS run_sequence 3 run(s), run_seq 1..3 contiguous; matches planned_runs 3, report_run_count 3, claims.json 3; no capture interruptions
Pack
PASS auditor key ed25519-d70b311bf6e25c0c, SHA-256(SPKI) d70b311bf6e25c0c20ec3b4645f806f28b8712e212142720eeb51d2c6fa5072b = --key-fingerprint
PASS SHA256SUMS 24 file(s) match; signed by ed25519-d70b311bf6e25c0c
PASS claims.json declares integrity, chain, receipts, independent_timestamps, harness_frozen_before_runs, run_sequence (authenticated by SHA256SUMS)
Evidence bundle
PASS canonical manifest sha256 45905afc920359eaac8516bd57f0a056b6d216267d08b80110d804df29000587
PASS signature signed by ed25519-d70b311bf6e25c0c
PASS events 5 event(s), 0 stub(s), payloads match their schemas, kronicle-lite-evidence-bundle/2
PASS chain seq 1..5 linked
PASS root root e5f727cfe1462aa4a7c7dd3f3d82c0c87eaf5c221450a0ac398560f33e71a863
PASS artifacts 5 artifact(s) hash to their names
PASS refs every reference resolves
PASS missing 0 missing
Anchor service keys
PASS anchor key list intact: 1 key(s), each verified back to the pinned genesis key: anchor-ed25519-3dca1ecf66c2993e (genesis); genesis 3dca1ecf66c2993e534ff08fe350dc9368ca52042e38b20a9d58cd7c2b7b3367@2026-11-01T02:29:25.705Z
Anchors
01-harness-frozen seq 1..1 received 2026-11-09T14:07:17.906Z id 01a1c0a2-8192-7f52-b0ac-e43c4d3a6e58
PASS receipt canonical sha256 95c3b7bc5171184cefbe39607ece34534ab3a6e3dd3cb4f9c56f8eec9ffea9c8
PASS service signature anchor-ed25519-3dca1ecf66c2993e (active, valid 2026-11-01T02:29:25.705Z → now, genesis; key chain verified), received_at 2026-11-09T14:07:17.906Z
PASS auditor checkpoint ed25519-d70b311bf6e25c0c, seq 1..1
PASS root matches bundle seq 1..1 root 9ceac115bbc69da83c6b844e5c24651dd147d0e28dd63cfe7774732eeee1b746
PASS RFC 3161 digicert 2026-11-09T14:07:20.000Z, signed by C=US, O=DigiCert\, Inc., CN=DigiCert SHA256 RSA4096 Timestamp Responder 2026 1, CA C=US, O=DigiCert\, Inc., CN=DigiCert Trusted G4 TimeStamping RSA4096 SHA256 2025 CA1 (digicert)
PASS RFC 3161 sectigo 2026-11-09T14:07:19.000Z, signed by C=GB, ST=Greater London, O=Sectigo Limited, CN=Sectigo Public Time Stamping Signer R37, CA C=GB, O=Sectigo Limited, CN=Sectigo Public Time Stamping Root R46 (sectigo)
PASS independent TSAs valid tokens under 2 pinned TSA CAs (digicert, sectigo)
PASS stamped within key validity earliest TSA time 2026-11-09T14:07:19.000Z; anchor-ed25519-3dca1ecf66c2993e has no valid_to
02-run-completed seq 1..4 received 2026-11-09T14:07:22.841Z id 01a1c0a2-94d9-7e9e-98e6-7edc7c3ec047
PASS receipt canonical sha256 dfc42709cc6871ed81e4347ba153dcd1f7938f88fa446f752d0392b3a574a4c8
PASS service signature anchor-ed25519-3dca1ecf66c2993e (active, valid 2026-11-01T02:29:25.705Z → now, genesis; key chain verified), received_at 2026-11-09T14:07:22.841Z
PASS auditor checkpoint ed25519-d70b311bf6e25c0c, seq 1..4
PASS root matches bundle seq 1..4 root 4f5ecec2eef4e89c2ccf4a997b89e1e63e5141587db940d9a0f7f2b8f715ac06
PASS RFC 3161 digicert 2026-11-09T14:07:24.000Z, signed by C=US, O=DigiCert\, Inc., CN=DigiCert SHA256 RSA4096 Timestamp Responder 2026 1, CA C=US, O=DigiCert\, Inc., CN=DigiCert Trusted G4 TimeStamping RSA4096 SHA256 2025 CA1 (digicert)
PASS RFC 3161 sectigo 2026-11-09T14:07:24.000Z, signed by C=GB, ST=Greater London, O=Sectigo Limited, CN=Sectigo Public Time Stamping Signer R37, CA C=GB, O=Sectigo Limited, CN=Sectigo Public Time Stamping Root R46 (sectigo)
PASS independent TSAs valid tokens under 2 pinned TSA CAs (digicert, sectigo)
PASS stamped within key validity earliest TSA time 2026-11-09T14:07:24.000Z; anchor-ed25519-3dca1ecf66c2993e has no valid_to
03-report-issued seq 1..5 received 2026-11-09T14:07:27.182Z id 01a1c0a2-a5ce-76a3-a4ee-1cb0b582e316
PASS receipt canonical sha256 afe8de451e0c0b88226488b8214e7f21251956a043f561a4b1cfb21a5c124bb8
PASS service signature anchor-ed25519-3dca1ecf66c2993e (active, valid 2026-11-01T02:29:25.705Z → now, genesis; key chain verified), received_at 2026-11-09T14:07:27.182Z
PASS auditor checkpoint ed25519-d70b311bf6e25c0c, seq 1..5
PASS root matches bundle seq 1..5 root e5f727cfe1462aa4a7c7dd3f3d82c0c87eaf5c221450a0ac398560f33e71a863
PASS RFC 3161 digicert 2026-11-09T14:07:29.000Z, signed by C=US, O=DigiCert\, Inc., CN=DigiCert SHA256 RSA4096 Timestamp Responder 2026 1, CA C=US, O=DigiCert\, Inc., CN=DigiCert Trusted G4 TimeStamping RSA4096 SHA256 2025 CA1 (digicert)
PASS RFC 3161 sectigo 2026-11-09T14:07:28.000Z, signed by C=GB, ST=Greater London, O=Sectigo Limited, CN=Sectigo Public Time Stamping Signer R37, CA C=GB, O=Sectigo Limited, CN=Sectigo Public Time Stamping Root R46 (sectigo)
PASS independent TSAs valid tokens under 2 pinned TSA CAs (digicert, sectigo)
PASS stamped within key validity earliest TSA time 2026-11-09T14:07:28.000Z; anchor-ed25519-3dca1ecf66c2993e has no valid_to
Timeline
PASS anchor order 01-harness-frozen → 02-run-completed → 03-report-issued: ranges extend, every clock moves forward
PASS coverage 5 audit event(s), seq 1..5, committed by timestamped anchors
Ordering
PASS harness frozen before runs harness 654b903b60950240… frozen, committed 2026-11-09T14:07:20.000Z (01-harness-frozen, RFC 3161); 3/3 run(s) name that harness, carry the freeze receipt digest 95c3b7bc5171184cefbe39607ece34534ab3a6e3dd3cb4f9c56f8eec9ffea9c8 and are committed by later timestamped anchors (02-run-completed)
Runs
PASS run sequence 3 run(s), run_seq 1..3 contiguous; matches planned_runs 3, report_run_count 3, claims.json 3; no capture interruptions
Milestones
01-harness-frozen: no later than 2026-11-09T14:07:20.000Z (RFC 3161)
seq 1 harness_frozen harness
02-run-completed: no later than 2026-11-09T14:07:24.000Z (RFC 3161)
seq 2 run_completed run:1
seq 3 run_completed run:2
seq 4 run_completed run:3
03-report-issued: no later than 2026-11-09T14:07:29.000Z (RFC 3161)
seq 5 report_issued report
Values to compare
manifest sha256 45905afc920359eaac8516bd57f0a056b6d216267d08b80110d804df29000587
auditor key ed25519-d70b311bf6e25c0c (spki sha256 d70b311bf6e25c0c20ec3b4645f806f28b8712e212142720eeb51d2c6fa5072b)
final root e5f727cfe1462aa4a7c7dd3f3d82c0c87eaf5c221450a0ac398560f33e71a863 (seq 1..5, 03-report-issued)
timestamped 2026-11-09T14:07:29.000Z (digicert), 2026-11-09T14:07:28.000Z (sectigo)
RESULT: VERIFIED integrity, chain, receipts, independent_timestamps, harness_frozen_before_runs, run_sequenceRead from the bottom up, this says: the harness (digest 654b903b…) was committed and independently timestamped by 14:07:20 UTC; all three runs carry the digest of that freeze receipt, which did not exist before the freeze; the runs were committed by 14:07:24; the report by 14:07:29. Every root the authorities stamped is recomputed from the events in the pack, and matches. The order of milestones is established by the hash chain, each anchor's range extending the previous one, and the freeze nonce; the timestamps establish when each milestone existed, within each authority's stated accuracy.
Exit code 0 means every checked claim passed and none was left unchecked.
Step 3: Compare the values with the completion letter
The last block, Values to compare, prints the manifest SHA-256, the auditor key, the final anchored root and its timestamp times. The same values are in the completion letter: the pack carries it as COMPLETION.txt, and IsoKron also sends it by email, separately from the pack, so you hold a copy that did not travel with the pack. They must match. A matching manifest SHA-256 ties the evidence you just verified to the audit IsoKron delivered, and the report is one of the files it covers.
The sample's anchors are also public at anchor.isokron.ai/v/d70b311b…, which shows all three as VERIFIED. That page shows the anchor service's own timestamp tokens over the same receipts, requested separately, so its times differ from the pack's by a second or two. Both sets are valid; verify-audit checks the tokens in the pack.
Step 4: Check it without our code
Every check can be repeated with standard tools, and each pack's VERIFY.md lists the commands with its own file names. The file checks need OpenSSL 3 (macOS ships LibreSSL, which lacks raw Ed25519 verification; brew install openssl@3):
$ openssl pkeyutl -verify -pubin -inkey auditor-key.pem -rawin -in SHA256SUMS -sigfile SHA256SUMS.sig
Signature Verified Successfully
$ shasum -a 256 -c SHA256SUMS
COMPLETION.txt: OK
VERIFY.md: OK
anchor-keys.json: OK
anchors/01-harness-frozen.digicert.tsr: OK
anchors/01-harness-frozen.receipt.json: OK
anchors/01-harness-frozen.sectigo.tsr: OK
anchors/02-run-completed.digicert.tsr: OK
anchors/02-run-completed.receipt.json: OK
anchors/02-run-completed.sectigo.tsr: OK
anchors/03-report-issued.digicert.tsr: OK
anchors/03-report-issued.receipt.json: OK
anchors/03-report-issued.sectigo.tsr: OK
anchors/index.json: OK
auditor-key.pem: OK
bundle/blobs/48337a5da754808b62439622460f94ecdfd355f6dda202e84cb9a7680646177c: OK
bundle/blobs/654b903b60950240f24d7e629e8cee1b40fdc7a69a66568ead1f5835feb57735: OK
bundle/blobs/c433a463e373f1d37efe8ee26e16b428d67fd68d8cca269e5f77b5fc38c1a10d: OK
bundle/blobs/ec32488a76aff1c97ac427a11af86fb8c2f1af75f24085447d69695b33674196: OK
bundle/blobs/ec475254f1726e24d046bc764e1b5f88eeb7b7c6a203bcab4ae60e263bfb0016: OK
bundle/events.jsonl: OK
bundle/manifest.json: OK
claims.json: OK
tsa-roots/digicert.pem: OK
tsa-roots/sectigo.pem: OK
$ D=$(shasum -a 256 anchors/01-harness-frozen.receipt.json | cut -c1-64)
$ openssl ts -verify -digest $D -in anchors/01-harness-frozen.digicert.tsr -CAfile tsa-roots/digicert.pem -partial_chain
Verification: OK
$ openssl ts -verify -digest $D -in anchors/01-harness-frozen.sectigo.tsr -CAfile tsa-roots/sectigo.pem -partial_chain
Verification: OK
$ openssl ts -reply -in anchors/01-harness-frozen.digicert.tsr -text | grep 'Time stamp'
Time stamp: Nov 9 14:07:20 2026 GMTVERIFY.md repeats the timestamp check for every anchor, verifies the anchor receipts' Ed25519 signatures with jq and openssl pkeyutl, and lists the -attime commands for re-verifying each token at its own time after a certificate expires. The same timestamp check, with CA certificates fetched from the authorities themselves, is shown under "Check the time without our code" below. Recomputing each anchored root from events.jsonl follows the public FORMAT §2 recipe, so a skeptical reader can reimplement that step too.
Reading the result
verify-audit prints one line per claim. A pack lists its claims in claims.json, and the verifier reads that file only after the signed checksum list has verified, so a tampered pack cannot quietly drop a claim it would fail.
| Claim | What a PASS establishes |
|---|---|
integrity | Every file matches the signed SHA256SUMS; nothing is unlisted or missing; the bundle's manifest signature, events, artifacts and references all check under the pinned auditor key |
chain | The audit events form one unbroken hash chain, and the signed Merkle root matches it |
receipts | Each anchor receipt is countersigned by an anchor service key that chains to the pinned genesis key; its root equals the root recomputed from the bundle's own events over the same range; each anchor extends the previous one; every audit event is covered by an anchor |
independent_timestamps | Every receipt carries valid RFC 3161 tokens from at least two different pinned authorities (Sectigo and DigiCert), each over the SHA-256 of the receipt file |
harness_frozen_before_runs | The harness digest was anchored and timestamped before any run was committed, and every run carries the freeze nonce: the digest of the freeze receipt, which did not exist before the freeze |
run_sequence | Runs are numbered 1 to N with no gap or repeat, and N matches the count declared at freeze time and in the report |
integrity and chain are always checked. Every claim shows one of five states:
- PASS: checked, and every check behind it held.
- FAIL: checked, and at least one check failed. The line names which one.
- not declared: the pack does not make this claim. It is a statement about the pack, not a failed check.
- not checked: the pack declares the claim but this run did not check it, for example the timestamp claims under
--no-tsa. A declared claim left unchecked blocks exit 0 and the result readsNOT VERIFIED. - skipped: you opted out of the claim with
--skip-claim.
Two flags change what counts:
--require CLAIM,…checks a claim whether or not the pack declares it, and needs it to pass. A procurement team that accepts only independently timestamped evidence runs--require independent_timestamps. On a pack without anchors, that fails:
$ npx -y kronicle-lite@0.10.1 verify-audit unanchored-pack --key-fingerprint b0af3937bdded873de01ada71b6d9888d8163dfa37a3206e191abaad9ea5850b --require independent_timestamps
…
Claims
PASS integrity 5 event(s), 0 stub(s), payloads match their schemas, kronicle-lite-evidence-bundle/2; 5 artifact(s) hash to their names; manifest sha256 5fb9550dbafcf682e6d269420bf1577a4260399804706a8e8ba396a366548e8d; 15 file(s) match; signed by ed25519-b0af3937bdded873
PASS chain seq 1..5 linked, root fa0932e7cd65cf2c2c19a326a10a4d1fbd757ce8df6f30f5109c91358dffef12
FAIL receipts anchor key list: no keys published: (for a self-hosted anchor service pass --genesis <sha256>@<valid_from> with its genesis key pin)
FAIL independent_timestamps nothing in the pack backs this claim
not declared harness_frozen_before_runs
PASS run_sequence 3 run(s), run_seq 1..3 contiguous; matches planned_runs 3, report_run_count 3, claims.json 3; no capture interruptions
…
RESULT: FAILED receipts, independent_timestamps (3 failed check(s))--skip-claim CLAIM,…opts out of a declared claim explicitly. It is reported as skipped, never silently passed.integrityandchaincannot be skipped:
$ npx -y kronicle-lite@0.10.1 verify-audit isokron-sample-evidence-pack --key-fingerprint d70b311bf6e25c0c20ec3b4645f806f28b8712e212142720eeb51d2c6fa5072b --skip-claim run_sequence
…
Claims
PASS integrity 5 event(s), 0 stub(s), payloads match their schemas, kronicle-lite-evidence-bundle/2; 5 artifact(s) hash to their names; manifest sha256 45905afc920359eaac8516bd57f0a056b6d216267d08b80110d804df29000587; 24 file(s) match; signed by ed25519-d70b311bf6e25c0c
PASS chain seq 1..5 linked, root e5f727cfe1462aa4a7c7dd3f3d82c0c87eaf5c221450a0ac398560f33e71a863
PASS receipts 3 anchor(s) countersigned by anchor-ed25519-3dca1ecf66c2993e (key chain intact to genesis 3dca1ecf66c2993e…); roots match the bundle
PASS independent_timestamps 6 of 6 RFC 3161 token(s), each receipt stamped under ≥ 2 pinned TSA CAs (digicert, sectigo)
PASS harness_frozen_before_runs harness 654b903b60950240… frozen, committed 2026-11-09T14:07:20.000Z (01-harness-frozen, RFC 3161); 3/3 run(s) name that harness, carry the freeze receipt digest 95c3b7bc5171184cefbe39607ece34534ab3a6e3dd3cb4f9c56f8eec9ffea9c8 and are committed by later timestamped anchors (02-run-completed)
skipped run_sequence skipped by user (--skip-claim run_sequence); the pack declares it
…
RESULT: VERIFIED integrity, chain, receipts, independent_timestamps, harness_frozen_before_runs (run_sequence skipped by user)Conflicting instructions are refused rather than resolved silently: --no-tsa with --require independent_timestamps exits 2 with error: --require independent_timestamps needs RFC 3161 tokens; it cannot be combined with --no-tsa. For automation, --json emits the same result in a documented schema.
Watch it fail
A verifier is only worth running if it fails when it should. Here are three ways to alter the sample pack, from a careless edit to the auditor rewriting its own history.
One byte changed in a run result
We changed one byte in the result file of run 3, the run that found the sample defect, turning "fail" into "Fail":
before: ms":{"p50":64,"p95":70},"result":"fail"}
after: ms":{"p50":64,"p95":70},"result":"Fail"}$ npx -y kronicle-lite@0.10.1 verify-audit tampered-pack --key-fingerprint d70b311bf6e25c0c20ec3b4645f806f28b8712e212142720eeb51d2c6fa5072b
…
Claims
FAIL integrity SHA256SUMS: bundle/blobs/48337a5da754808b62439622460f94ecdfd355f6dda202e84cb9a7680646177c does not match its SHA256SUMS line
PASS chain seq 1..5 linked, root e5f727cfe1462aa4a7c7dd3f3d82c0c87eaf5c221450a0ac398560f33e71a863
FAIL receipts 01-harness-frozen root matches bundle: not checked: the bundle does not verify
PASS independent_timestamps 6 of 6 RFC 3161 token(s), each receipt stamped under ≥ 2 pinned TSA CAs (digicert, sectigo)
FAIL harness_frozen_before_runs harness frozen before runs: not checked: the bundle does not verify
FAIL run_sequence run sequence: not checked: the bundle does not verify
Pack
PASS auditor key ed25519-d70b311bf6e25c0c, SHA-256(SPKI) d70b311bf6e25c0c20ec3b4645f806f28b8712e212142720eeb51d2c6fa5072b = --key-fingerprint
FAIL SHA256SUMS bundle/blobs/48337a5da754808b62439622460f94ecdfd355f6dda202e84cb9a7680646177c does not match its SHA256SUMS line
FAIL claims.json not authenticated (SHA256SUMS does not verify): every claim is checked
Evidence bundle
PASS canonical manifest sha256 45905afc920359eaac8516bd57f0a056b6d216267d08b80110d804df29000587
PASS signature signed by ed25519-d70b311bf6e25c0c
PASS events 5 event(s), 0 stub(s), payloads match their schemas, kronicle-lite-evidence-bundle/2
PASS chain seq 1..5 linked
PASS root root e5f727cfe1462aa4a7c7dd3f3d82c0c87eaf5c221450a0ac398560f33e71a863
FAIL artifacts blob 48337a5da754… does not hash to its name
PASS refs every reference resolves
PASS missing 0 missing
…
RESULT: FAILED integrity, receipts, harness_frozen_before_runs, run_sequence (8 failed check(s))The changed file no longer hashes to its name or to its line in the signed checksum list. Because that list no longer verifies, the verifier stops trusting claims.json and checks every claim, and every claim that depends on the bundle's contents fails. The timestamps themselves still verify, because they cover the receipts and nobody touched those. A pack that fails integrity is not partially trusted. Plain shasum -a 256 -c SHA256SUMS catches the same change: bundle/blobs/48337a5d…: FAILED.
Someone else rebuilds the history with their own key
A forger without our key can build an internally consistent pack, without anchors, from the sample's own files: same audit ID, same events, run 3 rewritten as a pass, every hash and signature correct under their own key. Verified against the fingerprint from the statement of work:
$ npx -y kronicle-lite@0.10.1 verify-audit forged-pack --key-fingerprint d70b311bf6e25c0c20ec3b4645f806f28b8712e212142720eeb51d2c6fa5072b
…
Pack
FAIL auditor key the pack's auditor key has SHA-256(SPKI) b0af3937bdded873de01ada71b6d9888d8163dfa37a3206e191abaad9ea5850b, not the pinned d70b311bf6e25c0c20ec3b4645f806f28b8712e212142720eeb51d2c6fa5072b
PASS SHA256SUMS 15 file(s) match; signed by ed25519-b0af3937bdded873
PASS claims.json declares integrity, chain, run_sequence (authenticated by SHA256SUMS)
…
RESULT: FAILED pack structure (1 failed check(s))Every internal check passes. The one that fails is the comparison with a value the forger cannot control, which is why the fingerprint must come from the statement of work and never from the pack.
The auditor rewrites its own history
This is the case the anchors and timestamps exist for. We rebuilt the sample audit from its own files with one change, run 3's failure rewritten as a pass, and signed it with the original auditor key. Then we did what a dishonest auditor would: swapped the new bundle into the pack, regenerated SHA256SUMS and its signature, and updated COMPLETION.txt. Every signature in the result is genuine. The anchor receipts and timestamp tokens are the originals:
$ npx -y kronicle-lite@0.10.1 verify-audit rewritten-pack --key-fingerprint d70b311bf6e25c0c20ec3b4645f806f28b8712e212142720eeb51d2c6fa5072b
…
Claims
PASS integrity 5 event(s), 0 stub(s), payloads match their schemas, kronicle-lite-evidence-bundle/2; 5 artifact(s) hash to their names; manifest sha256 45237877b4eac885701251b4f3ac5a79e3ad1ed738f622aa4137e97ce2804952; 24 file(s) match; signed by ed25519-d70b311bf6e25c0c
PASS chain seq 1..5 linked, root 4b3af4f68b084a585061ca1572583cdc8b7da9153815dcc80bb82a6bcc2d2ebc
FAIL receipts 01-harness-frozen root matches bundle: bundle seq 1..1 has root 8fd72c80a42adaa7…, the anchor holds 9ceac115bbc69da8…
PASS independent_timestamps 6 of 6 RFC 3161 token(s), each receipt stamped under ≥ 2 pinned TSA CAs (digicert, sectigo)
FAIL harness_frozen_before_runs harness frozen before runs: the freeze event (seq 1) is not committed by a timestamped anchor
PASS run_sequence 3 run(s), run_seq 1..3 contiguous; matches planned_runs 3, report_run_count 3, claims.json 3; no capture interruptions
…
Anchors
…
03-report-issued seq 1..5 received 2026-11-09T14:07:27.182Z id 01a1c0a2-a5ce-76a3-a4ee-1cb0b582e316
PASS receipt canonical sha256 afe8de451e0c0b88226488b8214e7f21251956a043f561a4b1cfb21a5c124bb8
PASS service signature anchor-ed25519-3dca1ecf66c2993e (active, valid 2026-11-01T02:29:25.705Z → now, genesis; key chain verified), received_at 2026-11-09T14:07:27.182Z
PASS auditor checkpoint ed25519-d70b311bf6e25c0c, seq 1..5
FAIL root matches bundle bundle seq 1..5 has root 4b3af4f68b084a58…, the anchor holds e5f727cfe1462aa4…
PASS RFC 3161 digicert 2026-11-09T14:07:29.000Z, signed by C=US, O=DigiCert\, Inc., CN=DigiCert SHA256 RSA4096 Timestamp Responder 2026 1, CA C=US, O=DigiCert\, Inc., CN=DigiCert Trusted G4 TimeStamping RSA4096 SHA256 2025 CA1 (digicert)
PASS RFC 3161 sectigo 2026-11-09T14:07:28.000Z, signed by C=GB, ST=Greater London, O=Sectigo Limited, CN=Sectigo Public Time Stamping Signer R37, CA C=GB, O=Sectigo Limited, CN=Sectigo Public Time Stamping Root R46 (sectigo)
PASS independent TSAs valid tokens under 2 pinned TSA CAs (digicert, sectigo)
PASS stamped within key validity earliest TSA time 2026-11-09T14:07:28.000Z; anchor-ed25519-3dca1ecf66c2993e has no valid_to
Timeline
N/A anchor order 0 verified anchor(s)
FAIL coverage seq 1, 2, 3, 4, 5 not committed by timestamped anchors
…
RESULT: FAILED receipts, harness_frozen_before_runs (5 failed check(s))A signature proves which key signed, not when. Whoever holds the key can sign a new history at any time, a limit the IETF agent action receipts draft states plainly. Three things expose this rewrite:
- The anchored roots no longer match. The receipts and tokens are untouched and still verify, but
receiptsrecomputes each milestone's root from the bundle's own events, and none of the three matches what the authorities stamped. Matching them would require new tokens from both Sectigo and DigiCert, and those carry today's date. With no anchor covering the rewritten events,harness_frozen_before_runsfails too. - The values no longer match. The manifest SHA-256 is now
45237877…, not the45905afc…in the completion letter already in the client's hands, including the copy sent by email. - A second root is public. If the rewritten root were anchored over an already anchored range, the anchor service and its verification page would flag the chain as
CONFLICT.
Check the time without our code
The independent time is the part of the pack that relies on IsoKron least, so it should be checkable with no IsoKron code at all. Here is that check on a test checkpoint on the live anchor service, using curl and openssl only.
1. Fetch the receipt and hash it. The timestamp authorities stamped the SHA-256 of the receipt in its canonical form:
$ B=https://anchor.isokron.ai
$ curl -s $B/v1/receipts/01a1c06f-bc82-7c0e-970b-242f8cfd6ece | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>process.stdout.write(JSON.parse(s).receipt_jcs))' > receipt.json
$ D=$(shasum -a 256 receipt.json | cut -c1-64); echo $D
05aa0f1a81107b9bbf42f9eb26d01c65b6a8d44eab92a16bec3747b08b6d2f552. Get each authority's CA certificate from the authority itself, not from us, and compare the fingerprints with those the authorities publish:
$ curl -s http://crt.sectigo.com/SectigoPublicTimeStampingRootR46.p7c | openssl pkcs7 -inform DER -print_certs \
| awk '/^issuer=.*USERTrust RSA Certification Authority/{f=1} f; f && /END CERTIFICATE/{exit}' > sectigo.pem
$ openssl x509 -in sectigo.pem -noout -subject -fingerprint -sha256
subject=C = GB, O = Sectigo Limited, CN = Sectigo Public Time Stamping Root R46
sha256 Fingerprint=B5:3A:C1:5C:C1:AF:B6:E2:AC:06:82:8F:55:5B:B3:BF:5B:AD:8B:2B:AC:17:33:CE:4C:B7:AA:FE:72:93:56:DE
$ for c in DigiCertTrustedG4RSA4096SHA256TimeStampingCA DigiCertTrustedG4TimeStampingRSA4096SHA2562025CA1; do
curl -s https://cacerts.digicert.com/$c.crt | openssl x509 -inform DER | tee -a digicert.pem | openssl x509 -noout -subject -fingerprint -sha256; done
subject=C = US, O = "DigiCert, Inc.", CN = DigiCert Trusted G4 RSA4096 SHA256 TimeStamping CA
sha256 Fingerprint=28:17:34:D4:59:2D:12:91:D2:71:90:70:9C:B5:10:B0:7E:22:C4:05:D5:E0:D6:11:9B:70:E7:35:89:F9:8A:CF
subject=C = US, O = "DigiCert, Inc.", CN = DigiCert Trusted G4 TimeStamping RSA4096 SHA256 2025 CA1
sha256 Fingerprint=CA:0B:15:54:EC:D9:01:EA:19:DC:AD:87:49:E9:F2:64:8C:8D:6D:FC:EA:1A:DD:9D:2C:21:09:41:5B:B8:2C:CD3. Verify each token against its authority's CA. -partial_chain makes that CA the trust anchor, so a token passes only if it chains to the authority's dedicated timestamping CA, not to any general web root:
$ curl -s -o sectigo.tsr $B/v1/receipts/01a1c06f-bc82-7c0e-970b-242f8cfd6ece/timestamps/sectigo.tsr
$ curl -s -o digicert.tsr $B/v1/receipts/01a1c06f-bc82-7c0e-970b-242f8cfd6ece/timestamps/digicert.tsr
$ openssl ts -verify -partial_chain -digest $D -in sectigo.tsr -CAfile sectigo.pem
Verification: OK
$ openssl ts -verify -partial_chain -digest $D -in digicert.tsr -CAfile digicert.pem
Verification: OK
$ openssl ts -reply -in sectigo.tsr -text | grep -E 'Time stamp|TSA:'
Time stamp: Nov 9 13:35:54 2026 GMT
TSA: DirName:/C=GB/ST=Greater London/O=Sectigo Limited/CN=Sectigo Public Time Stamping Signer R37
$ openssl ts -reply -in digicert.tsr -text | grep 'Time stamp'
Time stamp: Nov 9 13:35:54 2026 GMTThe same token fails against a different digest (message imprint mismatch) or the other authority's CA (unable to get local issuer certificate). Both authorities state, independently, that this receipt existed no later than 13:35:54 UTC on 9 November 2026. The receipt commits the checkpoint root 8928aa3f…b22b8f1, so that root existed by then too.
The browser view at anchor.isokron.ai/v/2d3098b1… runs the same checks and shows this checkpoint as VERIFIED: the receipt signature and auditor checkpoint signature valid, and both tokens verified to their pinned timestamping CAs. It also prints the commands above with this checkpoint's values. The anchor service's keys, their custody and rotation are published at anchor.isokron.ai/keys, and the pinned timestamping CAs at /.well-known/tsa-ca.json.
An RFC 3161 time is an upper bound: it shows a digest existed no later than that time, within the authority's stated accuracy. The anchor service's own clock recorded this receipt at 13:11:50Z. verify-audit accepts a receipt only when that service time is no later than the token time plus 5 minutes and no more than 24 hours before it.
Tamper-evident, not tamper-proof
Nothing in this design stops anyone from editing a file. What it does is make every edit detectable. That is the distinction Crosby and Wallach draw in their work on tamper-evident logging: preventing tampering is impractical, so the goal is to detect it. We say tamper-evident for that reason, and never tamper-proof.
The auditor's own clock is the other trap. A hash chain whose every timestamp is set to 2020 still verifies, as one write-up on backdated logs shows. Sigstore reached the same conclusion for its transparency log: Rekor's integrated time comes from its own clock and is not externally verifiable, so Rekor v2 relies on RFC 3161 timestamp authorities instead (Sigstore, trusted time; Rekor v2 GA). We made the same choice, with two unrelated authorities on every receipt. To fool the time check, Sectigo and DigiCert would both have to misdate a token.
What verification establishes, and what the method covers
Integrity, ordering and independent time come from cryptography. Thoroughness and correctness come from the method.
| Question | Answered by | You rely on |
|---|---|---|
| Are these the files the auditor signed? | integrity, chain | The pinned auditor key fingerprint, and SHA-256 and Ed25519 |
| Was each stage committed in this order? | chain, receipts | The hash chain and the anchored prefix roots |
| When did each stage exist? | independent_timestamps | Sectigo and DigiCert not both misdating a token |
| Was the harness fixed before any run? | harness_frozen_before_runs | The freeze nonce, which did not exist before the freeze was timestamped |
| Are all runs present? | run_sequence | Runs numbered 1 to N, matching the count declared at freeze time and in the report |
| Did the audit test the right things, and are the findings correct? | The report's method, controls and reproductions | The method, which you can rerun |
The last row is outside what a signature can show, so the method covers it. In an IsoKron audit, a script reproduces each finding and the same operation runs through a control, such as the vendor's own API. A separate AI refuter, in a clean context, tries to explain the finding away, and a person reruns the reproduction before anything reaches the report. The reproductions ship in the pack, so you can rerun them yourself. Our sample report shows the method applied to a public audit of the GitHub MCP server. See the method on our MCP audit page.
The same precision is standard across this field. npm provenance does not guarantee a package has no malicious code; ERC-7512 must not be read as an attestation of a contract's security; SCITT does not prevent dishonest issuers. Each states what its mechanism proves. So do we: anything recorded before the first anchor carries no timestamp, and values the auditor records, such as measured_at, are the auditor's record. That is why the harness freeze is the first anchor of every audit.
For your procurement and insurance file
Vendor reviews and cyber insurers ask who tested, how independent they were, how recent the test is, and what was fixed (Safe Security's vendor pentest checklist; Beagle Security on insurer requirements). Many teams share a short letter instead of the full report (Packetlabs).
An IsoKron audit completion letter carries the manifest SHA-256, the final anchored root and its timestamp times; the auditor key fingerprint is in the signed statement of work. Anyone you send it to, whether a customer, an insurer or an auditor, can run the same one command against the pack and confirm the values match, without trusting you, us, or the channel the files came through.
This fits how auditors already think about evidence. SOC 2 readers check a report's authenticity through the signing firm's licence and peer review (SOC2Auditors.org), and the PCAOB asks auditors to evaluate the reliability of electronic information a company provides, including its source and handling (PCAOB policy statement, 18 September 2025). A pack makes source and handling checkable. For teams shipping products with digital elements in the EU, the Cyber Resilience Act requires technical documentation, including test reports, to be kept for at least ten years or the support period, whichever is longer (summary). A pack keeps its tokens, certificate chains and pinned roots together, so each token can be re-verified offline at its own time; an archive timestamp over the final receipt, taken before the signing certificates expire, extends that.
FAQ
Do I need to trust IsoKron? Not for integrity or time. Those rest on your pinned key fingerprint, standard cryptography and two independent timestamp authorities. You rely on IsoKron for what no signature can show: that the audit was thorough, that nothing was left unrecorded, and that the findings are right. The method and reproductions are there so you can check those too.
What happens when a timestamp authority's certificate expires? openssl ts -verify checks certificates at the current time by default. After expiry, verify each token at its own time with -attime <genTime as Unix seconds>, or run verify-audit --tsa-at-gentime. Each pack's VERIFY.md prints the value per token. Keep the pack whole: each token carries its authority's certificate chain, and tsa-roots/ holds the roots. Revocation status (CRL or OCSP) is not checked, because verification is offline.
Why not a blockchain? RFC 3161 timestamps are the standard used for code signing and signed PDFs. They are immediate, free to obtain, and checkable with openssl, which is already on most machines. The authorities receive only a SHA-256 and publish nothing.
Is this a qualified timestamp under eIDAS? No. eIDAS gives qualified timestamps a legal presumption of accuracy (Art. 41). We use the public Sectigo and DigiCert services as independent clocks, and claim independence, not qualified status.
Does anything leave my machine when I verify? No. verify-audit runs locally on the directory you give it; after npx's first download it needs no network. The browser view on anchor.isokron.ai reads the public receipts and never takes uploads.
Can I verify without kronicle-lite? Yes. Each pack's VERIFY.md lists the shasum, openssl and jq commands with that pack's file names, and the root computation follows the public FORMAT specification.
Does it run on Windows? Yes. kronicle-lite's test suite, including verify-audit, runs on Linux, macOS and Windows in CI. RFC 3161 tokens are verified in-process with Node's own crypto, so the one-command check needs no openssl install on any platform.
Sources
Tools and live service
- kronicle-lite 0.10.1 on npm; evidence bundle and pack specification; audit events; FORMAT
- IsoKron anchor service: keys and custody, pinned timestamping CAs, test checkpoint verification page, test checkpoint timestamps
- Sectigo timestamping CA: SectigoPublicTimeStampingRootR46.p7c; DigiCert timestamping CAs: G4 RSA4096 SHA256 TimeStamping CA, G4 TimeStamping RSA4096 SHA256 2025 CA1
Audit publishing and authenticity
- trailofbits/publications; Zellic/publications; NCC Group Cryptography Services public reports; Cure53; OpenZeppelin, ADI Token Audit; OtterSec
- Least Authority, Lessons Learned from a Typosquatted Audit Report
- CryptoPotato, CertiK Exposes the Underbelly of Fraud Targeting its Brand (27 January 2024)
- IANS, Delve Allegations Expose Weak Points in Modern Compliance (2026)
Tamper evidence and trusted time
- Crosby and Wallach, Efficient Data Structures for Tamper-Evident Logging, USENIX Security 2009
- Sigstore, Trusted Time in Sigstore; Rekor v2 GA (10 October 2025)
- Your tamper-evident log can still be backdated, dev.to (19 June 2026)
- IETF draft-sahu-agent-action-receipts-00 (individual draft, 16 August 2026)
- RFC 9943, SCITT Architecture (June 2026); ERC-7512; npm, Generating provenance statements
- eIDAS Regulation 910/2014, Article 41 (UK-retained text)
Procurement, insurance and audit practice
- Safe Security, Vendor Penetration Test Report Review Checklist
- Beagle Security, Why cyber insurers are asking for penetration test reports
- Packetlabs, Penetration Testing Letters of Attestation
- SOC2Auditors.org, How to Check If Your SOC 2 Report Is Real
- PCAOB, policy statement on the reliability of external electronic information (18 September 2025)
- eucybersecurity.org, CRA technical documentation, OBL-ART31-01