Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/ca2a_runtime/transport/a2a_sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,8 +263,14 @@ def attach_to_sdk_message(message: Any, request: PeerRequest) -> Any:
speak HTTP.
"""
_require_protobuf()
merged = dict(metadata_from_sdk_message(message))
merged.update(a2a_adapter.attach_ca2a_metadata({}, request)["metadata"])
# Attach against the message's *current* metadata, not a throwaway ``{}``: the
# dict adapter clears an absent optional key (sealed_payload / caller_offer /
# holder_proof) with ``meta.pop(...)``, and those pops only bite when they run
# against the real metadata. Passing ``{}`` here would make them no-ops and let
# a reused Message ship a prior request's stale optional keys.
merged = a2a_adapter.attach_ca2a_metadata(
{"metadata": dict(metadata_from_sdk_message(message))}, request
)["metadata"]
message.metadata.Clear()
json_format.ParseDict(merged, message.metadata)

Expand Down
46 changes: 46 additions & 0 deletions tests/unit/test_a2a_sdk_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,52 @@ def test_attach_declares_the_extension_on_the_message() -> None:
assert list(message.extensions).count(EXTENSION_URI) == 1


def test_reused_message_clears_a_stale_sealed_payload() -> None:
"""A reused SDK message must not carry a prior request's sealed ciphertext.

``attach_to_sdk_message`` mutates in place, so an application that reuses one
``Message`` across tasks would otherwise ship task N-1's ``sealed_payload`` as
task N's: cross-request payload confusion.
"""
message = Message(message_id="m1")
a2a_sdk.attach_to_sdk_message(message, _request(sealed_payload=b"SECRET"))
a2a_sdk.attach_to_sdk_message(message, _request())
parsed = a2a_sdk.parse_sdk_message(message)
assert parsed is not None
assert parsed.sealed_payload is None


def test_reused_message_clears_a_stale_caller_offer() -> None:
"""A reused SDK message must not carry a prior request's caller offer."""
offer = ChannelOffer(
channel_public_key="k" * 43,
report=AttestationReport(
platform="software-only",
measurement="m",
public_key="k" * 43,
nonce="v1.123.abc.def",
),
)
message = Message(message_id="m1")
a2a_sdk.attach_to_sdk_message(message, _request(caller_offer=offer))
a2a_sdk.attach_to_sdk_message(message, _request())
parsed = a2a_sdk.parse_sdk_message(message)
assert parsed is not None
assert parsed.caller_offer is None


def test_reused_message_clears_a_stale_holder_proof() -> None:
"""A reused SDK message must not carry a prior request's holder proof."""
chain, leaf_key = _chain_with_keys()
node = PeerNode(LocalPolicy.of({"read"}), trusted_root_issuers={chain[0].issuer})
message = Message(message_id="m1")
a2a_sdk.attach_to_sdk_message(message, _request(node=node, chain=chain, leaf_key=leaf_key))
a2a_sdk.attach_to_sdk_message(message, _request())
parsed = a2a_sdk.parse_sdk_message(message)
assert parsed is not None
assert parsed.holder_proof is None


# --------------------------------------------------------------------------
# Not-a-cA2A-message, and failing closed
# --------------------------------------------------------------------------
Expand Down
Loading