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:
- Sandbox boundaries. Workspace root, command allowlist, network mode, timeout, environment variable exposure, filesystem write scope, a risky-command approval list, and a disposable runtime or container.
- Ephemeral containers. Disposable containers destroyed after each execution so nothing persists across calls.
- Network egress. Closed by default or limited to a strict allowlist.
- Resource quotas. Hard compute, memory, and timeout quotas so a generated infinite loop cannot exhaust a worker node.
- Improper Output Handling. This is the leading cause of Remote Code Execution (RCE). Systems that trust LLM output implicitly are highly susceptible to RCE, as they effectively treat potentially malicious model-generated content as trusted commands.
- Friction is the point. Every useful command needs an explicit policy. A sandbox that silently permits everything is only a logging wrapper.