Skip to content
allsrc.dev
Go back

Pattern 9: Sandboxed Execution

Sandboxed Execution is an architectural pattern that restricts and evaluates model-generated code or commands against strict workspace, network, and execution policies within an isolated runtime environment to prevent unauthorized operations. Evaluate every agent-directed command against an execution policy before anything runs — the runtime enforces the boundary, not the prompt.

Understanding the Code Execution Risk

An agent that can write code can also run it. An agent that can run code has, depending on the execution environment, access to the filesystem, network, environment variables, and other processes on the same host.

The agent’s instructions say: generate and test a solution. The instructions do not say: read environment variables, make outbound HTTP calls, or write outside the project. But none of those are prevented by instructions — they are prevented only by what the execution environment allows. Code execution is not just “run tests.” It can become file access, data exfiltration, dependency installation, network calls, process control, or production mutation, and the risk increases when the code was generated from untrusted context.

How to Implement Sandboxed Execution

Sandboxed Execution constrains what generated or agent-directed code can actually do, independent of what the agent is asked to do. Do not run arbitrary shell commands by default — use a policy decision before any operation that affects files, network, external systems, or the host environment.

flowchart TD
  A[Command request] --> B{Inside workspace?}
  B -->|no| X[Deny]
  B -->|yes| C{Network allowed?}
  C -->|no and command touches network| X
  C -->|yes or no network| D{Approval required prefix?}
  D -->|yes| E[approval_required]
  D -->|no| F{Command allowlisted?}
  F -->|no| X
  F -->|yes| G[allow in disposable workspace]

In LangGraph, generated code passes a policy gate before it reaches the sandbox node that runs it inside the isolated ContainerRuntime. Disallowed code becomes a denial the model can react to; both the decision and the isolated execution are distinct nodes on the tool path:

def policy(state: AgentState) -> dict:
    code = state["messages"][-1].tool_calls[0]["args"]["code"]
    return {"allowed": not any(bad in code for bad in BLOCKED)}

def sandbox(state: AgentState) -> dict:
    call = state["messages"][-1].tool_calls[0]
    result = runtime.execute_code(call["args"]["code"])
    body = result.stdout if result.exit_code == 0 else f"error: {result.stderr}"
    return {"messages": [ToolMessage(content=body, tool_call_id=call["id"])]}

builder.add_conditional_edges("agent", route_agent, {"policy": "policy", END: END})
builder.add_conditional_edges("policy", route_policy, {"sandbox": "sandbox", "denied": "denied"})
builder.add_edge("sandbox", "agent")
builder.add_edge("denied", "agent")

Security Considerations for Sandbox Boundaries

Consider the following factors when you implement this pattern:

References



Previous Post
Pattern 8: Memory Isolation
Next Post
Pattern 10: Agent Evaluations