fix(mcp): harden inter-agent messaging (loopback-only + anti-hijack + audit log)#89
fix(mcp): harden inter-agent messaging (loopback-only + anti-hijack + audit log)#89paulovitin wants to merge 1 commit into
Conversation
…dentity hijack The `agent` tool's messaging actions (register/list_peers/send/inbox) had no source-address check. Any client reaching POST /mcp — including a remote/LAN client authenticated via Basic Auth or reaching the server through lan_auth_bypass — could register an arbitrary self-asserted peer identity, enumerate peers, inject messages into any peer's inbox, and read inboxes. `register` also blindly overwrote existing bindings (inbox hijacking), and there was no audit trail for register/send. - Restrict register/list_peers/send/inbox to loopback connections, matching `spawn`. Remote/LAN MCP clients are rejected. Local same-instance peer coordination (Unix socket / loopback) is unaffected. - Reject a register that would overwrite a peer bound to a different, live MCP session (anti-hijack). Same-session reconnect/rename and dead-session takeover still work. - Audit-log register and send under source="agent_msg" (identities + message size + delivery channel, never message content). Tests: messaging_actions_require_loopback, messaging_register_rejects_hijack_of_live_session, messaging_register_same_session_updates_even_when_live, messaging_register_takeover_of_dead_session_allowed. Docs: docs/backend/mcp-http.md (Inter-Agent Messaging > Security); to-test.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Hi @paulovitin can you explain how it happened? What kind of prompt triggered the subagent action? |
|
@sstraus honestly, I can't point to the exact triggering prompt. The incident is what kicked off the investigation, but there was no audit logging on the messaging path at the time, so there's no record of the message content or its source — adding that trail is part of this PR. What I could establish and reproduce with a local PoC:
Caveats so I'm not overstating it:
Net: this PR closes the remote/LAN injection and identity-hijack holes and adds the audit trail so a next occurrence is actually traceable. |
|
Closing this as superseded. The same fix has already landed on Nothing to merge here — the hardening is in |
Context
Audit triggered by an incident: a subagent attempted to send an email to an unknown address (blocked by local guardrails). Investigation of the inter-agent messaging surface found the
agenttool's messaging actions reachable with no source-address check and a self-asserted, forgeable peer identity — and no audit trail, which is why the incident left no evidence.A local PoC over the same-user Unix socket confirmed an unauthenticated caller could
initialize → register → list_peers → send → inboxend to end.Problem
register,list_peers,send,inboxinhandle_agent_unified:SocketAddrbut dropped it — onlyspawnenforced loopback. A remote/LAN MCP client (Basic-Auth'd or vialan_auth_bypass) could drive peer messaging and inject into any peer's inbox.registeraccepted any well-formed UUID with no proof of ownership and blindly overwrote an existing peer (inbox hijacking).register/send.None of these are in
AGENTS.md"Accepted Security Decisions".Fix
register/list_peers/send/inboxrestricted to localhost, matchingspawn. Remote/LAN clients rejected; local same-instance coordination unaffected.registerthat would overwrite a peer bound to a different, live MCP session. Same-session reconnect/rename and dead-session takeover still work.register/sendemitsource="agent_msg"records (identities, message size, delivery channel; never message content).Residual / scope
Same-user local processes remain inside the trust boundary by design (the Unix socket is unauthenticated — documented Security Model). This PR closes the remote/LAN injection vector and cross-session identity hijacking, and adds the audit trail that makes the local case observable. Fully gating same-user local callers would require changing the IPC trust model and is out of scope.
Tests
cargo test --lib mcp_http::mcp_transport -- --test-threads=1→ 143 passed.New:
messaging_actions_require_loopback,messaging_register_rejects_hijack_of_live_session,messaging_register_same_session_updates_even_when_live,messaging_register_takeover_of_dead_session_allowed.(Single-threaded run avoids a pre-existing parallel-sqlite isolation flake in
messaging_inbox_fifo_eviction, unrelated to this change.)Docs:
docs/backend/mcp-http.md(Inter-Agent Messaging → Security); manual checks added toto-test.md.🤖 Generated with Claude Code