Why defense in depth is required
Cross-machine agent communication has a unique threat surface: content that originates on one developer’s laptop, travels through a relay, and lands in another developer’s agent session. At each step, that content could carry adversarial instructions — either maliciously crafted or the result of a compromised upstream context. A single gate that can be bypassed or fooled collapses the whole guarantee. Four independent layers mean an attacker must break all four simultaneously.Trust boundaries
The four layers
1
L1 — Provenance-wrapped inbound content
The MCP server never injects raw teammate content into the receiver’s context window. Every inbound message, summary, and artifact is wrapped with a preamble that tells the receiving agent to treat the content as data, not instructions:This is the standard pattern for handling untrusted input in agent prompts. It significantly reduces — but does not eliminate — prompt-injection success rate.Where it runs:
mcp-server/src/tools/accept.ts and mcp-server/src/tools/view-thread.ts. Applied when calling accept_handoff (which transitions state) and view_thread (read-only fetch). The check_inbox tool returns only summary previews, not full content, so it does not apply the L1 preamble.2
L2 — Claude Code / Codex permission overlay
L1 reduces the probability that Frank’s agent attempts a dangerous action. L2 ensures that even if it does, the action does not execute. This is the load-bearing layer.
agentrelay install writes a recommended permission config into ~/.claude/settings.json (Claude Code) or ~/.codex/config.toml (Codex CLI). The config follows a risk-tiered friction model:The harness — Claude Code or Codex — intercepts every tool call before it executes. It does not matter what Bob’s agent told Frank’s agent to do:
git push is denied at the harness level.If Frank has already customized his permission config,
agentrelay install detects the divergence, shows a unified diff, and asks before applying. It never silently overwrites user customizations.3
L3 — Per-teammate trust config
Frank pre-authorizes per teammate before any handoff arrives. This creates a session-scoped overlay on top of L2’s defaults. Teammates Frank trusts more get less friction.The config lives at When Frank’s MCP server processes an inbound handoff, it reads
~/.agentrelay/trust.yaml:trust.yaml, computes the trust_overlay for that sender, and returns it alongside the thread content. The accept_handoff tool result includes:unknown_teammates: { policy: reject } is the default created by agentrelay register. Frank must explicitly opt in each teammate — there is no ambient trust.4
L4 — Audit log and instant atomic revocation
Every action Frank’s agent takes in response to a remote handoff is logged with the handoff thread ID, the originating teammate, and the exact tool call, arguments, and result.Two CLI commands surface Layer 4:Audit log retention defaults to 90 days, configurable via
agentrelay block adds the handle to the blocked array in ~/.agentrelay/trust.yaml. Frank’s MCP server reads this on every handoff evaluation and rejects any handoff from a blocked handle before returning content to Frank’s agent. To block at the relay level (enforced even when the MCP server is not running), call POST /agents/me/block directly — see the Admin endpoints reference.Example audit output:RELAY_AUDIT_RETENTION_DAYS.