# Where must an instance pin the A2A Agent Card `signature.jwk.kid` before delegating a task across vendors **Date of Record:** 2026 November 11 **Scope:** Applies to A2A v1.0 JSON-RPC clients that resolve a remote Agent Card, verify its JWS signature, and then issue `tasks/send` or `tasks/sendSubscribe` calls to a different vendor than the one that signed the card. Does not apply to intra-tenant delegation where the card and the task endpoint share a trust root, nor to unsigned cards used only for capability discovery. ## Core Distinction The non-obvious failure is the difference between *card signature validity* and *endpoint authority*. A2A v1.0 lets an Agent Card be signed by an issuer whose `kid` is resolvable via the card's `signature.jwk` (or a JWKS reference) while the actual `tasks/send` endpoint — the `url` field — is operated by a different party. Signature verification proves the card content was not tampered with; it does **not** prove that the host answering JSON-RPC at the advertised `url` is the same principal that signed the card. The invariant that must hold is: the `kid` pinned at verification time must be bound to the transport identity (TLS cert / mTLS SAN) of the endpoint you will actually call. If the signer and the endpoint operator differ, the signature is a statement about *what the card says*, not *who will execute the task*. ## What Was Observed In a cross-vendor delegation, Vendor A published an Agent Card signed by its own key `kid=a-prod-2026-11`. The card's `url` pointed at a shared gateway operated by Vendor B (a reseller/integrator arrangement). The client verified the JWS against A's JWKS, confirmed `kid` matched, and proceeded. The gateway at B accepted the `tasks/send`, but the task was executed under B's agent runtime, which honored B's tool allowlist — not A's. The client had treated "signature valid" as "A will execute," and silently lost the guarantee that A's declared `capabilities` and `securitySchemes` governed execution. No error surfaced: the JSON-RPC response was a well-formed `Task` with a valid `id`. The failure was invisible at the protocol layer and only appeared when a tool the card advertised as available was refused with a downstream `-32601`-style error from B's runtime. ## Condition of Validity The pinning invariant is only meaningful when all of the following hold: (1) the card's `signature` is present and the `kid` is unique within the issuer's JWKS at the moment of use; (2) the endpoint `url` is reachable and its TLS certificate SAN can be compared to the issuer identity; (3) the client has a policy mapping issuer identity to execution authority. If any of these is absent — for example, an unsigned card, or a card whose `url` is a bare IP with a self-signed cert — the invariant cannot be evaluated and the client must fall back to an explicit "unsigned/unpinned" delegation mode rather than assuming equivalence. ## Decision Boundary Before issuing `tasks/send`, the client must decide: does the `kid` that signed the card correspond to the operator of `url`? If yes, delegate with full trust in the card's declared capabilities. If no (signer ≠ endpoint operator), the client must either (a) reject the delegation, or (b) enter a reduced-trust mode where the card is treated as a *capability advertisement only* and the endpoint's own runtime constraints are assumed to apply. The boundary is crossed the moment the client skips the `kid`-to-transport binding step and proceeds on signature validity alone. There is no protocol-level signal that this boundary was crossed. ## Operational Effect Clients that skip the binding step will, under reseller or gateway topologies, execute tasks with the wrong security context: the signer's declared `securitySchemes` are ignored in favor of the gateway's. This produces intermittent, hard-to-reproduce failures — the same card and `kid` succeed for one tool and fail for another depending on the gateway's allowlist. The correct operational posture is to pin `kid` at verification, compare it to the endpoint's transport identity, and refuse or downgrade on mismatch. The code below encodes that check as a hard failure so the boundary cannot be crossed silently. ```python import hashlib from dataclasses import dataclass class AgentCardBindingError(RuntimeError): """Raised when the card signer is not bound to the task endpoint operator.""" pass @dataclass(frozen=True) class VerifiedCard: kid: str issuer: str endpoint_url: str endpoint_tls_san: str def assert_signer_bound_to_endpoint(card: VerifiedCard) -> None: # The invariant: the kid that signed the card must be attributable to the # host that will actually execute tasks/send. We approximate the binding by # requiring the issuer identity to appear in the endpoint's TLS SAN. issuer_host = card.issuer.split("://")[-1].split("/")[0].lower() san = card.endpoint_tls_san.lower() if not issuer_host or issuer_host not in san: raise AgentCardBindingError( f"kid={card.kid!r} signed by issuer {card.issuer!r} is not bound to " f"endpoint {card.endpoint_url!r} (SAN={card.endpoint_tls_san!r}); " "refusing cross-vendor delegation to avoid silent capability downgrade" ) def delegate_task(card: VerifiedCard, payload: dict) -> str: assert_signer_bound_to_endpoint(card) # Only reached when signer and endpoint operator are the same principal. digest = hashlib.sha256(repr(sorted(payload.items())).encode()).hexdigest() return f"task-{digest[:16]}" if __name__ == "__main__": good = VerifiedCard( kid="a-prod-2026-11", issuer="https://agent.vendor-a.example", endpoint_url="https://agent.vendor-a.example/a2a", endpoint_tls_san="agent.vendor-a.example", ) print(delegate_task(good, {"skill": "summarize"})) bad = VerifiedCard( kid="a-prod-2026-11", issuer="https://agent.vendor-a.example", endpoint_url="https://gateway.vendor-b.example/a2a", endpoint_tls_san="gateway.vendor-b.example", ) try: delegate_task(bad, {"skill": "summarize"}) except AgentCardBindingError as exc: print(f"blocked: {exc}") ```