A sandbox computer use agent needs hard boundaries: the model can click, type, and run shell commands, but a mistake or prompt injection shouldn’t reach your host. This guide builds a reproducible Docker-based sandbox that runs a headless browser and a minimal command API, giving you a safe place to test agent loops. We’ll cover image definition, kernel-level restrictions, network isolation, and wiring an LLM control plane.
Step 1: Define a minimal container image
Start from a stripped Ubuntu base and install only what the agent requires. A full desktop image is unnecessary; Xvfb provides a virtual display for browser automation without a window manager.
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
python3.11 \
python3-pip \
xvfb \
libnss3 \
libnspr4 \
libatk1.0-0 \
libatk-bridge2.0-0 \
libcups2 \
libdrm2 \
libxkbcommon0 \
libxcomposite1 \
libxdamage1 \
libxfixes3 \
libxrandr2 \
libgbm1 \
&& rm -rf /var/lib/apt/lists/*
RUN pip3 install playwright openai fastapi uvicorn
RUN playwright install chromium
WORKDIR /agent
COPY agent_server.py /agent/agent_server.py
The sandbox computer use agent will execute inside this image. Keep the layer count low and avoid copying host secrets into the build context.
Step 2: Restrict syscalls with seccomp
Docker’s default seccomp profile blocks many dangerous calls, but a computer-use agent that drives a browser still gets more than it needs. Drop mount, reboot, and bpf explicitly.
{
"defaultAction": "SCMP_ACT_ERRNO",
"syscalls": [
{
"names": [
"read", "write", "close", "fork", "wait4", "execve",
"exit", "exit_group", "mmap", "munmap", "brk", "rt_sigaction",
"rt_sigprocmask", "sigreturn", "ioctl", "poll", "select",
"clock_gettime", "clone", "dup", "dup2", "pipe", "socket",
"connect", "accept", "sendto", "recvfrom", "bind", "listen"
],
"action": "SCMP_ACT_ALLOW"
}
]
}
Save this as seccomp-agent.json. The whitelist covers browser and Python runtime needs while rejecting privilege escalation vectors.
Step 3: Run with resource limits and user namespaces
Launch the container with hard caps. Memory and CPU limits prevent a runaway agent loop from starving the host. User namespaces map the container root to an unprivileged host UID.
docker run -d \
--name agent-sandbox \
--memory=512m \
--cpus=1.0 \
--security-opt no-new-privileges \
--security-opt seccomp=seccomp-agent.json \
--cap-drop ALL \
--user 1000:1000 \
--network agent-internal \
agent-image:latest
Running the sandbox computer use agent with --cap-drop ALL removes even basic capabilities like CHOWN. If the agent needs to bind port 8000, it can because that’s above privileged range; no NET_BIND_SERVICE required.
Step 4: Isolate network egress
Create a dedicated bridge network with no external routing. The agent can talk to itself but cannot reach your LAN or the internet unless you explicitly proxy.
docker network create --internal agent-internal
If the agent must fetch public web pages for the task, run a separate squid proxy container on a different network and only allow the sandbox to connect to that proxy IP. Never attach the sandbox to the default bridge with internet access.
Step 5: Install agent control dependencies
The container runs a tiny FastAPI server that accepts JSON actions ({"action": "click", "selector": "#btn"}) and returns observations. This is the only interface the outer loop uses.
# agent_server.py
from fastapi import FastAPI
from playwright.sync_api import sync_playwright
import uvicorn
app = FastAPI()
browser = None
@app.on_event("startup")
def startup():
global browser
with sync_playwright() as p:
browser = p.chromium.launch()
@app.post("/act")
def act(cmd: dict):
page = browser.new_page()
if cmd["action"] == "goto":
page.goto(cmd["url"])
elif cmd["action"] == "title":
return {"result": page.title()}
page.close()
return {"result": "ok"}
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
Start it under Xvfb:
Xvfb :99 & DISPLAY=:99 python3 agent_server.py
Step 6: Wire the LLM control plane
The outer loop calls a model to decide the next action. Point the OpenAI client at an OpenAI-compatible gateway so you can swap models without code changes. n4n.ai exposes one endpoint fronting 240+ models and automatically falls back when a provider is rate-limited, which keeps the sandbox computer use agent resilient during long tasks.
import openai, requests
client = openai.OpenAI(
base_url="https://api.n4n.ai/v1",
api_key="YOUR_KEY"
)
def decide(action_history):
resp = client.chat.completions.create(
model="anthropic/claude-3.5-sonnet",
messages=[
{"role": "system", "content": "You control a browser. Return JSON actions."},
{"role": "user", "content": str(action_history)}
]
)
return resp.choices[0].message.content
The gateway honors client routing directives and forwards provider cache-control hints, so repeated context across agent steps can hit provider prompt caches.
Step 7: Verify the sandbox with a test task
Run a scripted task: navigate to a local test page, extract the title, and write a marker file inside the container. Then confirm the host is untouched.
# test_run.py
import requests, json
r = requests.post("http://localhost:8000/act", json={"action":"goto","url":"http://example.com"})
print(r.json())
r = requests.post("http://localhost:8000/act", json={"action":"title"})
print("TITLE:", r.json()["result"])
Execute from the host against the forwarded port (map -p 8000:8000 only for testing, remove for production). Verify success by checking that /agent/marker.txt exists inside the container but no new files appear in your host home directory:
docker exec agent-sandbox ls /agent
ls ~ | grep marker.txt && echo "LEAK" || echo "HOST CLEAN"
If the container crashes on memory limit, dmesg on host shows OOM kill—expected under constraint. The sandbox computer use agent is now isolated: it cannot spawn privileged processes, cannot reach the network directly, and cannot write outside its mount.
Hardening notes
- Mount
/agentastmpfsto avoid disk persistence across runs. - Use
--read-onlyroot filesystem and a writable scratch volume. - Rotate the LLM API key per sandbox instance; per-token metering on the gateway lets you attribute cost to each agent session.
- For browser agents, disable WebRTC and geolocation via Playwright context to reduce data leakage surface.
Following these steps gives you a repeatable, auditable environment to run untrusted model-driven workflows without betting your infrastructure on their correctness.