fix(intent-bridge): compare the required transcript call over canonical bytes - #333
Merged
imran-siddique merged 1 commit intoSep 12, 2026
Merged
Conversation
…al bytes `verify_bridge()` checked `transcript.before.tool_call` against the executed call with host-language equality while every other comparison in the function is over RFC 8785 canonical bytes. Python holds `True == 1` and `False == 0`, nested objects included, so a transcript substituting a boolean for the corresponding integer had different JCS bytes from the call the authorization digested and was still reported as bound to the execution. The signed `tool_call_digest` was never affected; it is checked against the actual `tool_call`. What could differ was the separately supplied transcript, in the one place whose purpose is to show that the two agree. The comparison now reuses the digest already computed for the `tool_call_digest` check. The isinstance guard stays in front, and a `transcript.before.tool_call` that JCS has no form for raises `AuthorizationMismatch` rather than `IntentBridgeError`, so the documented result class for a malformed transcript is unchanged. Regression coverage pins all four substitutions and asserts each one is Python-equal, so a test that stopped exercising the defect would fail rather than pass quietly. An unchanged-call control and a parametrized non-object case cover the other direction. Reported by @altrudev in #317. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X27MBo6tGVw1UmmchuGFnW
imran-siddique
deleted the
fix/intent-bridge-transcript-canonical-equality
branch
September 12, 2026 04:25
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.
Closes #317.
verify_bridge()checked the required transcript's call against the executed call withbefore.get("tool_call") != tool_call(intent_bridge.py:232), which is host-language equality. Every other comparison in that function is over RFC 8785 canonical bytes. Python holdsTrue == 1andFalse == 0, nested objects included, so a transcript whosebefore.tool_callsubstituted a boolean for the corresponding integer had different JCS bytes from the call the authorization digested and was still reported as bound to the execution.Reproduced at
10fcba4in all four directions, with controls:The two refusals are what make the hole narrow rather than merely real: only the substitutions Python cannot distinguish got through.
Scope
The signed
tool_call_digestwas never affected. It is checked against the actualtool_call, so the executed call could not differ from the authorized one. What could differ was the separately supplied transcript, in the one place whose purpose is to show that the two agree.Line 232 was also the only JSON-object equality comparison in
src/agentrust_trace, so nothing else in the package is in this class.Change
compare_digestoverdigest_jcs, reusing the digest already computed for thetool_call_digestcheck rather than hashing the executed call twice.transcript.before.tool_callthat JCS has no form for raisesAuthorizationMismatchrather thanIntentBridgeError. A malformed transcript keeps its documented result class, whichtests/test_public_functions_raise_what_they_document.pycares about.Tests
Four parametrized substitutions, each asserting the substituted call is Python-equal to the executed one before asserting the refusal, so a test that stopped exercising the defect fails rather than passing quietly. Plus an unchanged-call control and a parametrized non-object case pinning the exception class.
Verified the corpus fails in both directions: the four substitution cases fail on
origin/mainand pass here;tests/test_intent_bridge.pyis 43 passed.The four repository-wide failures in
test_generators_reproduce_fixtures.pyandtest_safe_integer_range.pyare pre-existing Windows path-separator artifacts (schema\...againstschema/...), confirmed on a cleanorigin/mainworktree and unrelated to this change.Reported by @altrudev, who scoped it precisely and anticipated the exception-class trap in the fix.
🤖 Generated with Claude Code
https://claude.ai/code/session_01X27MBo6tGVw1UmmchuGFnW