fix(transport): clear stale optional cA2A metadata on reused SDK messages - #167
Conversation
…ages
attach_to_sdk_message mutates an SDK Message in place but did not strip
stale optional cA2A keys (sealed_payload, caller_offer, holder_proof) when
the message was reused across calls: it attached against a throwaway {}, so
the dict adapter's meta.pop(...) clears were no-ops and only present keys
were overlaid onto stale metadata. A later task could then ship an earlier
task's sealed ciphertext (cross-request payload confusion).
Attach against the message's current metadata instead, so the adapter's pops
apply -- a single source of truth with the dict adapter. Adds round-trip
tests covering all three optional keys.
Closes agentrust-io#166
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Nishar <nishar@dayos.com>
carloshvp
left a comment
There was a problem hiding this comment.
Reviewed commit 1e038a9. No blocking findings.
The bridge now applies the dict adapter to the current metadata, so absent sealed_payload, caller_offer, and holder_proof fields are removed on reuse while unrelated metadata is preserved. This fixes the stale cross-request carrier data without changing verification policy.
Validation in an isolated checkout on Python 3.13 with the real a2a-sdk 1.1.2: unit and conformance suites passed (604 passed, 2 skipped); ruff check, ruff format --check, mypy, and Bandit passed. All three new regression tests fail against the parent implementation. An additional eight-combination optional-field matrix, repeated three times per combination, verified clearing, replacement, unrelated metadata preservation, routing preservation, and extension idempotence.
Approval covers this exact head. The hosted CI matrix is not present in the current check rollup; local validation does not replace remaining repository gates.
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
What
attach_to_sdk_messagenow attaches cA2A metadata against the SDK message's current metadata instead of a throwaway{}, so an absent optional key (sealed_payload,caller_offer,holder_proof) is cleared when aMessageis reused across calls. Adds round-trip tests covering all three optional keys.Why
The bridge mutates an SDK
Messagein place. It attached viaa2a_adapter.attach_ca2a_metadata({}, request), but the dict adapter clears an absent optional key withmeta.pop(KEY_..., None)— run against{}those pops are no-ops, so only present keys were overlaid onto the message's stale metadata and an earlier request's optional key survived. The dict adapter itself is correct; only the SDK bridge diverged. Attaching against the real current metadata restores parity with the dict adapter.Closes #166.
Security impact
Fixes a cross-request payload-confusion bug on the sealed-channel carrier: a reused SDK
Messagecould ship an earlier task'ssealed_payload(and likewisecaller_offer/holder_proof). With holder-proof enforcement it fails closed (HolderProofInvalid); in advisory/unenforced modes the callee would open the stale ciphertext as this task's payload. The fix ensures reused messages reflect only the current request.Test plan
pytestpasses (tests/unit/+tests/conformance/: 604 passed, 2 skipped; 3 new tests red before the fix, green after)ruff checkpassesmypypasses (ruff format --checkandbanditalso clean)Repro before the fix (against the real a2a-sdk):
DCO sign-off