You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(endpoints): /a2a/intercept has no authentication — sender_agent_id is self-declared, anyone can be any trusted agent (impersonation → forwarded + signed JWT) #83
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-89 — POST /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-391 — evaluate() keys blocklists, directional allowlist, scoped entries, and per-pair token buckets entirely on the body-declared sender_agent_id
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
Add transport authentication: mTLS client certs, or per-agent API keys/HMAC via a FastAPI dependency.
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.
Summary
The A2A verification gateway has no authentication of any kind.
/a2a/interceptaccepts any HTTP caller, andsender_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-89—POST /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-391—evaluate()keys blocklists, directional allowlist, scoped entries, and per-pair token buckets entirely on the body-declaredsender_agent_idsrc/qwed_a2a/protocol/schema.py:64-67— the optionalsignaturefield is never read anywhere (tracked separately as fix(schema): AgentMessage.signature field accepted but never verified — creates false trust signal #19); it provides no protectionReproduction (CONFIRMED BY EXECUTION)
Unauthenticated request to a service with
QWED_A2A_TRUSTED_AGENTS=procurement-agent,treasury-agent:Impact
qwed_a2a.senderclaim names the victim agent — audit trails and metrics attribute the attacker's traffic to the victim.(sender, receiver)token bucket; N identities = N× the per-pair limit.False VERIFIED/ADMIT + unauthorized execution: yes — the interceptor verifies, forwards, and attests content "from" an agent that never sent it.
Remediation
sender_agent_id, never merely "confirmed" by it.signaturefield) so exactly one identity source of truth exists.AgentMessagedirectly own identity binding and must not feed untrusted IDs.Architecture change required: NO (FastAPI dependency injection; additive).