fix(tr-sig): canonicalize with RFC 8785, which is the rule this suite… - #66
Merged
imran-siddique merged 1 commit intoAug 20, 2026
Merged
Conversation
… scores
TR-SIG verified signatures over
json.dumps(d, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
which specification section 3.2.2 names, in as many words, as insufficient:
"Implementations MUST use an RFC 8785-conformant library. Using
json.dumps(sort_keys=True) (Python) or equivalent ad-hoc sorting is
insufficient." The suite that scores conformance was failing the one requirement
it had no way to see itself failing.
The two forms agree byte-for-byte on any record whose strings are ASCII, and
every record this suite carried was ASCII, so all 176 tests passed while the
runner rejected valid records. Pointed at trace-spec's
examples/canonicalization-boundary/ -- four schema-valid records, correctly
signed over their RFC 8785 bytes, each declaring expected outcome "verified" --
it failed all four.
Direction matters for reading the severity: this rejects valid records, it does
not accept invalid ones, so it is an interoperability defect rather than a
security hole. What it costs is real anyway. Any record carrying a non-ASCII
character in any string value -- a data_class, a model version, a subject path,
any human-language field -- is reported by the official suite as having a failed
signature, which tells a conformant implementation that it is not one.
The fix is the one function. agentrust_trace.sign already canonicalizes with
rfc8785.dumps, so the signer and the verifier were using different rules;
rfc8785 joins the dependencies for the verifier to use the same one.
The four vectors come in as regression material, copied rather than vendored.
Nothing compares them to the originals: a guard that needs another repository
checked out is a guard that gets skipped. Each is instead held to the two
properties that make it worth having -- it verifies over its own RFC 8785 bytes,
and it does not verify under any ad-hoc form its diverges_under names -- so a
vector edited into something that is no longer a boundary fails rather than
sitting in the directory looking like coverage. A fifth test compares
_canonical_json's output to the reference directly, on a probe every ad-hoc form
gets wrong, so a reintroduced shortcut is reported as a serializer problem
rather than as a failed signature.
Verified by reverting the one-line change: the four runner assertions and the
direct byte comparison fail, and the eight vector self-validation assertions
pass, which is the shape a regression test should have.
The module docstring also claimed plain trace records could not be
cryptographically verified, while check() has verified them against an embedded
signature field for some time. Corrected in the same pass, since a reader
following it would not have looked at the path this commit fixes.
190 passed. The six ruff findings in this file are the six that were there
before; the line numbers moved.
Signed-off-by: lywinged <48041247+lywinged@users.noreply.github.com>
Contributor
|
❔ Contributor Check: UNKNOWN
Automated check by AgenTrust Contributor Check. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What is wrong
TR-SIGverifies signatures overSpecification §3.2.2 names that form, in as many words:
The suite that scores conformance was failing the one requirement it had no way to see itself failing.
Why nothing here noticed
The two forms agree byte-for-byte on any record whose strings are ASCII, and every record this suite carried was ASCII. All 176 tests passed.
Pointed at
trace-spec'sexamples/canonicalization-boundary/— four schema-valid records, correctly signed over their RFC 8785 bytes, each declaringexpected: {"outcome": "verified"}— the runner failed all four:non-ascii-valuesnon-bmp-valuesutf16-key-orderutf16-key-order-nestedThat corpus exists in
trace-specprecisely to separate a conforming serializer fromjson.dumpswith every option set carefully. The defect is here, the corpus is there, and nothing ran one against the other — the same shape as #136, one repository further out.report.py:57already draws the distinction explicitly ("matching the registry anchor's canonicalization rather than RFC 8785, because this is an identifier for a reader to compare against"). The distinction was understood; it just was not applied on the one path where it is cryptographic.Severity, in the honest direction
This rejects valid records; it does not accept invalid ones. Fail-closed, so an interoperability defect rather than a security hole.
What it costs is real anyway: any record with a non-ASCII character in any string value — a
data_class, a model version, a subject path, any human-language field — is reported by the official suite as having a failed signature. The suite tells a conformant implementation that it is not one.The fix
One function.
agentrust_trace.sign._canonical_bytesis alreadyrfc8785.dumps, so the signer and the verifier were applying different rules;rfc8785>=0.1.2joins the dependencies so the verifier uses the same one.The regression material
The four vectors come in copied, not vendored, and nothing compares them to the originals — a guard that needs another repository checked out is a guard that gets skipped. Each is held instead to the two properties that make it worth having:
diverges_undernamesA vector edited into something that is no longer a boundary fails, rather than sitting in the directory looking like coverage.
A fifth test compares
_canonical_json's output against the reference directly, on a probe every ad-hoc form gets wrong, so a reintroduced shortcut is reported as a serializer problem rather than as a failed signature — one step closer to the cause than a red TR-SIG-001.Verified by reverting
A regression test that passes before and after the fix is not one. With the one-line change reverted:
The nine that still pass are the vector self-validation assertions, which correctly do not depend on the fix.
Also in this commit
The module docstring claimed plain trace records could not be cryptographically verified, while
check()has verified them against an embeddedsignaturefield for some time. Corrected in the same pass — a reader following the old text would not have looked at the path this fixes.Checks
The 5 xpassed are pre-existing: identical count with and without this change. The six
rufffindings intr_sig.pyare the six that were there before, with line numbers moved; the new test file is clean.How this was found
Building a delegation-link conformance corpus in
trace-specand running it through every verifier in the org that would accept it. This suite was the fourth of those, and it agreed with the corpus on all 65 records — because all 65 were ASCII. Turning the corpus around and pointing this suite attrace-spec's canonicalization vectors is what surfaced it.Generated by Claude Code