Skip to main content
A Verification Bundle is a self-contained archive of everything an external verifier needs to validate a Decision Receipt offline:
  • the signed receipt itself,
  • the policy snapshot the receipt was evaluated against (so rules can be re-run),
  • chain proofs and seal (when the receipt is part of a chain),
  • the Rekor transparency proof (when anchored),
  • the public keys used to sign the receipt and chain proofs (informational only),
  • a top-level manifest that hashes everything together.
Trust roots come from out-of-band channels — sigstore.dev’s well-known Rekor key for transparency proofs, and for receipt signatures a key set you pin yourself, through whatever channel your policy accepts. MeshQu’s own GET /v1/.well-known/signing-keys is deprecated and self-asserted and must not be used as a trust root. The bundle’s own trusted_keys.json is informational and never used to authenticate signatures.

At-a-glance

Bundle layout

The 10 sub-claims

verifyBundle() returns one structured result per sub-claim. Every relevant sub-claim is reported — the verifier never short-circuits past the first failure.

1. bundle_manifest

File presence + per-file SHA-256 + manifest digest match. Failure codes: bundle.unknown_version, bundle.unknown_profile, bundle.file_missing, bundle.file_digest_mismatch, bundle.manifest_digest_mismatch.

2. integrity

Recomputed integrity hash matches the receipt’s stored value. Failure code: integrity.mismatch.

3. signature

Ed25519 signature verified against out-of-band trust roots. What the signature covers depends on the receipt version: a v1 receipt signs the bare integrity_hash string, while a v2 receipt signs a canonical envelope of evidence_manifest_digest, integrity_hash, policy_snapshot_digest, receipt_schema_version, signature_algorithm, signature_kid and timestamp. Recorded receipts are v2. See Receipt Reference §2.3 for the exact envelope. The verifier rebuilds the v2 envelope from the receipt’s stored signature_kid and signature_algorithm rather than hardcoded values, so tampering with either field fails this sub-claim. Failure codes: signature.unknown_kid, signature.invalid, signature.not_signed, signature.no_external_trust_root.

4. snapshot_replay

Bundled snapshot.id matches receipt.policy_snapshot_id; re-running the bundled rules against the bundled context reproduces the receipt’s integrity_hash. Failure codes: snapshot_replay.snapshot_id_mismatch, snapshot_replay.hash_mismatch, snapshot_replay.evaluator_unavailable, snapshot_replay.skipped_in_browser (advisory).
On a v1 receipt, snapshot_replay.status === 'valid' binds snapshot.id and snapshot.rules only — snapshot.policy_versions and snapshot.created_at are informational. On a v2 receipt the sub-claim additionally checks policy_snapshot_digest, which covers id, rules, policy_versions and created_at together.

5. transparency

If the bundle ships transparency_proof.json: DSSE envelope’s subject digest equals receipt’s integrity hash; sha256(dsse_envelope) equals entry_body.spec.envelopeHash.value; Rekor’s SET signature verifies against a trusted Rekor log key. Failure codes: transparency.body_subject_mismatch, transparency.envelope_body_hash_mismatch, transparency.set_invalid, transparency.unknown_log_id, transparency.envelope_decode_failed, transparency.inclusion_proof_invalid, transparency.inclusion_proof_unverified (advisory). Chain proof identifies this receipt (decision_id + decision_integrity_hash); chain integrity hash recomputes; chain signature verifies against external trust roots. Failure codes: chain_link.hash_mismatch, chain_link.signature_invalid, chain_link.receipt_binding_mismatch.

7. chain_seal

Seal covers this receipt’s integrity hash; seal hash recomputes; seal signature verifies. Failure codes: chain_seal.hash_mismatch, chain_seal.signature_invalid, chain_seal.receipt_binding_mismatch.

8. canonicalization

Manifest’s canonicalization profile is one the verifier accepts (meshqu-canonical/v0 for v1 bundles). Failure codes: canonicalization.profile_mismatch, canonicalization.vector_failure.

9. evidence

For a v2 receipt carrying an evidence_manifest_digest: the bundled evidence_manifest.json re-digests to the value stored on the receipt, and any custodian signatures on its items verify against the caller-supplied custodianTrustedRoots. Reports not_applicable for v1 receipts and for v2 receipts with a null digest. Failure codes: evidence.digest_mismatch, evidence.bundled_manifest_invalid, evidence.custodian_unknown_kid, evidence.custodian_signature_invalid, evidence.custodian_signature_malformed.

10. approval_lineage

Activates when the receipt is v2 and the bundled snapshot has at least one non-null policy_versions[i].approval_receipt_digest. For each such version the verifier checks that the approval receipt is present in policy_approval_receipts.json, that its recomputed digest matches the snapshot entry, that policy_rules_hash matches by string equality, and that the ratifier’s Ed25519 signature verifies against the caller-supplied ratifierTrustedRoots. Reports not_applicable otherwise. Failure codes: approval_lineage.digest_mismatch, approval_lineage.signature_invalid, approval_lineage.unknown_ratifier, approval_lineage.policy_rules_hash_mismatch.

How verified is determined

The browser UI’s headline verdict collapses 10 sub-claim results into one of three states:
  • Valid — every sub-claim passes cleanly.
  • Verified with caveats — only advisory codes are present (e.g. snapshot_replay.skipped_in_browser, transparency.inclusion_proof_unverified). Cryptographic checks all passed; the verifier honestly flags what it can’t fully attest in this context.
  • Failed — at least one hard failure code on any sub-claim.
The exact rule: downgrade only when ALL failure codes on a claim are advisory. Real failures always win.

Trust roots — required input

Empty trustedRootssignature.no_external_trust_root (the verifier refuses to authenticate via the bundle’s own keys). Empty rekorTrustedRoots is allowed but downgrades transparency to inclusion_proof_unverified.

Cross-runtime parity

Two implementations:
  • Node in @meshqu/core
  • Browser in meshqu-verify
The verify-app deliberately doesn’t import meshqu-core — same pattern as canonical-json and rekor primitives. Drift between the two would silently weaken the offline guarantee, so a parity test asserts byte-identical sub-claim verdicts on every released archive.

See also