Skip to content

0.8.0 — Reference-SDK rigs for A2A and MCP

Choose a tag to compare

@evkir evkir released this 29 Jul 17:33
· 142 commits to main since this release

Both protocol clients were validated against hand-written mocks, which only ever confirm that the scanner agrees with itself. Pointing each one at its reference implementation turned that agreement into test failures: five defects in A2A, six in MCP. This release exists because 0.7.0 reports clean scans it has not earned — a strict A2A v1.0 server answered -32601 to every call while the active scan reported success, and a fully populated MCP server over Streamable HTTP fingerprinted as zero tools with every downstream audit running over nothing. A scanner that says "nothing found" because it never arrived is worse than no scanner.

Reference-SDK rigs

  • A2A rig (lab/a2a/agent.py, pip install -e '.[lab]') built on the reference a2a-sdk. It publishes a deliberately weak v1.0 AgentCard and echoes task text back as an artifact, so the passive audit and the injection canary both have something real to find. Serves strict v1.0 by default and the legacy v0.3.x vocabulary under A2A_LAB_COMPAT=1.
  • MCP rig (lab/mcp/server.py, plus a compose service) built on the reference mcp SDK. Both transports and both protocol generations answer on one endpoint. Deliberately vulnerable along each detector axis: a poisoned tool description, an unguarded read_file, a fetch_url that dereferences anything including file://, a resource carrying both an injection directive and a Markdown-image beacon, and a templated resource whose description smuggles a directive of its own. It exposed four transport and reporting defects within the first hour.

A2A wire correctness

  • The client spoke the v0.3.x JSON-RPC vocabulary unconditionally and sent no A2A-Version header. The dialect is now resolved from the discovered card: supportedInterfaces means v1.0, a top-level url with preferredTransport means v0.3.x, and an explicit protocolVersion overrides the shape.
  • A fully secured v0.3.x card was reported HIGH as enforcing no authentication. Both generations name the mandatory subset under a different key — securityRequirements in v1.0, security in v0.3.x — and only the v1.0 spelling was read. A false positive at HIGH, aimed precisely at the operators who got it right.
  • An agent replying with a Message instead of a Task went unscanned. SendMessageResponse is a oneof; the client looked only for a Task, then polled a task id it had never been given. Task history is deliberately excluded from the scan, since it contains the probe payload and would make every agent self-match.
  • Dropped the invented params.id. Neither generation lets a client choose the id of a task it is creating. Seven unit mocks asserted that field, which is precisely why the divergence survived.
  • probe_unauthorized_cancel accepted any JSON-RPC error as proof the server had rejected the call, so -32601 was reported as "server behaved safely". Only the task-domain codes count as a rejection now; everything else is inconclusive.
  • Removed two card checks reading fields that exist in neither generation, so no configuration could ever clear them.

MCP wire correctness

  • Streamable HTTP scanned every server clean. The transport dropped both pieces of per-connection state: a server that mints an Mcp-Session-Id on initialize rejects every later request without it, and the enumeration helpers turned that error into an empty list. Remote and hosted servers are exactly the ones an operator points a scanner at, so the blind spot covered the whole practical MCP surface. The negotiated revision failed more quietly — with no MCP-Protocol-Version header the spec tells a server to assume 2025-03-26, so the scanner reasoned about a generation the server was not speaking.
  • A refused listing read as an empty inventory. All three list methods returned [] on error, the same value they return for a server that genuinely has none, so the coverage claim and the clean result were indistinguishable. Failures now surface as an enumeration_gap check, with the JSON-RPC code deciding severity: -32601 means the surface does not exist (INFO); anything else means it may exist and went unexamined (MEDIUM).
  • Every MCP finding reached HTML, Markdown, SARIF and JUnit as module unknown with a blank title and no taxonomy — a CRITICAL tool-poisoning hit filed under SARIF ruleId MAS-SENTRY-UNKNOWN. from_mcp_check was written, unit-tested and never called. Full coverage of the function was exactly what hid the missing call, so the new tests drive the CLI instead.
  • StdioConfig.timeout was declared and never applied. A server that accepted a request and answered nothing stopped the scan for good — and whether it answers is the target's choice. A pentest tool the scanned host can hang is a denial of service on its own operator. Reads now poll with a deadline; a timeout or a closed pipe becomes a JSON-RPC error rather than a hang, and a closed pipe reports the exit code and stderr tail.
  • Paginated listings are walked to the end. All three list methods read page one and stopped. A server that repeats a cursor, or never stops issuing them, would spin the scanner in place, so both end the walk and are recorded as gaps.
  • Templated resources are enumerated and scanned (resources/templates/list, check resource_template). They never appear in resources/list. The body needs a parameter value and stays out of reach, but the name and description do not — and those are what an agent weighs before expanding the template and pulling the result into context.
  • MCP resource-content auditing (--checks resources) and structured CallToolResult reading: content-block extraction across text, image, audio, embedded-resource and resource-link types, with base64 decoding. The SSRF and traversal probes previously matched indicators against a truncated Python repr, so content delivered as base64 could not match at all.

Upgrading

No API changes. Anyone scanning MCP over HTTP or A2A v1.0 should re-run against targets previously reported clean — those results were false negatives, not findings.

Full Changelog: v0.7.0...v0.8.0

What's Changed

  • docs(hcap): fix dead account references in spec by @evkir in #1

New Contributors

  • @evkir made their first contribution in #1

Full Changelog: v0.7.0...v0.8.0