Skip to content

fix(trace,catalog): canonicalize TRACE claims and catalog hashes with RFC 8785 JCS (#837) - #838

Open
drk-ram wants to merge 2 commits into
agentrust-io:mainfrom
drk-ram:fix-jcs-trace-hashes
Open

drk-ram wants to merge 2 commits into
agentrust-io:mainfrom
drk-ram:fix-jcs-trace-hashes

Conversation

@drk-ram

@drk-ram drk-ram commented Nov 6, 2026 •

Copy link
Copy Markdown

What

Canonicalize TRACE claim signing inputs, claim digests, and tool catalog/definition SHA-256 hashes with RFC 8785 (JCS) instead of Python JSON formatting. The review follow-up f19cca5 specifies Unicode code-point catalog-entry ordering, corrects all four authoring/deployment hash snippets, and explicitly reads catalogs as UTF-8 so non-ASCII examples also work on Windows.

Why

Python JSON escaping and number formatting can disagree with RFC 8785. The old tutorial generated definition hashes and catalog pins that the updated runtime rejected for descriptions such as Résumé records and schema bounds such as minimum: 1e-7.

Closes #837.

Adopts the ordering documentation/vectors proposed in dinakarjs/cmcp@03e1d7f and the four tutorial corrections from #839. Entry sorting remains the current Python Unicode code-point order, explicitly distinguished from JCS's UTF-16 object-member ordering; no existing-pin migration or bypass is authorized.

Security impact

Restores RFC 8785 canonicalization across cmcp_runtime.audit.trace_claim, cmcp_runtime.catalog.loader, cmcp_runtime.session.manager, and cmcp_verify, preventing cross-verifier signature and digest mismatches. Catalog files are decoded as UTF-8 independently of the host locale, and invalid UTF-8 is reported as ConfigError.

Test plan

  • pytest passes — pytest tests/unit/test_catalog_jcs_hashes.py tests/unit/test_trace_claim.py tests/unit/test_session_manager.py --basetemp .council-validation\pytest -q: 85 passed.
  • ruff check passes in the documented scope — ruff check src tests: passed with repository-locked Ruff 0.17.0. Unscoped ruff check reports 370 pre-existing findings, all in unchanged files outside src/tests; this is not a clean repository-wide lint result.
  • mypy passes — mypy src: no issues in 77 source files with repository-locked mypy 2.4.0. The initially installed mypy 1.18.2 reported an error in unchanged mcp/proxy.py; restoring the locked tool in an isolated project environment resolved that tooling mismatch.
  • Manual test performed (describe steps below if applicable) — no separate manual/deployment acceptance test was performed.

Additional automated validation:

  • pytest tests/unit/test_catalog.py tests/unit/test_catalog_canonical_json.py --basetemp .council-validation\catalog-extended -q: 48 passed.
  • test_tutorial_hash_snippets_match_loader executes all four published hash snippets with Résumé records and minimum: 1e-7, and checks the resulting definition hash and catalog pin against load_catalog(..., expected_hash=...).
  • Both new code-point-order regressions reject an in-memory UTF-16 comparator mutation; source was not modified by the mutation controls.
  • bandit -r src -c pyproject.toml -q: passed (existing nosec-comment warnings).
  • git diff --check: passed.

Validation was local on Windows/Python 3.11.9. Hosted validation and renewed independent security-path reviews remain separate requirements.

DCO sign-off

Signed-off-by: Raghuvar Karthik Durga <177662415+drk-ram@users.noreply.github.com>
@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

🔴 Contributor Check: HIGH

Check Result
Profile HIGH
Credential LOW
Overall HIGH

Automated check by AgenTrust Contributor Check.

@github-actions github-actions Bot added the needs-review:HIGH Contributor check flagged HIGH risk label Nov 6, 2026

@noah-ing noah-ing left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed current head f2e3c2ddf74202ce0136b742f50dcefb2b520d66. I am requesting changes for the catalog ordering issue inline and the authoring workflow below.

Update the copyable catalog hashing instructions. docs/tutorials/tool-catalog-authoring.md:109,165,182,276 still calculates definition and catalog hashes with json.dumps(..., ensure_ascii=True). The runtime now uses RFC 8785. With description = "Résumé records" in the minimal catalog, the documented definition calculation gives sha256:6571a651…, while load_catalog expects sha256:2c88dbe4… and rejects the catalog. After correcting the definition hash, using the tutorial's CMCP_CATALOG_HASH still raises CatalogHashMismatch (sha256:2f92b0b9… versus sha256:8074ec8c…). Please update all four snippets and the adjacent prose to the new canonical bytes, and exercise a non-ASCII example so the published deployment command remains usable.

Local validation on this head: the full unit suite passed (2,597 passed, 21 skipped); Ruff, mypy (77 source files), and Bandit passed. The hosted code-test, CodeQL, documentation, confinement, and fuzz jobs are currently action_required with no executed jobs; the green contributor/vouch checks are not code validation. The required maintainer approvals remain outstanding.

sorted_entries = sorted(raw_entries, key=lambda e: e["tool_name"])
canonical = json.dumps(sorted_entries, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
return f"sha256:{_sha256_hex(canonical.encode())}"
return f"sha256:{_sha256_hex(rfc8785.dumps(sorted_entries))}"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Catalog hash interoperability: JCS preserves array element order; it does not define the preceding sort here. Python sorts tool_name by Unicode code point, while UTF-16 string ordering places supplementary-plane characters before high BMP characters. I passed two schema-valid entries named tool_\uff5a and tool_\U00010000 through load_catalog: this code produced sha256:fd502d32645bfb6a3173e049c2644f4b044365c23ca0e16082b3b37888cece50; ordering those same entries by UTF-16 code units and applying the same RFC 8785 serializer produced sha256:d73d69f2f715692a18e007a2ff5d641daee6c43ce7ad6e1dc856ec2ee4df7294. Both names are accepted by the schema and loader. docs/spec/attestation.md says only “lexicographic ascending,” and the new test has one entry, so cross-language implementations can disagree on a valid catalog. Please specify the exact tool_name comparison order, implement it consistently, and add a two-entry interoperability vector.

@carloshvp carloshvp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed commit f2e3c2ddf74202ce0136b742f50dcefb2b520d66.

I confirmed the catalog-authoring blocker already identified in the review and prepared a correction for all four executable hash snippets in docs/tutorials/tool-catalog-authoring.md.

Each snippet needs rfc8785.dumps(...) and must hash the returned bytes directly:

import hashlib
import rfc8785

canonical = rfc8785.dumps(approved_definition)
definition_hash = "sha256:" + hashlib.sha256(canonical).hexdigest()
sorted_entries = sorted(entries, key=lambda e: e["tool_name"])
catalog_hash = "sha256:" + hashlib.sha256(rfc8785.dumps(sorted_entries)).hexdigest()

Update the definition helper, both catalog helpers, the deployment one-liner, and the adjacent canonicalization prose. I exercised all four corrected snippets with Résumé records and a schema containing minimum: 1e-7: their output agrees with load_catalog, including expected_hash validation. With the original snippets, definition validation and then the catalog pin both fail. The separately raised cross-language tool-name ordering decision remains outstanding; this snippet correction retains the current runtime order.

On the unmodified runtime head, 153 focused claim/catalog/session/kill-switch/verifier tests pass; Ruff, mypy (77 files), and configured Bandit pass in the isolated hash-locked environment. These tests do not resolve the tutorial and ordering blockers or replace the required hosted validation and security-path approvals.

Copy link
Copy Markdown
Contributor

Prepared an adoption proposal for the catalog-entry ordering decision, based on head f2e3c2ddf74202ce0136b742f50dcefb2b520d66: commit 03e1d7f.

Only docs/spec/attestation.md and tests/unit/test_catalog_jcs_hashes.py change. The proposal specifies Unicode code-point ordering for tool_name, preserving current Python runtime behavior, and distinguishes that array-entry order from RFC 8785's UTF-16 ordering of object member names. An explicit JavaScript code-point comparator agreed with Python in a separate two-entry probe; default UTF-16 ordering reversed the entries and changed the digest.

The tests add a fixed two-entry hashing vector and a schema-valid loader/pin control using tool_\uff5a and tool_\U00010000. Both input permutations accept the same code-point-ordered pin, and reject the UTF-16-ordered pin. Temporarily switching the runtime comparator to UTF-16 caused both added regressions to fail; the source was restored afterward.

Local hash-locked Python 3.12 validation: 38 focused catalog tests passed; full unit suite 2,612 passed, eight skipped with proxy environment variables removed. The initial full run had nine SOCKS-proxy dependency errors, not a clean pass. Ruff, mypy over 64 runtime source files, configured Bandit, and whitespace checks passed. Bandit emitted nosec-comment warnings. No runtime source or dependency change is included.

Your branch is unchanged. The patch is available for cherry-picking or adaptation, subject to maintainer acceptance of the ordering rule. This does not resolve the review by itself or establish hosted validation/independent approval. The four tutorial snippet corrections already prepared in #839 remain separate and still need coordinated adoption. No existing catalog-pin transition is authorized by this proposal.

Prepared with OpenAI Codex; commit DCO signed.

Document Unicode code-point entry order separately from JCS object-key order. Use RFC 8785 bytes in every tutorial hash snippet and verify them against a UTF-8 catalog containing non-ASCII text and a fractional schema bound. Decode catalog files explicitly as UTF-8 so the published workflow also works on Windows.

Signed-off-by: Raghuvar Karthik Durga <177662415+drk-ram@users.noreply.github.com>
@drk-ram

drk-ram commented Nov 10, 2026

Copy link
Copy Markdown
Author

@noah-ing @carloshvp Addressed in f19cca5. Adopted @dinakarjs's code-point ordering specification and two-entry vectors, plus all four RFC 8785 tutorial snippets from #839. A regression executes every snippet with Résumé records and minimum: 1e-7, validating both definition hashes and load_catalog(..., expected_hash=...). Catalog reads are explicitly UTF-8 so this also works on Windows. The 85 requested focused tests and 48 additional catalog tests pass; both ordering regressions detect a UTF-16 comparator mutation. Repository-locked mypy (77 files), documented ruff check src tests, and configured Bandit pass. Bare ruff check still reports 370 pre-existing findings in unchanged files outside src/tests; the description records that limitation. Ready for re-review, without claiming hosted validation or maintainer acceptance of the ordering clarification.

@dinakarjs dinakarjs left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed the adopted catalog follow-up at f19cca5. The runtime, specification, two-entry vectors and tutorial consistently sort catalog entries by Unicode code point before JCS serializes the array. The tutorial examples exercise UTF-8 input and agree with the pinned loader hashes. Locally, 340 catalog, TRACE, session and verifier tests passed, and ruff check src tests passed. This closes my adoption follow-up. Hosted workflows report action_required, and Noah and Carlos retain disposition of their reviews. Prepared with OpenAI Codex.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-review:HIGH Contributor check flagged HIGH risk

Projects

None yet

4 participants