@anvia/sandbox
Docker-backed sandbox sessions for running untrusted code.
@anvia/sandbox provides ephemeral Docker containers for running model-generated or untrusted code outside the host process. It also exposes sandbox operations as agent tools.
Install
pnpm add @anvia/sandboxDocker must be installed and running on the host machine.
Quick Start
import { DockerSandbox } from "@anvia/sandbox";
const sandbox = new DockerSandbox();
const session = await sandbox.createSession();
// Run a command
const result = await session.exec({ command: "node", args: ["-e", "console.log(42)"] });
console.log(result.stdout); // "42\n"
// Clean up
await session.destroy();Sandbox Configuration
type DockerSandboxOptions = {
image?: string; // Docker image (default: "node:22-bookworm")
pull?: "missing" | "always" | "never"; // pull policy (default: "missing")
workdir?: string; // workspace directory (default: "/workspace")
workspace?: SandboxWorkspaceOptions; // ephemeral or persistent volume
lifecycle?: SandboxLifecycleOptions; // ttl and idle cleanup
network?: boolean | "none" | "host" | string | { mode: boolean | "none" | "host" | string };
user?: string; // container user
dockerPath?: string; // path to docker CLI
labels?: Record<string, string>; // Docker container labels
limits?: SandboxLimits; // resource limits
security?: DockerSandboxSecurityOptions;
hooks?: SandboxHooks;
};// Custom image and limits
const sandbox = new DockerSandbox({
image: "python:3.12-slim",
limits: { timeoutMs: 30_000, maxFileBytes: 5_000_000, memoryMb: 512, cpus: 1 },
});
// Network access enabled
const sandbox = new DockerSandbox({
network: { mode: "host" },
});Presets
const nodeSandbox = DockerSandbox.node();
const pythonSandbox = DockerSandbox.python();
const denoSandbox = DockerSandbox.deno();Security Defaults
By default, sandboxes run with:
- Network access disabled
noNewPrivilegesenabled- All Linux capabilities dropped (
dropCapabilities: ["ALL"])
Override with the security option:
const sandbox = new DockerSandbox({
security: {
readonlyRootfs: false, // allow writes to root filesystem
noNewPrivileges: true,
dropCapabilities: ["ALL"],
},
});Sessions
Each session creates one Docker container and one Docker volume mounted at the workdir.
Creating Sessions
const session = await sandbox.createSession({
id: "my-session", // optional custom ID
workspace: { mode: "ephemeral" },
metadata: { userId: "123" }, // optional metadata
manifest: {
files: {
"main.ts": 'console.log("hello")',
"data.json": '{"key": "value"}',
},
directories: ["src", "tests"],
env: { API_KEY: "secret" },
},
});The manifest seeds the workspace with files and directories before any commands run.
Use a persistent workspace only when continuity is explicit:
const session = await sandbox.createSession({
workspace: {
mode: "persistent",
id: `user-${userId}`,
},
});Executing Commands
const result = await session.exec({
command: "node",
args: ["main.ts"],
cwd: "src", // working directory (relative to workdir)
env: { DEBUG: "true" }, // additional environment variables
timeoutMs: 10_000, // per-command timeout
input: "stdin data", // stdin input
signal: abortController.signal, // abort signal
onStdout: (chunk) => {}, // streaming stdout callback
onStderr: (chunk) => {}, // streaming stderr callback
});
console.log(result.stdout);
console.log(result.stderr);
console.log(result.exitCode);
console.log(result.durationMs);
console.log(result.timedOut);Stream command output with execStream(...):
for await (const event of session.execStream({ command: "npm", args: ["test"] })) {
if (event.type === "stdout" || event.type === "stderr") {
process.stdout.write(event.text);
}
}File Operations
// Write files
await session.writeTextFile("output.txt", "Hello, world!");
await session.writeFile("binary.bin", uint8Array);
// Read files
const content = await session.readTextFile("output.txt");
const bytes = await session.readFile("binary.bin");
// List files
const files = await session.listFiles("src");
// [{ path: "src/index.ts", type: "file", size: 1234 }]Cleanup
await session.destroy();This removes both the container and the Docker volume.
Agent Tools
Expose a sandbox session as agent tools:
import { createSandboxTools } from "@anvia/sandbox";
import { AgentBuilder } from "@anvia/core";
const session = await sandbox.createSession();
const tools = createSandboxTools(session, {
allow: ["exec_command", "read_file", "write_file", "list_files"],
exec: {
allowedCommands: ["node", "npm"],
maxTimeoutMs: 30_000,
},
});
const agent = new AgentBuilder("coder", model)
.instructions("Write and run code to solve problems.")
.tool(...tools)
.defaultMaxTurns(5)
.build();Available Tools
| Tool | Description |
|---|---|
exec_command | Run a shell command in the sandbox |
read_file | Read a file from the workspace |
write_file | Write a file to the workspace |
list_files | List files in a directory |
Tool Options
const tools = createSandboxTools(session, {
include: ["exec_command", "read_file"], // only include specific tools
execTimeoutMs: 30_000, // default timeout for exec_command
});Resource Limits
type SandboxLimits = {
timeoutMs?: number; // session-level timeout
maxOutputBytes?: number; // max stdout/stderr bytes
memoryMb?: number; // container memory limit
cpus?: number; // container CPU limit
pidsLimit?: number; // max processes
};Error Types
| Error | When |
|---|---|
SandboxDockerUnavailableError | Docker CLI not found |
SandboxDockerCommandError | Docker setup command failed |
SandboxSessionDestroyedError | Using a session after destroy() |
SandboxPathError | Path traversal outside workspace |
SandboxTimeoutError | Command exceeded timeout |
Related
- Sandbox Guide for setup walkthrough
- Sandbox Reference for full API types
- Sandbox Testing for testing patterns
