docs(samples): add Agent Card signing sample - #1198
Open
orangecola wants to merge 4 commits into
Open
Conversation
There was no runnable example for `a2a.utils.signing`, so add one covering both halves of Agent Card signing: - Server: signs the card with an ES256 key via `create_agent_card_signer`, wired in through `create_agent_card_routes(card_modifier=...)`, and publishes the public key as a JWKS document referenced by the signature's `jku` header. - Client: verifies the fetched card with `create_signature_verifier` passed to `A2ACardResolver.get_agent_card`, resolving keys by `kid` from an allowlist of pinned JWKS URLs and an allowlist of algorithms. The default demo mode runs both, then shows the rejections a verifier exists for: 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. Adds a smoke test for the sample and a README section. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y9eQGQeMVdqZ1f1m8ijGm3
🧪 Code Coverage (vs
|
mykytanetipa
requested changes
Aug 31, 2026
Collaborator
There was a problem hiding this comment.
Presubmits need to be green before review can be granted.
Only failure is Check Spelling: it flags SECP at samples/agent_card_signing.py. Since check-spelling splits on digits, add a bare SECP to .github/actions/spelling/allow.txt (the existing SECP256R1 won't match).
Review fixes on the Agent Card signing sample: - Raise instead of printing `ERROR:` when a forged card is accepted. The demo previously exited 0 in exactly the case it exists to catch, and the smoke test's only guard was a substring check that any stray ERROR log would trip. - Drop that substring assertion from the smoke test; a non-zero exit now covers it. - Quiet INFO logging in the client modes, where httpx and the card resolver (which logs the whole card) buried the numbered walkthrough. `serve` keeps INFO, since there the server log is the point. - Rename `JwksKeyProvider.fetch` to `from_urls`, matching the `PyJWKSet` constructors it wraps. - Check the response status on the JWKS fetch in the last demo case. - Fix a stale docstring and a docstring wrapped past 80 columns. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y9eQGQeMVdqZ1f1m8ijGm3
…leanly" This reverts commit 64c8e58.
check-spelling splits tokens on digits, so `SECP256R1` in the source is checked as `SECP`, which the existing `SECP256R1` allowlist entry does not cover. Add the bare token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y9eQGQeMVdqZ1f1m8ijGm3
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.
There was no runnable example for
a2a.utils.signing, so this adds onecovering both halves of Agent Card signing.
samples/agent_card_signing.py:create_agent_card_signer, serves it viacreate_agent_card_routes(card_modifier=...), and publishes the public keyas a JWKS document at
/.well-known/jwks.json— the URL the signature'sjkupoints to.create_signature_verifierpassed toA2ACardResolver.get_agent_card(signature_verifier=...), resolving keys bykidfrom an allowlist of pinned JWKS URLs, with algorithms pinned toES256.demo(default, runs both in-process),serve,verify --url.The default
demomode verifies the genuine card and then shows the threerejections a verifier exists for:
Two non-obvious details are called out in comments, since they bite anyone
copying the pattern:
with a sync HTTP client blocks the event loop (it deadlocks against an
in-process server and reports
No valid signature found). The key providerdoes pure in-memory lookups over JWKS prefetched asynchronously.
create_agent_card_signermutates the card it signs, so a per-requestcard_modifierwould accumulate signatures. The helper deep-copies first andthe card is signed once at startup.
The key provider also raises
PyJWKErrorrather than a plain exception, so theverifier treats one unusable signature as unverifiable and moves to the next
instead of aborting a card that also carries a good signature.
Also adds
tests/integration/test_agent_card_signing_smoke.py(runs the demoon a free port, asserts one acceptance and three rejections) and a
samples/README.mdsection.No library code changed — samples, tests and docs only.
Testing
uv run pytest tests/utils/test_signing.py tests/integration/test_agent_card_signing_smoke.py tests/client/test_card_resolver.py→ 55 passeduv run ruff format --checkanduv run ruff check→ cleanuv run ty check→ same 4 pre-existing diagnostics asmain(samples are outside[tool.ty.src])Checklist
CONTRIBUTINGGuide.bash scripts/format.shfrom the repository root to format)Fixes #1197 🦕