Is your feature request related to a problem? Please describe.
The SDK ships Agent Card signing support in a2a.utils.signing
(create_agent_card_signer / create_signature_verifier), and both
A2ACardResolver.get_agent_card and ClientFactory.create_from_url accept a
signature_verifier. But samples/ only contains hello_world_agent.py and
cli.py, so there is no end-to-end example showing how the two halves fit
together.
That leaves a few things easy to get wrong, and all of them fail in ways that
are hard to diagnose:
- The verifier is a synchronous callable. The natural implementation of a
key_provider — fetch the JWKS from the signature's jku with a sync HTTP
client — does blocking I/O inside async code. Against an in-process server it
deadlocks, and the symptom is a bare InvalidSignaturesError: No valid signature found, which points at the crypto rather than at the I/O.
create_agent_card_signer mutates the card it signs (it appends to
card.signatures). Wiring it directly into
create_agent_card_routes(card_modifier=...) means the served card
accumulates a new signature on every request.
key_provider failures need to be PyJWTError subclasses. The verifier
only catches PyJWTError while iterating signatures, so a provider raising
ValueError aborts the whole card instead of moving on to the next
signature.
- Trusting the card's own
jku defeats the purpose. The jku header lives
in the untrusted card, so a verifier that fetches keys from whatever URL the
card names will happily verify a forged card against the attacker's own key.
The key source has to be pinned out of band, and nothing in the API signature
makes that obvious.
Describe the solution you'd like
A runnable sample under samples/ covering both halves of card signing:
- Server: sign the Agent Card with an ES256 key via
create_agent_card_signer, serve it through
create_agent_card_routes(card_modifier=...), and publish the matching
public key as a JWKS document at /.well-known/jwks.json — the URL the
signature's jku points to.
- Client: verify the fetched card with
create_signature_verifier passed
to A2ACardResolver.get_agent_card(signature_verifier=...), resolving keys
by kid from an allowlist of pinned JWKS URLs, with the accepted algorithms
pinned too.
- Negative cases, so the security property is visible rather than asserted:
a card whose transport URL was rewritten in transit, a card with its
signature stripped, and a genuine card whose jku the client does not trust.
Plus a smoke test in the style of tests/integration/test_samples_smoke.py and
a samples/README.md section.
Describe alternatives you've considered
Documenting the flow in prose only (e.g. in the SDK docs). The unit tests in
tests/utils/test_signing.py already cover the crypto, but they use symmetric
keys and an inline key_provider, so they do not surface the async, mutation
or key-pinning pitfalls above — those only show up once a real server and
client are wired together.
Additional context
No response
Code of Conduct
Is your feature request related to a problem? Please describe.
The SDK ships Agent Card signing support in
a2a.utils.signing(
create_agent_card_signer/create_signature_verifier), and bothA2ACardResolver.get_agent_cardandClientFactory.create_from_urlaccept asignature_verifier. Butsamples/only containshello_world_agent.pyandcli.py, so there is no end-to-end example showing how the two halves fittogether.
That leaves a few things easy to get wrong, and all of them fail in ways that
are hard to diagnose:
key_provider— fetch the JWKS from the signature'sjkuwith a sync HTTPclient — does blocking I/O inside async code. Against an in-process server it
deadlocks, and the symptom is a bare
InvalidSignaturesError: No valid signature found, which points at the crypto rather than at the I/O.create_agent_card_signermutates the card it signs (it appends tocard.signatures). Wiring it directly intocreate_agent_card_routes(card_modifier=...)means the served cardaccumulates a new signature on every request.
key_providerfailures need to bePyJWTErrorsubclasses. The verifieronly catches
PyJWTErrorwhile iterating signatures, so a provider raisingValueErroraborts the whole card instead of moving on to the nextsignature.
jkudefeats the purpose. Thejkuheader livesin the untrusted card, so a verifier that fetches keys from whatever URL the
card names will happily verify a forged card against the attacker's own key.
The key source has to be pinned out of band, and nothing in the API signature
makes that obvious.
Describe the solution you'd like
A runnable sample under
samples/covering both halves of card signing:create_agent_card_signer, serve it throughcreate_agent_card_routes(card_modifier=...), and publish the matchingpublic key as a JWKS document at
/.well-known/jwks.json— the URL thesignature's
jkupoints to.create_signature_verifierpassedto
A2ACardResolver.get_agent_card(signature_verifier=...), resolving keysby
kidfrom an allowlist of pinned JWKS URLs, with the accepted algorithmspinned too.
a card whose transport URL was rewritten in transit, a card with its
signature stripped, and a genuine card whose
jkuthe client does not trust.Plus a smoke test in the style of
tests/integration/test_samples_smoke.pyanda
samples/README.mdsection.Describe alternatives you've considered
Documenting the flow in prose only (e.g. in the SDK docs). The unit tests in
tests/utils/test_signing.pyalready cover the crypto, but they use symmetrickeys and an inline
key_provider, so they do not surface the async, mutationor key-pinning pitfalls above — those only show up once a real server and
client are wired together.
Additional context
No response
Code of Conduct