fix(a2a): add Agent Card JWS signature verification and trust policy - #6202
Merged
Conversation
bug-ops
enabled auto-merge (squash)
July 13, 2026 01:44
Add optional, feature-gated (card-signing) A2A 1.0.0 AgentCardSignature verification to AgentRegistry::discover(), closing the card-spoofing/ impersonation gap described in #5928: peer cards were previously trusted unauthenticated with no cross-check between the queried base_url and the card's own url field. Adds a tri-state card_trust_policy (ignore/prefer/require, default ignore) combining signature verification and URL-origin consistency via a most-severe-wins precedence, an out-of-band operator-configured trusted-key store (not the card-supplied jku, which would reopen an SSRF surface this crate already guards against), and a config-load fail-fast when require is set without the card-signing feature compiled in. A2A_PROTOCOL_VERSION intentionally stays at 0.2.1 rather than claiming full 1.0.0 conformance, since this adds one 1.0.0 feature (signatures), not the full spec delta (transport bindings, well-known path rename, method reconciliation all remain unimplemented and are documented as deferred). Known limitations, tracked as follow-ups: - Canonicalization is implemented per spec text but not validated against a real a2a-sdk signed-card vector (no network access during development); require may reject genuinely valid peers until this is proven. Tracked in #6201. - AgentRegistry has no runtime construction site in this codebase yet, so card_trust_policy is a fully implemented and tested library/config primitive with no live consumer. Tracked in #6200. Closes #5928
bug-ops
force-pushed
the
fix/5928-a2a-card-signing
branch
from
July 13, 2026 01:44
7ce3b80 to
656411e
Compare
…spec Update specs/014-a2a/spec.md to reflect the Agent Card signature verification and CardTrustPolicy work landed in this PR (#5928): policy outcome table, the two-enum protocol/config split rationale, and a new Key Invariant codifying that the signature trust anchor must be an out-of-band key store, never a card-supplied jku, so a future contributor doesn't "fix" that as an oversight. Also adds an A2A 1.0.0 Conformance section explaining why A2A_PROTOCOL_VERSION stays at 0.2.1 and documenting the two known gaps (#6200 unwired registry, #6201 unvalidated interop) as current limitations, so the spec doesn't imply more is enforced than it is. Updates the specs/README.md index entry accordingly.
bug-ops
added a commit
that referenced
this pull request
Jul 13, 2026
The rebase onto origin/main picked up PR #6202's migration step 82 (A2A card trust). Renumber the worktree-quota step to 83 and fix the now-stale hardcoded step counts and step-order doc comments the merge left behind.
bug-ops
added a commit
that referenced
this pull request
Jul 13, 2026
The rebase onto origin/main picked up PR #6202's migration step 82 (A2A card trust). Renumber the worktree-quota step to 83 and fix the now-stale hardcoded step counts and step-order doc comments the merge left behind.
bug-ops
added a commit
that referenced
this pull request
Jul 13, 2026
The rebase onto origin/main picked up PR #6202's migration step 82 (A2A card trust). Renumber the worktree-quota step to 83 and fix the now-stale hardcoded step counts and step-order doc comments the merge left behind.
7 tasks
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.
Summary
card-signing) A2A 1.0.0AgentCardSignature(JWS/ES256) verification toAgentRegistry::discover(), closing the unauthenticated Agent Card trust gap described in research(a2a): no Agent Card signing/verification — protocol version stuck at 0.2.1 vs. current spec 1.0.0 #5928: peer cards were previously accepted with no signature check and no cross-check between the queriedbase_urland the card's ownurlfield.card_trust_policy(ignore/prefer/require, defaultignore) that combines signature verification and URL-origin consistency via a most-severe-wins precedence, backed by an out-of-band operator-configured trusted-key store ([a2a_client].trusted_agent_keys) rather than the card-suppliedjkuURL, which would reopen an SSRF surface this crate already guards against.A2A_PROTOCOL_VERSIONintentionally stays at"0.2.1"rather than claiming full 1.0.0 conformance — this PR adds one 1.0.0 feature (signatures), not the full spec delta. Well-known path rename, transport bindings, and method-name reconciliation remain unimplemented and are documented as deferred.requireis set without thecard-signingfeature compiled in, rather than silently degrading.Known limitations (tracked as follow-ups, not silently deferred)
a2a-sdksigned-card vector — no network access to the reference implementation during development.require/prefermay reject genuinely valid peers until this is proven; atracing::warn!fires atwith_trust()call time to surface this to operators. Tracked in validate A2A card signature JWS canonicalization against a real signed-card vector #6201.AgentRegistryhas no runtime construction site anywhere in this codebase (verified independently by multiple review passes) —card_trust_policy/trusted_agent_keysare fully implemented and tested as a library + config primitive, but nothing in the running agent wires them up yet, sorequirecurrently enforces nothing live. Tracked in wire AgentRegistry construction site so card_trust_policy actually enforces #6200.--initwizard or CLI flag for the new[a2a_client]knobs — there is no existing precedent for the siblingrequire_tls/ssrf_protectionfields either (TOML + env only), so none was invented here.Review process
This went through the full architecture → critique → implementation → parallel validation (tests/perf/security/adversarial critique) → code review pipeline:
requirefail-fast, default policy, CI feature wiring) — all folded into the implementation.config/default.toml, the setup-guide skill, and both docker-compose files) — fixed and re-approved.Test plan
cargo nextest run -p zeph-a2a --features ibct,server,card-signing— 207 passedcargo nextest run -p zeph-a2a(no features) — 124 passedcargo nextest run -p zeph-config --features card-signing— 778 passedcargo nextest run -p zeph-config(no features) — 773 passedcargo nextest run --workspace --features "desktop,ide,server,chat,pdf,scheduler" --lib --bins— full CI feature set greencargo clippy --profile ci --workspace --all-targets --features "desktop,ide,server,chat,pdf,scheduler,testing" -- -D warnings— cleancargo +nightly fmt --check— cleancargo test --doc(workspace + per-cratezeph-a2a/zeph-config) — cleancargo deny check— advisories/bans/licenses/sources all ok for new deps (p256,serde_json_canonicalizer); noopenssl-syspulled ingitleaks protect --staged— cleankid, every policy × axis severity combination, config-load fail-fast, migration idempotency, order-independent multi-signature verification.local/testing/playbooks/a2a.md) and coverage-status row added — statusUntestedpending a live session (no runtime consumer yet per wire AgentRegistry construction site so card_trust_policy actually enforces #6200)Closes #5928