Skip to content

AdCP 3.3: A2A operation-resolution hardening is MUST; advertise request_signing.operation_sources #1637

Open

Description

@bokelley

Spec

The A2A operation-resolution errata (GHSA-2pm6-6mc8-8xcm) shipped in 3.2.3 with its hardening clauses at SHOULD. From 3.3 they're MUST for every verifier, whether or not it advertises the marker. See the migration guide §A2A operation-resolution hardening becomes MUST and security.mdx §Operation resolution over A2A (tag v3.3.0-beta.0):

  1. Strict parsing of unsigned bodies, including JSON-RPC batches and duplicate or case-variant member names. Duplicate keys are detected after JSON string decoding ("skill" and "skill" collide).
  2. A2A Part shape: one content member per Part, no FilePart, and kind agreement.
  3. Task-continuation binding: a message.taskId request must resolve to the task's original operation and come from the principal that created it.

The new optional capability request_signing.operation_sources (mcp_tools_call, a2a_invocation_skill) declares support. A present array must list every source the verifier supports.

Current state

server/signed_requests.py::_jsonrpc_target parses with json.loads, which silently keeps the last duplicate. parse_a2a_jsonrpc_skill validates through a2a-sdk models. Part-shape and continuation binding still need auditing.

Work

  • Strict duplicate- and case-variant-key detection (reuse signing/_strict_json.py if it fits) on the operation-resolution path; fail closed with request_body_malformed.
  • Part-shape checks; reject more than one DataPart on an invocation.
  • Bind taskId continuations to the original operation and principal.
  • Run the A2A conformance vectors, including the hardening tier, if the 3.3 bundle ships them.
  • After regen: advertise request_signing.operation_sources from RequestSignatureVerification when enabled.

Batch: A (enforcement; hand-written). The capability advertisement follows in batch D.

Activity

  1. added
    no-triageSkip the Claude triage bot — human or designated agent will handle this issue
    sdk-10SDK 10 (next branch, AdCP 3.3)
    on Oct 26, 2026
  2. bokelley commented on Oct 27, 2026

    @bokelley
    ContributorAuthor

    PR #1659 enforces the three MUST clauses when request signing is configured. All 33 A2A operation-resolution vectors from v3.3.0-beta.0 pass, including the hardening tier.

    Follow-ups, still open here:

    • adcp_task_binding (the operation and an unsalted SHA-256 of tenant and principal) rides in client-visible Task.metadata. Strip it from client-facing task payloads, or move it to server-side storage.
    • Unsigned servers: the executor's default parser still accepts the TextPart JSON fallback and extra DataParts.
    • input member: the default parser reads parameters but not the A2A profile's input member.
    • Discovery bypass: server/auth.py's discovery-skill bypass parses without the strict resolver.
    • Bounded parse: call the shared bounded parse (_json_bounds, AdCP 3.3: bound inbound push/webhook bodies before signature verification, and response JSON depth/node count #1636) in SignedRequestVerificationMiddleware.__call__ after the body is buffered, and in _jsonrpc_target/_a2a_target before parse_strict_json.
    • After regen (batch D): advertise request_signing.operation_sources.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    no-triageSkip the Claude triage bot — human or designated agent will handle this issuesdk-10SDK 10 (next branch, AdCP 3.3)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions