docker-sandbox
by shriramkv
README.md
# docker-agent-sandbox
**Let your AI agent run code. Just not on your machine.**
Coding agents are at their most useful when they can execute what they write: test a function, crunch a CSV, check a regex. They are at their most dangerous for exactly the same reason. One hallucinated `rm -rf`, one prompt-injected `curl | sh`, and the agent is acting with *your* permissions on *your* laptop.
`docker-agent-sandbox` (`das`) gives agents a safe place to run code. Every execution gets a fresh, throwaway Docker container with:
| Control | Setting | What it stops |
|---|---|---|
| Network | `--network none` | Data exfiltration, downloading payloads, calling home |
| Filesystem | `--read-only` root, in-memory `/tmp` (`noexec`) | Tampering with the image, dropping binaries |
| Host access | Code streamed on stdin, **no volume mounts** | Reading your SSH keys, `.env` files, source tree |
| Privileges | `--cap-drop ALL`, `no-new-privileges`, user `65534` (nobody) | Privilege escalation, `setuid`, raw sockets |
| CPU / memory | `--cpus 0.5`, `--memory 256m` (no swap) | Crypto-mining, memory exhaustion |
| Processes | `--pids-limit 64` | Fork bombs |
| Time | Hard timeout, then `docker kill` | Infinite loops, hung jobs |
| Output | Capped (64 KB by default) | Flooding the agent's context window |
| Lifetime | `--rm` | Leftover state between runs |
It ships as a **CLI** for humans and CI, and an **MCP server** so any MCP-capable agent (Claude Desktop, goose, VS Code, Cursor, or anything behind the Docker MCP Gateway) gets a `run_code` tool that can only ever execute inside that box.
No runtime dependencies beyond Python 3.10+ and the Docker CLI.
## Quick start
```bash
pip install git+https://github.com/shriramkv/docker-agent-sandbox
docker pull python:3.12-slim
echo 'print(sum(range(10)))' | das run -
# 45
# [sandbox] success in 0.41s
```
Now try something an agent should never be allowed to do:
```bash
das run examples/escape_attempts.py
```
```
running as uid=65534 gid=65534
[BLOCKED ] outbound network connection: OSError
[BLOCKED ] write to /etc/passwd: PermissionError
[BLOCKED ] drop a file in /usr/local/bin: OSError
[BLOCKED ] setuid(0): PermissionError
[BLOCKED ] effective capabilities = 0000000000000000
[OK ] /tmp is writable scratch space (in-memory, discarded after the run)
[sandbox] success in 0.27s
```
## See exactly what runs
No magic. `das explain` prints the precise `docker run` command, which makes this a handy teaching tool for container hardening in its own right:
```bash
das explain
```
```
docker run \
--rm \
-i \
--name das-<random> \
--network none \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,nodev,mode=1777,size=32m \
--memory 256m \
--memory-swap 256m \
--cpus 0.5 \
--pids-limit 64 \
--cap-drop ALL \
--security-opt no-new-privileges \
--user 65534:65534 \
--workdir /tmp \
--env HOME=/tmp \
--env PYTHONDONTWRITEBYTECODE=1 \
--label docker-agent-sandbox=1 \
python:3.12-slim python -I -
```
## Use it from an agent (MCP)
```bash
das mcp --policy examples/strict-policy.json --audit-log ~/das-audit.jsonl
```
Claude Desktop (`claude_desktop_config.json`):
```json
{
"mcpServers": {
"docker-sandbox": {
"command": "das",
"args": ["mcp", "--policy", "/absolute/path/to/strict-policy.json"]
}
}
}
```
The server exposes one tool:
- **`run_code`**: `code` (required), `language` (`python`, `node` or `shell`), `timeout_seconds` (optional).
An agent may *shorten* the timeout but can never lengthen it beyond the server policy, and it cannot switch the network on. Those decisions belong to the human who started the server, not to the model. Results come back as readable text plus `structuredContent` (exit code, verdict, duration, truncation flag), and failures set `isError` so the agent can see and react to them.
## Policies
Defaults are strict. Loosen them deliberately, in a file you can review:
```json
{
"timeout_seconds": 5,
"memory_mb": 128,
"cpus": 0.25,
"pids_limit": 32,
"network": false,
"images": { "python": "python:3.12-slim" }
}
```
```bash
das policy --policy my-policy.json # print the effective policy
das run job.py --memory 512 --timeout 30 # per-run overrides
das run job.py --allow-network # explicit, and warned about
```
Policies are validated: running as root is refused, unknown keys are rejected, and every limit has a sane range.
## Audit trail
`--audit-log` appends one JSON line per execution: timestamp, language, image, verdict, exit code, duration and the policy limits in force. The code itself is stored **only as a SHA-256 hash**, so the log shows what ran and when without becoming a second copy of whatever the agent wrote.
```json
{"timestamp": "2026-11-03T13:05:12+00:00", "language": "python", "image": "python:3.12-slim", "code_sha256": "9f2c...", "exit_code": 0, "verdict": "success", "duration_seconds": 0.38, "timed_out": false, "truncated": false, "network": false, "memory_mb": 256, "cpus": 0.5}
```
## Use it as a library
```python
from docker_agent_sandbox import SandboxPolicy, run_code
result = run_code("print(2 ** 64)", policy=SandboxPolicy(timeout_seconds=3))
print(result.verdict, result.stdout)
```
## What this is not
A container is a strong boundary, not a perfect one; it shares the host kernel. For genuinely hostile, multi-tenant workloads, pair this with a stronger runtime such as gVisor (`--runtime runsc`) or a microVM, and keep the Docker daemon itself patched. Also note that `das` drives your local Docker CLI, so whoever can start the MCP server already has Docker access; the sandbox protects you from the *agent*, not from yourself.
## Development
```bash
pip install -e ".[dev]"
pytest -q # unit tests, no Docker needed (a fake docker CLI is used)
DAS_INTEGRATION_IMAGE=python:3.12-slim pytest -q -m integration # real containers
ruff check . && ruff format --check .
```
The integration suite proves each control against a real daemon: non-root user, network blocked, read-only root with writable `/tmp`, zero capabilities, fork bomb contained, memory hog killed (exit 137) and infinite loop timed out.
## Licence
MIT
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues