How to Design Security Boundaries for Multi-Agent Systems

How to design security boundaries for multi-agent AI systems: model inter-agent threats, enforce least-privilege controls, and avoid orchestration bottlenecks.

Most teams building multi-agent AI pipelines today are securing them the same way they secured single-agent systems: wrap the LLM in input validation, add a content filter, sandbox the outputs, call it done. That approach is incomplete in a specific way: it misses the entire threat surface that emerges when agents start talking to each other.

You can design hard security boundaries between agents without turning your orchestration layer into a latency sinkhole. But doing so requires abandoning one comfortable assumption: that agents in your pipeline are inherently trustworthy because you built them.

This article walks through the threat model for agent-to-agent communication, maps the controls that actually matter in production, and shows why the "security adds latency" concern is almost always a red herring.


Why Single-Agent Security Doesn't Transfer

When you secure a single-agent system, your threat model is essentially this: untrusted user input enters, the model processes it, and you validate the output before anything sensitive happens. The perimeter is clear. The data flow is linear.

Multi-agent systems break this model in three ways.

First, agents inherit and amplify each other's privileges. If the researcher agent in your pipeline has read access to your internal knowledge base and the drafter agent has write access to a content management system, a compromised researcher can pass poisoned instructions downstream that cause the drafter to write unauthorized content. The drafter is acting within its own permissions, so no individual agent appears to be misbehaving. The combined effect, however, violates your intent.

Second, agents share context across turns. Shared memory, conversation history, and tool outputs all flow between agents. Any agent that can write to shared context can influence the behavior of every downstream agent that reads from it. This is the vector for memory poisoning attacks: subtle corruptions planted early in a pipeline that don't trigger alarms but gradually shift the behavior of later stages.

Third, implicit peer trust makes multi-agent systems fragile by default. When your reviewer agent receives a draft from your drafter agent, it has no way to verify that the content wasn't tampered with in transit, that the drafter wasn't operating under a compromised instruction set, or that the context passed to it is accurate. It trusts the previous hop.

OWASP's ongoing agentic AI threat modeling work catalogs substantially more threat categories for multi-agent systems than for single-agent deployments. That gap is not academic; it reflects a real increase in attack surface.


Five Threat Categories Worth Understanding

Before designing controls, you need a shared vocabulary for what you're defending against. The major threat categories cluster into five meaningful groups for most production pipelines.

Prompt injection and chain propagation. An adversarial instruction injected into an early agent's input propagates through the pipeline as if it were legitimate data. A researcher agent that scrapes a malicious webpage may embed that instruction in its output; the drafter treats it as context and executes it. Unlike single-agent injection, there is no single sanitization point you can defend.

Privilege escalation via delegation. Agents can invoke tools and other agents. When Agent A has database read access and can invoke Agent B, which has network egress, a compromised Agent A effectively gains the union of both permission sets. Privilege escalation in multi-agent systems is recursive and often invisible to traditional access logs.

Memory poisoning. Any agent with write access to shared context, vector stores, or conversation history can corrupt the information environment downstream agents rely on. This is particularly dangerous in retrieval-augmented pipelines, where a poisoned knowledge base silently shapes every subsequent generation.

Covert channels and collusion. Two agents can coordinate hidden behaviors without triggering logging systems. Steganographic communication (embedding signals in seemingly normal outputs), timing-based channels (coordinating via inference latency), and correlated behavioral drift are all documented attack vectors in multi-agent research. These are difficult to detect because each individual agent appears to be behaving normally.

Byzantine failure propagation. A compromised or malfunctioning agent can provide systematically false information to downstream agents, corrupting decision-making across the entire pipeline. Unlike a simple failure (which is visible), a Byzantine agent lies in ways that are plausible and internally consistent.


Agent-to-Agent Communication and the Implicit Trust Problem

Production orchestration frameworks use protocols like MCP (Model Context Protocol), ACP, and A2A (Agent-to-Agent) to manage how agents exchange data and invoke each other. The choice of protocol directly shapes your security surface, because each protocol encodes different trust assumptions.

Some protocols authenticate that an agent is who it claims to be but don't restrict what that agent can send once authenticated. Others assume all agents in a pipeline are cooperative and make no provision for adversarial behavior. In both cases, the protocol gives you identity verification without trustworthiness verification, which is a different and harder problem.

The practical implication: don't assume that because your orchestration framework routes messages between agents, those messages are safe to act on. Treat every agent-to-agent handoff as a trust boundary, the same way you'd treat an API call from an external service.

Concretely, this means three things:

  • Define explicit schemas for what data can flow between which agents. If your researcher agent should only pass structured citations and summaries to the drafter, enforce that schema at the handoff point and reject anything that doesn't conform.
  • Authenticate the source of every message at the receiving agent, even within your own pipeline. If the drafter receives a message claiming to come from the researcher, it should verify that claim rather than assume it.
  • Log the content and metadata of every handoff. You cannot investigate an incident you didn't record.

A Five-Plane Security Architecture

The most useful framework for reasoning about multi-agent security in production is a five-plane model that separates concerns across the system:

  1. Perception plane: How agents receive input from the environment and from other agents. This is where prompt injection enters.
  2. Reasoning plane: The model's internal decision-making. This is where memory poisoning and instruction manipulation take effect.
  3. Data plane: Shared storage, vector databases, conversation history. This is where context flows between agents and where poisoning persists across turns.
  4. Execution plane: Tool calls, API invocations, code execution. This is where privilege escalation and tool misuse cause real-world harm.
  5. Auditing plane: Logs, traces, and monitoring. This is what lets you detect, investigate, and recover from incidents.

The key insight from this model is that most security teams focus entirely on the execution plane (sandboxing tool calls) while leaving the data and perception planes largely undefended. A fully sandboxed code execution environment does nothing to stop a poisoned context from causing the model to generate malicious code in the first place.

Effective security requires controls at all five planes, operating independently, so that a failure at one plane doesn't automatically compromise the others.


The Three Controls That Actually Matter

Given that list, where should you actually spend your effort? Three controls deliver the highest coverage per unit of implementation complexity.

1. Execution Isolation: Limit the Blast Radius

If an agent is tricked into generating or executing malicious code, the damage should be limited to that agent's sandbox: not the host system, not other agents, and not your data infrastructure.

Docker containers provide OS-level isolation appropriate for most agents. MicroVMs (Firecracker is the common choice) provide hardware-level isolation when agents can generate and execute arbitrary code, because container escapes, while rare, are documented and ongoing. The rule of thumb: if an agent can write and run code, use microVM isolation. If an agent can only call predefined tools, container isolation is usually sufficient.

Isolation must be configured with network egress restrictions. An isolated container with unrestricted outbound network access can still exfiltrate data. Egress should be allowlisted to only the specific endpoints each agent needs, not the open internet.

2. Per-Agent Authorization: Enforce Least Privilege at Runtime

Each agent should operate with only the permissions it needs for its defined role, and those permissions should be checked at runtime, not assumed at initialization.

The pattern that works well in practice is to wrap tool execution at the agent level with a policy evaluator that runs before each tool call. This co-locates policy enforcement with the agent, eliminating the latency of a round-trip to a centralized policy server.

Here is a minimal Python example of this pattern:

from dataclasses import dataclass
from typing import Callable, Any
import functools

@dataclass
class AgentPolicy:
    role: str
    allowed_tools: set[str]
    read_only_tools: set[str]   # Tools allowed, but writes are blocked

def enforce_policy(policy: AgentPolicy):
    """Decorator factory that wraps a tool function with policy enforcement."""
    def decorator(tool_fn: Callable) -> Callable:
        tool_name = tool_fn.__name__

        @functools.wraps(tool_fn)
        def wrapper(*args, **kwargs) -> Any:
            if tool_name not in policy.allowed_tools:
                raise PermissionError(
                    f"Agent role '{policy.role}' is not permitted to call '{tool_name}'"
                )
            if tool_name in policy.read_only_tools and kwargs.get("write"):
                raise PermissionError(
                    f"Agent role '{policy.role}' may only read via '{tool_name}', not write"
                )
            return tool_fn(*args, **kwargs)

        return wrapper
    return decorator

# Usage: define role policies at agent initialization
RESEARCHER_POLICY = AgentPolicy(
    role="researcher",
    allowed_tools={"web_search", "read_knowledge_base"},
    read_only_tools={"read_knowledge_base"},
)

# Wrap tools at the agent boundary
@enforce_policy(RESEARCHER_POLICY)
def read_knowledge_base(query: str, write: bool = False) -> str:
    # Actual implementation
    ...

This approach has two advantages. Policy violations raise exceptions immediately and are logged before any side effects occur. And because the policy evaluator runs in the same process as the agent, there is no network hop and no meaningful latency cost.

Role definitions should be explicit and minimal. A researcher agent gets read access to external sources. A drafter gets read access to research outputs and write access to the draft workspace. A reviewer gets read access to draft outputs and write access to a review workspace. No role should have access to resources it doesn't need for its specific pipeline stage.

3. Audit Logging: Correlate Everything Back to the Origin

Without end-to-end logging, you cannot investigate security incidents, understand cascading failures, or prove that your pipeline behaved correctly for a given request. The requirement is not just logging individual agent actions but correlating every action back to the top-level request that triggered it.

The structure that works in production looks like this:

import uuid  # For generating request_id values: str(uuid.uuid4())
import time
import json
from dataclasses import dataclass, field, asdict
from typing import Optional

@dataclass
class AgentActionLog:
    request_id: str          # Top-level user request; shared across all agents
    agent_id: str            # Which agent performed this action
    agent_role: str          # Role at time of action
    action_type: str         # "tool_call", "handoff", "policy_check", etc.
    action_detail: dict      # Tool name, args (sanitized), target agent for handoffs
    timestamp: float = field(default_factory=time.time)
    duration_ms: Optional[float] = None
    result_status: str = "pending"   # "ok", "denied", "error"
    result_summary: Optional[str] = None

    def to_json(self) -> str:
        return json.dumps(asdict(self))

def log_action(logger, request_id: str, agent_id: str, agent_role: str,
               action_type: str, action_detail: dict) -> AgentActionLog:
    entry = AgentActionLog(
        request_id=request_id,
        agent_id=agent_id,
        agent_role=agent_role,
        action_type=action_type,
        action_detail=action_detail,
    )
    logger.info(entry.to_json())
    return entry

The request_id is the critical field. When a single user request fans out across a researcher, drafter, and reviewer agent, every action from every agent should carry the same request_id. This lets you reconstruct the full causal chain of any incident with a single query:

SELECT agent_id, agent_role, action_type, action_detail, result_status, timestamp
FROM agent_audit_log
WHERE request_id = '3f8a2c1d-...'
ORDER BY timestamp ASC;

Logs should be append-only and stored outside the reach of individual agents. An agent that can modify its own audit log is a serious security control failure.


Why Security Overhead Is a Red Herring

The most common objection to per-action security controls in multi-agent pipelines is latency. If every tool call requires a policy check, won't that slow everything down?

The data says no. Policy evaluation overhead for per-action checks runs between 0.2 and 4.5 milliseconds using modern policy engines like Open Policy Agent. Agent work itself (model inference, tool execution, API calls) runs in the range of tens to hundreds of milliseconds. The policy check adds well under 5 percent overhead in typical workloads.

The actual source of latency in secure multi-agent systems is architectural, not computational: centralized policy decision points that require every agent to make a network round-trip to a policy server before each action. Co-locating policy evaluation at the agent edge eliminates that round-trip entirely.

Two additional techniques help keep overhead minimal:

Decision caching. If an agent performs the same tool call repeatedly with the same parameters in the same session, cache the policy decision after the first evaluation. Invalidate the cache when role assignments or policy definitions change, which is infrequent in production.

Constraint-based pre-authorization. Rather than evaluating each action individually, evaluate a set of constraints at agent initialization and issue a short-lived capability token that covers all permitted actions for that session. Each tool call then validates the token locally without a policy lookup. This trades fine-grained per-action control for lower overhead and is appropriate for well-understood, stable workflows.

"We can't add security because it'll slow us down" is almost always a claim about a specific architectural choice (centralized policy servers), not about security overhead in general.


Production Readiness Checklist

The following controls represent a reasonable baseline for any multi-agent system in production, ordered roughly by impact per implementation cost.

Isolation - [ ] Each agent runs in an isolated execution environment (container or microVM) - [ ] Network egress from each agent is restricted to an explicit allowlist - [ ] Agents cannot directly access each other's filesystems or memory

Authorization - [ ] Each agent role has an explicit, documented permission set - [ ] Permissions are enforced at runtime on each tool invocation, not assumed at startup - [ ] Agent handoffs pass only the specific data the receiving agent needs, not the full conversation history

Communication boundaries - [ ] Every agent-to-agent message is validated against an explicit schema before it is acted on - [ ] Source authentication is performed on inter-agent messages - [ ] Agents cannot invoke other agents directly; invocations are routed through the orchestrator

Audit and observability - [ ] Every tool invocation is logged with a correlation ID tied to the originating user request - [ ] Every agent-to-agent handoff is logged, including the data transferred - [ ] Policy denial events are logged with enough context to diagnose the cause - [ ] Logs are written to storage that individual agents cannot modify or delete

Detection - [ ] Behavioral baselines exist for each agent role (expected tool call patterns, data volumes) - [ ] Anomalous correlations between agent behaviors (possible collusion signal) trigger alerts - [ ] Policy violations by the same agent ID within a session trigger escalating review


Conclusion

Multi-agent security is not a harder version of single-agent security. It is a different problem: the threat model shifts from defending a perimeter to managing trust across internal boundaries.

The practical path forward has three steps. First, treat every agent-to-agent communication as a trust boundary and validate data at every handoff. Second, enforce least privilege at runtime using role-based policies co-located with each agent. Third, instrument every action with correlated audit logs so you can reconstruct what happened when something goes wrong.

None of these controls requires sacrificing orchestration performance. The overhead of per-action policy checks is measured in milliseconds. The architectural choices that actually create bottlenecks (centralized policy servers, synchronous cross-agent blocking calls) are optional, not inherent to secure design.

The teams that get this right now, before multi-agent deployment becomes universal, will have a meaningful advantage when regulators, customers, and incident response teams start asking harder questions.

Subscribe to Marvin G6 | Engineering, AI & Automation

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe