Skip to content
IsoKron

Research / Guide in Testing and benchmarks

Tamper-evident audit evidence: how to verify an MCP audit report yourself

By IsoKron Research. Published . 18 minute read. AI-assisted.

Software
kronicle-lite 0.10.1, OpenSSL 3.0.13 and 4.1.0

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:

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:

  1. Is this the evidence the auditor delivered, unaltered? Every log, script, result file and the report itself, byte for byte.
  2. Was it recorded in the order claimed? The harness first, then the runs, then the findings and the report.
  3. 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.

FileWhat 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.jsonOne anchor receipt per milestone (harness frozen, runs completed, report issued), countersigned by IsoKron's anchor service
anchors/NN-<milestone>.<tsa>.tsrRFC 3161 timestamp tokens over the SHA-256 of each receipt, one from Sectigo and one from DigiCert
auditor-key.pemThe auditor public key for this audit. Trusted only when its fingerprint matches the one in your signed statement of work
anchor-keys.jsonThe anchor service's published key list
tsa-roots/The pinned timestamping CA certificates, for the openssl route
claims.jsonThe claims this pack makes
COMPLETION.txtThe 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.mdThe one-command check, and the raw shasum / openssl / jq commands with this pack's own file names
SHA256SUMS, SHA256SUMS.sigA 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:

shell
openssl pkey -pubin -in auditor-key.pem -outform DER | shasum -a 256

Step 2: Run one command

shell
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:

text
$ 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_sequence

Read 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):

text
$ 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 GMT

VERIFY.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.

ClaimWhat a PASS establishes
integrityEvery 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
chainThe audit events form one unbroken hash chain, and the signed Merkle root matches it
receiptsEach 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_timestampsEvery 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_runsThe 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_sequenceRuns 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 reads NOT 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:
text
$ 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. integrity and chain cannot be skipped:
text
$ 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":

text
before: ms":{"p50":64,"p95":70},"result":"fail"}
after:  ms":{"p50":64,"p95":70},"result":"Fail"}
text
$ 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:

text
$ 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:

text
$ 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 receipts recomputes 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_runs fails too.
  • The values no longer match. The manifest SHA-256 is now 45237877…, not the 45905afc… 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:

text
$ 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
05aa0f1a81107b9bbf42f9eb26d01c65b6a8d44eab92a16bec3747b08b6d2f55

2. Get each authority's CA certificate from the authority itself, not from us, and compare the fingerprints with those the authorities publish:

text
$ 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:CD

3. 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:

text
$ 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 GMT

The 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.

QuestionAnswered byYou rely on
Are these the files the auditor signed?integrity, chainThe pinned auditor key fingerprint, and SHA-256 and Ed25519
Was each stage committed in this order?chain, receiptsThe hash chain and the anchored prefix roots
When did each stage exist?independent_timestampsSectigo and DigiCert not both misdating a token
Was the harness fixed before any run?harness_frozen_before_runsThe freeze nonce, which did not exist before the freeze was timestamped
Are all runs present?run_sequenceRuns 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 reproductionsThe 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

Audit publishing and authenticity

Tamper evidence and trusted time

Procurement, insurance and audit practice

Get new research by email

New MCP audits and research, about twice a month, plus occasional IsoKron service news. Unsubscribe anytime.

Sent with Kit. See our privacy page.