Repository navigation
Releases: agentrust-io/trace-registry
Release list
trace-verify 0.4.3
- Duplicate JSON members are refused. The
trace-verifyCLI parsed the
claim, proof, receipt and entry files with last-winsjson.loads, so a claim
carrying a member twice verified over one value while a first-wins reader of
the same file saw the other. It now usesloads_uniqueat all four parse
sites and exits 2 withduplicate JSON member name(#104).
trace-verify 0.4.2
pip install trace-verify==0.4.2
Behaviour changes
- Key rotation is now refused.
trace-verify chainfails a link whosekey_iddiffers from the previous checkpoint's. Each checkpoint names its own key, so a chain that changed signers midway used to verify clean. The chain format has no rotation statement, so a checkpoint signed by a new key is a break, not a continuation: there is no supported way to rotate the checkpoint key and keep a chain verifying. The anchor pipeline also refuses to extend a published chain when its configured signing key is not the one that signed the last checkpoint. trace-verify chainwith no checkpoints exits 1 (#88). It exited 0 and, with--json, reportedverified: truefor input containing no checkpoint. It now printsNOT VERIFIED, and--jsonreportsverified: false,checkpoints: 0,reason: "no_checkpoints".- Malformed input fails instead of raising (#94).
CheckpointRecord.from_dictraisesValueErrorfor a missing or mistyped member (it raisedKeyErrororTypeError) and refuses a boolean, float or numeric-string integer.verify_checkpoint_linkreturns a failure for a non-string root.InclusionProof.from_dictandConsistencyProof.from_dictraiseInvalidArgumentError. A non-stringcanonicalization_idis anUnknownCanonicalizationError. The CLI exits 2 for a file that is not UTF-8 or is nested too deeply, andchainreports a malformed checkpoint as a failure; both printed tracebacks. Found by the new ClusterFuzzLite targets in.clusterfuzzlite/.
Fixes
trace-verify --jsonprints one JSON document when the signature does not verify. It printed two (#93).trace-verify --entry-urlfollows a redirect only to an allowlisted https host. The allowlist was checked on the first URL only, and urllib followed any redirect after it (#93).
Registry side (not in the package)
- The pipeline, the aggregator and
tools/anchor.pyrefuse a claim outside the registry-anchor-v1 section 1 profile: a non-integer number, an integer outside -(2^53-1)..2^53-1, or a duplicate member name. Every claim already anchored is inside the profile (#93). - A staged record whose
produceris an array or object is rejected by name. It used to abort the scheduled run for every producer. CI now dry-runs staged records (#93). tools/check_append_only.pydiffs with--no-renamesand fails when the base SHA does not resolve (#93).- The aggregator caps request bodies at 4 MiB (413), answers a missing, negative or malformed
Content-Lengthwith 400, and answers 400 to a body nested too deeply to parse (#93, #94). tools/capture_witness_receipt.py --registry-entrylifts themmr_checkpointout of a registry entry file, with--batch-idfor a multi-line file (#78).- CI: ruff (#96), per-job write permissions (#95), pipeline runtime dependencies (#89), maintainer approval check (#92).
Full list in CHANGELOG.md.
trace-verify 0.4.1
Verifying a claim no longer needs a clone of the registry.
pip install trace-verify
trace-verify \
--claim your-record.json \
--proof your-record.proof.json \
--entry-url https://raw.githubusercontent.com/agentrust-io/trace-registry/main/registry/2026/06/12.ndjsonIn 0.4.0, --entry-url fetched the registry entry and stopped there. The producer key still had to be on disk, so a reader without a clone got no registered key for producer '...' and exit 1. The only workaround was --no-verify-signature, which checks inclusion and not authorship: the weaker half of the answer, reached by switching off the stronger half.
The key now comes from the same allowlisted host as the entry. The producers base is derived from the entry URL, and three things keep that from being a hole:
- the derived URL is re-checked against the host allowlist, so an entry URL on another host cannot smuggle a fetch;
- the fetched file lands under the exact name the existing loader expects, so the producer-id validation that keeps a crafted id inside the directory still runs;
- derivation happens only when neither producers option is given and only for
--entry-url, so a local--entrymakes no network call at all.
--producers-url overrides the derivation. --producers-dir still takes a local directory. The two are mutually exclusive.
The output says when a key came over the wire, as a second line in text and as producer_key_source in --json. A key fetched from a host is a different trust statement from one already on disk, and an unqualified signature valid would blur them.
0.4.0 shipped a README claiming --entry-url was enough on its own. It was not. This is that claim made true rather than withdrawn.
Full notes: CHANGELOG.md
trace-verify 0.4.0
One install now answers all three questions a reader can ask of this registry.
pip install trace-verify
trace-verify --claim C.json --proof P.json --entry E.ndjson # is my claim anchored?
trace-verify chain E.ndjson [MORE...] # does the registry's history hold?
pip install "trace-verify[witness]"
trace-verify receipt --checkpoint CP.json --response POST.json \
--registry-key HEX --witness-key HEX --expected-log-id ID # does an outside witness agree?Chain verification was a Python API with no command, and witness-receipt verification was not in the package at all. Both were reachable only by cloning the repository.
verify_chain_against_entries is new in the package and is the substantive part. It rebuilds the Merkle Mountain Range from the raw entries and compares it to what each checkpoint claims, which is what catches a quiet edit to an already-anchored entry: such an edit leaves every checkpoint record's own math self-consistent, so the chain-consistency check still passes and only the recompute fails. That check previously existed in the repository and nowhere a pip user could reach it.
Compatibility. The inclusion check keeps its bare-flag form, so every invocation written against 0.3.x runs unchanged. Only the literal tokens chain and receipt route. cbor2 and scitt-cose are imported inside the witness verifier rather than at module scope, so import trace_verify still works without a CBOR stack; they arrive through the new witness extra.
--version was wrong before this. 0.3.0 and 0.3.1 both answered --version with 0.1.0. The publish workflow now refuses a release whose tag disagrees with the packaged version, or whose __version__ disagrees with pyproject.toml.
Package metadata. Homepage, Bug Tracker and Changelog point at this repository again, now that it is public. License metadata is Apache-2.0.
Full notes: CHANGELOG.md
trace-verify 0.3.1
Metadata-only release. No behaviour change to the verifier.
What this fixes
trace-verify 0.3.0 on PyPI serves [project.urls] pointing at agentrust-io/trace-registry, which is private. Anyone who installed the package and followed its Documentation or Homepage link got a 404.
The URLs were corrected on main in #38, after v0.3.0 was tagged and without a version bump, so PyPI has been serving the stale metadata ever since. There was nothing left to fix in the URLs themselves. The version bump is the fix.
Closes the remaining half of agentrust-io/trace-spec#138, via #43.
After this release
| Link | Before | After |
|---|---|---|
| Homepage | agentrust-io/trace-registry (private, 404) |
public trace-spec |
| Documentation | trace-registry/.../docs/anchor-format.md (404) |
anchor format in trace-spec |
| Changelog | trace-registry/.../CHANGELOG.md (404) |
public |
| Bug Tracker | trace-spec/issues |
unchanged |
Worth stating plainly, since this package exists to verify that evidence resolves: a published artifact pointing at a repository nobody can reach is the same failure the anchor format rules are written to prevent. It went unnoticed for eleven days.
trace-verify 0.3.0
The published 0.2.0 verifies non-ASCII records against bytes the producer never signed
canonical_body_bytes built the signature pre-image with json.dumps(sort_keys=True, ensure_ascii=True), while agentrust_trace.sign_record signs through rfc8785.dumps. The two agree on ASCII and diverge everywhere else, so signer and verifier computed different bytes for any record carrying an accented character, a non-Latin script, or an emoji.
old: b'{"note":"café","producer":"acme/1.0.0"}'
new: b'{"note":"caf\xc3\xa9","producer":"acme/1.0.0"}'
The failure mode is rejection, not acceptance
A verifier that reconstructs different bytes fails the signature check. 0.2.0 therefore rejected valid records; it did not accept invalid ones. Nothing was forgeable through this and no advisory is warranted.
It was still wrong in the one function whose entire job is reproducing what a producer signed.
Why 0.3.0 and not 0.2.1
The behaviour changed, not only the bytes. Signature verification now refuses rather than guessing when rfc8785 is unavailable:
pip install "trace-verify[signature]"Falling back to a canonicalization that only agrees on ASCII is precisely how this defect shipped, so the fallback is gone and the error names what to install.
Scope is one function, deliberately
Four other json.dumps(sort_keys=True, ...) sites in this repository are correct. The anchor leaf is defined as sorted-key JSON by docs/anchor-format.md §1 and must not move. Two canonicalizations at two layers is the trap that document's §0 exists to name, and fixing the wrong one would have created the exact bug it warns about.
Reported externally as agentrust-io/trace-spec#138.
v0.2.0
Security hardening release. trace-verify now verifies claim signatures by default (fail-closed); the registry verifies producer signatures before anchoring; outbound fetches are restricted to an https host allowlist (SSRF mitigation); producer-id path traversal is blocked. BREAKING for scripts that relied on inclusion-only success of trace-verify.