build_evidence_pack computes meta.integrity_sha256 over the body only:
pack["meta"]["integrity_sha256"] = _integrity_hash(body)
verify_evidence_pack recomputes over the same _BODY_KEYS. Nothing in meta is covered, so every field in it can be rewritten and the pack still verifies clean.
Reproduce
pack = build_evidence_pack(date(2000, 1, 1), date(2000, 1, 2))
pack["meta"]["producer"] = "somebody-else/9.9.9"
pack["meta"]["date_to"] = "2031-12-31"
verify_evidence_pack(pack) # -> (True, "integrity verified")
agentmetry verify <pack> prints OK — integrity verified and exits 0.
Why it matters
The date range is the worst of them. A pack can be relabelled to claim a period it does not cover, and the events inside it are unchanged so nothing else contradicts the label. An auditor reading the pack has no way to tell.
meta.producer is next. The comment sitting directly above it in evidence_pack.py says:
An auditor reading this pack in 2028 needs to know which build produced it: rules, redaction, and mappings all move between versions. Provenance, not decoration.
tests/test_version.py makes the same argument: "Evidence packs record the producing version, so a version that disagrees with the tag is a provenance defect, not cosmetics." Both are right, and neither is enforced.
meta.trail_chain is also uncovered, which is the chain head state the pack asserts about the trail it was drawn from.
The awkward part
Widening the hash to include meta invalidates every pack already issued, because their stored digest was computed over the body alone. Options, roughly in order of how much I like them:
- Hash
meta minus integrity_sha256, and bump the pack schema version. verify accepts the old scope for packs below the new version and the new scope at or above it. Old packs keep verifying, new ones are properly covered, and the version says which rule applies.
- Add a second field (
meta_sha256) beside the existing one. Simpler, but leaves two digests and an obvious question about why.
- Leave it and document the limit in
SECURITY.md. Cheapest, and wrong for an artifact whose whole purpose is being handed to somebody who does not trust you.
Option 1 unless there is a reason not to.
Test
tests/test_cli_commands.py::test_verify_accepts_a_pack_whose_meta_was_edited currently asserts the broken behaviour, deliberately, with a docstring pointing here. When this is fixed that test flips to asserting failure.
build_evidence_packcomputesmeta.integrity_sha256over the body only:verify_evidence_packrecomputes over the same_BODY_KEYS. Nothing inmetais covered, so every field in it can be rewritten and the pack still verifies clean.Reproduce
agentmetry verify <pack>printsOK — integrity verifiedand exits 0.Why it matters
The date range is the worst of them. A pack can be relabelled to claim a period it does not cover, and the events inside it are unchanged so nothing else contradicts the label. An auditor reading the pack has no way to tell.
meta.produceris next. The comment sitting directly above it inevidence_pack.pysays:tests/test_version.pymakes the same argument: "Evidence packs record the producing version, so a version that disagrees with the tag is a provenance defect, not cosmetics." Both are right, and neither is enforced.meta.trail_chainis also uncovered, which is the chain head state the pack asserts about the trail it was drawn from.The awkward part
Widening the hash to include
metainvalidates every pack already issued, because their stored digest was computed over the body alone. Options, roughly in order of how much I like them:metaminusintegrity_sha256, and bump the pack schema version.verifyaccepts the old scope for packs below the new version and the new scope at or above it. Old packs keep verifying, new ones are properly covered, and the version says which rule applies.meta_sha256) beside the existing one. Simpler, but leaves two digests and an obvious question about why.SECURITY.md. Cheapest, and wrong for an artifact whose whole purpose is being handed to somebody who does not trust you.Option 1 unless there is a reason not to.
Test
tests/test_cli_commands.py::test_verify_accepts_a_pack_whose_meta_was_editedcurrently asserts the broken behaviour, deliberately, with a docstring pointing here. When this is fixed that test flips to asserting failure.