Skip to content

fix(mcp): harden inter-agent messaging (loopback-only + anti-hijack + audit log)#89

Closed
paulovitin wants to merge 1 commit into
sstraus:mainfrom
paulovitin:fix/agent-messaging-loopback-auth
Closed

fix(mcp): harden inter-agent messaging (loopback-only + anti-hijack + audit log)#89
paulovitin wants to merge 1 commit into
sstraus:mainfrom
paulovitin:fix/agent-messaging-loopback-auth

Conversation

@paulovitin

@paulovitin paulovitin commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

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 agent tool'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 → inbox end to end.

Problem

register, list_peers, send, inbox in handle_agent_unified:

  • received the caller SocketAddr but dropped it — only spawn enforced loopback. A remote/LAN MCP client (Basic-Auth'd or via lan_auth_bypass) could drive peer messaging and inject into any peer's inbox.
  • register accepted any well-formed UUID with no proof of ownership and blindly overwrote an existing peer (inbox hijacking).
  • no audit logging on register/send.

None of these are in AGENTS.md "Accepted Security Decisions".

Fix

  1. Loopback-only messagingregister/list_peers/send/inbox restricted to localhost, matching spawn. Remote/LAN clients rejected; local same-instance coordination unaffected.
  2. Anti-hijack — reject a register that would overwrite a peer bound to a different, live MCP session. Same-session reconnect/rename and dead-session takeover still work.
  3. Audit logregister/send emit source="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 to to-test.md.

🤖 Generated with Claude Code

…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>
@paulovitin paulovitin closed this Jun 24, 2026
@paulovitin paulovitin deleted the fix/agent-messaging-loopback-auth branch June 24, 2026 16:54
@paulovitin paulovitin changed the title fix(mcp): harden inter-agent messaging (loopback-only + anti-hijack + audit log) error Jun 24, 2026
@paulovitin paulovitin restored the fix/agent-messaging-loopback-auth branch June 24, 2026 16:59
@paulovitin paulovitin reopened this Jun 24, 2026
@paulovitin paulovitin changed the title error fix(mcp): harden inter-agent messaging (loopback-only + anti-hijack + audit log) Jun 24, 2026
@paulovitin paulovitin marked this pull request as ready for review June 24, 2026 17:00
@sstraus

sstraus commented Jun 25, 2026

Copy link
Copy Markdown
Owner

Hi @paulovitin can you explain how it happened? What kind of prompt triggered the subagent action?

@paulovitin

Copy link
Copy Markdown
Contributor Author

@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:

  • register/list_peers/send/inbox were reachable with no source-address check, and peer identity was self-asserted (any UUID, no proof of ownership).
  • So a local process or peer could register, enumerate peers, and drop an arbitrary message into another agent's inbox. Agents are instructed to poll the inbox and act on peer messages, so attacker-controlled content (including a spoofable from_name) could land in an agent's context and steer it — e.g. toward an email/exfil action.

Caveats so I'm not overstating it:

  • This is a demonstrated capability + hardening, not a confirmed root cause of that specific email attempt. I can't prove the channel was the vector.
  • Delivery is a passive mailbox (+ optional SSE notification), not a direct PTY/keystroke injection — it reaches the agent only via polling or an open channel.
  • The "email to an unknown address" signature also commonly comes from prompt injection in content the agent read, or a malicious skill/MCP. I couldn't rule those out without logs.

Net: this PR closes the remote/LAN injection and identity-hijack holes and adds the audit trail so a next occurrence is actually traceable.

@paulovitin

Copy link
Copy Markdown
Contributor Author

Closing this as superseded.

The same fix has already landed on main in 63286e5 — loopback gating for register/list_peers/send/inbox (matching spawn), the anti-hijack guard on register, and source="agent_msg" audit logging for register/send. The logic is identical, so this PR's diff now conflicts as "both sides made the same change" and merging it would only re-introduce duplicate code.

Nothing to merge here — the hardening is in main. Leaving this thread up for the investigation context, PoC, and rationale. Thanks for landing it. 👍

@paulovitin paulovitin closed this Jun 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants