Skip to content

fix(endpoints): /a2a/intercept has no authentication — sender_agent_id is self-declared, anyone can be any trusted agent (impersonation → forwarded + signed JWT) #83

Description

Summary

The A2A verification gateway has no authentication of any kind. /a2a/intercept accepts any HTTP caller, and sender_agent_id — the sole identity signal the entire trust model keys on — is a self-declared string from the request body. Anyone with network access to the endpoint is any allowlisted agent they name, and receives a signed ES256 attestation attributing the verdict to that agent.

Found in the 2026-08-23 adversarial audit of v0.2.0 @ 571073c.

Location

  • src/qwed_a2a/protocol/endpoints.py:71-89POST /a2a/intercept: no auth dependency, no header check, no API key, no mTLS (grep confirms zero auth primitives in the module)
  • src/qwed_a2a/security/trust_boundary.py:316-391evaluate() keys blocklists, directional allowlist, scoped entries, and per-pair token buckets entirely on the body-declared sender_agent_id
  • src/qwed_a2a/protocol/schema.py:64-67 — the optional signature field is never read anywhere (tracked separately as fix(schema): AgentMessage.signature field accepted but never verified — creates false trust signal #19); it provides no protection

Reproduction (CONFIRMED BY EXECUTION)

Unauthenticated request to a service with QWED_A2A_TRUSTED_AGENTS=procurement-agent,treasury-agent:

client.post("/a2a/intercept", json={
    "sender_agent_id": "procurement-agent",   # spoofed — no credentials exist
    "receiver_agent_id": "treasury-agent",
    "payload_type": "financial_transaction",
    "payload": {"data": {"claimed_total": 150.00, "line_items": [
        {"amount": 50.00, "quantity": 2}, {"amount": 25.00, "quantity": 2}]}}
})
# → HTTP 200, status="forwarded", attestation_jwt=<valid ES256 token naming the spoofed sender>

Impact

  • Total zero-trust boundary bypass: every downstream control (allowlist, scoping, rate limiting) is keyed on an attacker-chosen identity.
  • Cryptographically attested impersonation: the JWT's qwed_a2a.sender claim names the victim agent — audit trails and metrics attribute the attacker's traffic to the victim.
  • Rate-limit evasion: each spoofed sender creates a fresh (sender, receiver) token bucket; N identities = N× the per-pair limit.
  • Cross-tenant escalation wherever multiple operators share a deployment.

False VERIFIED/ADMIT + unauthorized execution: yes — the interceptor verifies, forwards, and attests content "from" an agent that never sent it.

Remediation

  1. Add transport authentication: mTLS client certs, or per-agent API keys/HMAC via a FastAPI dependency.
  2. Server-side identity must override the body field — the authenticated credential maps to an agent ID that replaces sender_agent_id, never merely "confirmed" by it.
  3. Land together with fix(schema): AgentMessage.signature field accepted but never verified — creates false trust signal #19 (dead signature field) so exactly one identity source of truth exists.
  4. Document the library-mode equivalent: callers constructing AgentMessage directly own identity binding and must not feed untrusted IDs.

Architecture change required: NO (FastAPI dependency injection; additive).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions