cloudflare-sandbox — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited cloudflare-sandbox (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Status: Production Ready (Open Beta) Last Updated: 2025-10-29 Dependencies: cloudflare-worker-base, cloudflare-durable-objects (recommended for understanding) Latest Versions: @cloudflare/[email protected], Docker image: cloudflare/sandbox:0.4.12
npm install @cloudflare/sandbox@latestwrangler.jsonc:
{
"name": "my-sandbox-worker",
"main": "src/index.ts",
"compatibility_flags": ["nodejs_compat"],
"containers": [{
"class_name": "Sandbox",
"image": "cloudflare/sandbox:0.4.12",
"instance_type": "lite"
}],
"durable_objects": {
"bindings": [{
"class_name": "Sandbox",
"name": "Sandbox"
}]
},
"migrations": [{
"tag": "v1",
"new_sqlite_classes": ["Sandbox"]
}]
}Why this matters:
nodejs_compat enables Node.js APIs required by SDKcontainers defines the Ubuntu container imagedurable_objects binding enables persistent routingmigrations registers the Sandbox classimport { getSandbox, type Sandbox } from '@cloudflare/sandbox';
export { Sandbox } from '@cloudflare/sandbox';
type Env = {
Sandbox: DurableObjectNamespace<Sandbox>;
};
export default {
async fetch(request: Request, env: Env): Promise<Response> {
// Get sandbox instance (creates if doesn't exist)
const sandbox = getSandbox(env.Sandbox, 'my-first-sandbox');
// Execute Python code
const result = await sandbox.exec('python3 -c "print(2 + 2)"');
return Response.json({
output: result.stdout,
success: result.success,
exitCode: result.exitCode
});
}
};CRITICAL:
{ Sandbox } from @cloudflare/sandbox in your Workernpm run deploy
curl https://your-worker.workers.devExpected output:
{
"output": "4\n",
"success": true,
"exitCode": 0
}┌─────────────────────────────────────────┐
│ Your Worker (Layer 1) │
│ - Handles HTTP requests │
│ - Calls getSandbox() │
│ - Uses sandbox.exec(), writeFile(), etc│
└──────────────┬──────────────────────────┘
│ RPC via Durable Object
┌──────────────▼──────────────────────────┐
│ Durable Object (Layer 2) │
│ - Routes by sandbox ID │
│ - Maintains persistent identity │
│ - Geographic stickiness │
└──────────────┬──────────────────────────┘
│ Container API
┌──────────────▼──────────────────────────┐
│ Ubuntu Container (Layer 3) │
│ - Full Linux environment │
│ - Python 3.11, Node 20, Git, etc. │
│ - Filesystem: /workspace, /tmp, /home │
│ - Process isolation (VM-based) │
└─────────────────────────────────────────┘Key Insight: Workers handle API logic (fast), Durable Objects route requests (persistent identity), Containers execute code (full capabilities).
┌─────────┐ First request ┌────────┐ ~10 min idle ┌──────┐
│ Not │ ───────────────>│ Active │ ─────────────> │ Idle │
│ Created │ │ │ │ │
└─────────┘ └───┬────┘ └──┬───┘
│ ^ │
│ │ New request │
│ └──────────────────────┘
│ │
▼ ▼
Files persist ALL FILES DELETED
Processes run ALL PROCESSES KILLED
State maintained ALL STATE RESETWhile Container is Active (~10 min after last request):
/workspace, /tmp, /home persistWhen Container Goes Idle (after inactivity):
This is NOT like a traditional server. Sandboxes are ephemeral by design.
For Important Data: Use external storage
// Save to R2 before container goes idle
await sandbox.writeFile('/workspace/data.txt', content);
const fileData = await sandbox.readFile('/workspace/data.txt');
await env.R2.put('backup/data.txt', fileData);
// Restore on next request
const restored = await env.R2.get('backup/data.txt');
if (restored) {
await sandbox.writeFile('/workspace/data.txt', await restored.text());
}For Build Artifacts: Accept ephemerality or use caching
// Check if setup needed (handles cold starts)
const exists = await sandbox.readdir('/workspace/project').catch(() => null);
if (!exists) {
await sandbox.gitCheckout(repoUrl, '/workspace/project');
await sandbox.exec('npm install', { cwd: '/workspace/project' });
}
// Now safe to run build
await sandbox.exec('npm run build', { cwd: '/workspace/project' });Sessions are bash shell contexts within one sandbox. Think terminal tabs.
Key Properties:
type ConversationState = {
sandboxId: string;
sessionId: string;
};
// First message: Create sandbox and session
const sandboxId = `user-${userId}`;
const sandbox = getSandbox(env.Sandbox, sandboxId);
const sessionId = await sandbox.createSession();
// Store in conversation state (database, KV, etc.)
await env.KV.put(`conversation:${conversationId}`, JSON.stringify({
sandboxId,
sessionId
}));
// Later messages: Reuse same session
const state = await env.KV.get(`conversation:${conversationId}`);
const { sandboxId, sessionId } = JSON.parse(state);
const sandbox = getSandbox(env.Sandbox, sandboxId);
// Commands run in same context
await sandbox.exec('cd /workspace/project', { session: sessionId });
await sandbox.exec('ls -la', { session: sessionId }); // Still in /workspace/project
await sandbox.exec('git status', { session: sessionId }); // Still in /workspace/project// ❌ WRONG: Each command runs in separate session
await sandbox.exec('cd /workspace/project');
await sandbox.exec('ls'); // NOT in /workspace/project (different session)const session1 = await sandbox.createSession();
const session2 = await sandbox.createSession();
// Run different tasks simultaneously
await Promise.all([
sandbox.exec('python train_model.py', { session: session1 }),
sandbox.exec('node generate_reports.js', { session: session2 })
]);const sandbox = getSandbox(env.Sandbox, `user-${userId}`);Pros: User's work persists while actively using (10 min idle time) Cons: Geographic lock-in (first request determines location) Use Cases: Interactive notebooks, IDEs, persistent workspaces
const sandboxId = `session-${Date.now()}-${crypto.randomUUID()}`;
const sandbox = getSandbox(env.Sandbox, sandboxId);
// Always destroy after use
await sandbox.destroy();Pros: Clean environment, no state pollution Cons: No persistence between requests Use Cases: One-shot code execution, CI/CD, testing
const sandbox = getSandbox(env.Sandbox, `build-${repoName}-${commitSha}`);Pros: Reproducible, debuggable, cacheable Cons: Need explicit cleanup strategy Use Cases: Build systems, data pipelines, automated workflows
import { getSandbox } from '@cloudflare/sandbox';
const sandbox = getSandbox(env.Sandbox, 'unique-sandbox-id');
// Creates new sandbox if doesn't exist, or gets existing one// Basic execution
const result = await sandbox.exec('python3 script.py');
console.log(result.stdout); // Standard output
console.log(result.stderr); // Standard error
console.log(result.exitCode); // Exit code (0 = success)
console.log(result.success); // Boolean (exitCode === 0)
// With options
const result = await sandbox.exec('npm install', {
cwd: '/workspace/project', // Working directory
timeout: 120000, // Timeout in ms (default: 30s)
session: sessionId, // Session ID
env: { NODE_ENV: 'production' } // Environment variables
});
// Always check exit codes!
if (!result.success) {
throw new Error(`Command failed: ${result.stderr}`);
}// Write file
await sandbox.writeFile('/workspace/data.txt', 'content');
// Read file
const content = await sandbox.readFile('/workspace/data.txt');
// Create directory
await sandbox.mkdir('/workspace/project', { recursive: true });
// List directory
const files = await sandbox.readdir('/workspace');
console.log(files); // ['data.txt', 'project']
// Delete file/directory
await sandbox.rm('/workspace/data.txt');
await sandbox.rm('/workspace/project', { recursive: true });// Clone repository (optimized, faster than exec('git clone'))
await sandbox.gitCheckout(
'https://github.com/user/repo',
'/workspace/repo'
);
// Now use standard git commands
await sandbox.exec('git status', { cwd: '/workspace/repo' });
await sandbox.exec('git diff', { cwd: '/workspace/repo' });// Create Python context
const ctx = await sandbox.createCodeContext({ language: 'python' });
// Execute code - last expression auto-returned
const result = await sandbox.runCode(`
import pandas as pd
df = pd.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6]})
df['a'].sum() # This value is automatically returned
`, { context: ctx });
console.log(result.results[0].text); // "6"
console.log(result.logs); // Output from print() statements
console.log(result.error); // Any errors// Start long-running process
const proc = await sandbox.spawn('python server.py');
console.log(proc.pid);
// Check if still running
const running = await sandbox.isProcessRunning(proc.pid);
// Kill process
await sandbox.killProcess(proc.pid);// Destroy sandbox permanently
await sandbox.destroy();
// All files deleted, container removed, cannot be recovered✅ Check exit codes - if (!result.success) { handle error } ✅ Use sessions for multi-step workflows - Preserve working directory ✅ Handle cold starts - Check if files exist before assuming they're there ✅ Set timeouts - Prevent hanging on long operations ✅ Destroy ephemeral sandboxes - Cleanup temp/session-based sandboxes ✅ Use external storage for persistence - R2/KV/D1 for important data ✅ Validate user input - Sanitize before exec() to prevent command injection ✅ Export Sandbox class - export { Sandbox } from '@cloudflare/sandbox'
❌ Assume files persist after idle - Container resets after ~10 min ❌ Ignore exit codes - Always check result.success or result.exitCode ❌ Chain commands without sessions - cd /dir then ls won't work ❌ Execute unsanitized user input - Use code interpreter or validate thoroughly ❌ Forget nodejs_compat flag - Required in wrangler.jsonc ❌ Skip migrations - Durable Objects need migration entries ❌ Use .workers.dev for preview URLs - Need custom domain ❌ Create unlimited sandboxes - Destroy ephemeral ones to avoid leaks
This skill prevents 10 documented issues:
Error: ReferenceError: fetch is not defined or Buffer is not defined Source: https://developers.cloudflare.com/sandbox/get-started/ Why It Happens: SDK requires Node.js APIs not available in standard Workers Prevention: Add "compatibility_flags": ["nodejs_compat"] to wrangler.jsonc
Error: Error: Class 'Sandbox' not found Source: https://developers.cloudflare.com/durable-objects/ Why It Happens: Durable Objects must be registered via migrations Prevention: Include migrations array in wrangler.jsonc
Error: Files disappear after inactivity Source: https://developers.cloudflare.com/sandbox/concepts/sandboxes/ Why It Happens: Containers go idle after ~10 min, all state reset Prevention: Use external storage (R2/KV) or check existence on each request
Error: Commands execute in wrong directory Source: https://developers.cloudflare.com/sandbox/concepts/sessions/ Why It Happens: Each exec() uses new session unless explicitly specified Prevention: Create session with createSession(), pass to all related commands
Error: Assuming command succeeded when it failed Source: Shell best practices Why It Happens: Not checking result.success or result.exitCode Prevention: Always check: if (!result.success) throw new Error(result.stderr)
Error: Commands fail because dependencies aren't installed Source: https://developers.cloudflare.com/sandbox/concepts/sandboxes/ Why It Happens: Container resets after idle period Prevention: Check if setup needed before running commands
Error: Failed to build container during local development Source: https://developers.cloudflare.com/sandbox/get-started/ Why It Happens: Local dev requires Docker daemon Prevention: Ensure Docker Desktop is running before npm run dev
Error: API methods not available or behaving unexpectedly Source: GitHub issues Why It Happens: npm package version doesn't match Docker image version Prevention: Keep @cloudflare/sandbox package and cloudflare/sandbox image in sync
Error: Resource exhaustion, unexpected costs Source: Resource management best practices Why It Happens: Creating sandboxes without destroying them Prevention: await sandbox.destroy() in finally block for temp sandboxes
Error: Security breach from unsanitized user input Source: Security best practices Why It Happens: Passing user input directly to exec() Prevention: Use code interpreter API or validate/sanitize input thoroughly
{
"name": "my-sandbox-app",
"main": "src/index.ts",
"compatibility_date": "2025-10-29",
"compatibility_flags": ["nodejs_compat"],
"containers": [{
"class_name": "Sandbox",
"image": "cloudflare/sandbox:0.4.12",
"instance_type": "lite"
}],
"durable_objects": {
"bindings": [{
"class_name": "Sandbox",
"name": "Sandbox"
}]
},
"migrations": [{
"tag": "v1",
"new_sqlite_classes": ["Sandbox"]
}],
"env": {
"ANTHROPIC_API_KEY": {
"description": "Optional: For AI features"
}
},
"observability": {
"enabled": true
}
}Why these settings:
nodejs_compat: Required for SDK to workcontainers.image: Specific version ensures consistencyinstance_type: "lite": Smallest instance, upgrade to "large" for more resourcesmigrations: Registers Sandbox Durable Object classobservability: Enable logging for debuggingOnly needed if extending base image:
FROM cloudflare/sandbox:0.4.12
# Install additional tools
RUN apt-get update && apt-get install -y \
ffmpeg \
imagemagick \
&& rm -rf /var/lib/apt/lists/*
# Expose port for preview URLs (local dev only)
EXPOSE 8080export default {
async fetch(request: Request, env: Env) {
const { code, language } = await request.json();
// Create ephemeral sandbox
const sandboxId = `exec-${Date.now()}-${crypto.randomUUID()}`;
const sandbox = getSandbox(env.Sandbox, sandboxId);
try {
// Create code context
const ctx = await sandbox.createCodeContext({ language });
// Execute code safely
const result = await sandbox.runCode(code, {
context: ctx,
timeout: 10000
});
return Response.json({
result: result.results?.[0]?.text,
logs: result.logs,
error: result.error
});
} finally {
// Always cleanup
await sandbox.destroy();
}
}
};When to use: API endpoints for code execution, code playgrounds, learning platforms
export default {
async fetch(request: Request, env: Env) {
const userId = request.headers.get('X-User-ID');
const { command, sessionId: existingSession } = await request.json();
// User-specific sandbox (persists while active)
const sandbox = getSandbox(env.Sandbox, `user-${userId}`);
// Get or create session
let sessionId = existingSession;
if (!sessionId) {
sessionId = await sandbox.createSession();
}
// Execute command in persistent context
const result = await sandbox.exec(command, {
session: sessionId,
timeout: 30000
});
return Response.json({
sessionId, // Return for next request
output: result.stdout,
error: result.stderr,
success: result.success
});
}
};When to use: Interactive coding environments, notebooks, IDEs, development workspaces
async function runBuild(repoUrl: string, commit: string, env: Env) {
const sandboxId = `build-${repoUrl.split('/').pop()}-${commit}`;
const sandbox = getSandbox(env.Sandbox, sandboxId);
try {
// Clone repository
await sandbox.gitCheckout(repoUrl, '/workspace/repo');
// Checkout specific commit
await sandbox.exec(`git checkout ${commit}`, {
cwd: '/workspace/repo'
});
// Install dependencies
const install = await sandbox.exec('npm install', {
cwd: '/workspace/repo',
timeout: 180000 // 3 minutes
});
if (!install.success) {
throw new Error(`Install failed: ${install.stderr}`);
}
// Run build
const build = await sandbox.exec('npm run build', {
cwd: '/workspace/repo',
timeout: 300000 // 5 minutes
});
if (!build.success) {
throw new Error(`Build failed: ${build.stderr}`);
}
// Save artifacts to R2
const dist = await sandbox.exec('tar -czf dist.tar.gz dist', {
cwd: '/workspace/repo'
});
const artifact = await sandbox.readFile('/workspace/repo/dist.tar.gz');
await env.R2.put(`builds/${commit}.tar.gz`, artifact);
return { success: true, artifactKey: `builds/${commit}.tar.gz` };
} finally {
// Optional: Keep sandbox for debugging or destroy
// await sandbox.destroy();
}
}When to use: Build systems, testing pipelines, deployment automation
async function runClaudeCodeOnRepo(
repoUrl: string,
task: string,
env: Env
): Promise<{ diff: string; logs: string }> {
const sandboxId = `claude-${Date.now()}`;
const sandbox = getSandbox(env.Sandbox, sandboxId);
try {
// Clone repository
await sandbox.gitCheckout(repoUrl, '/workspace/repo');
// Run Claude Code CLI with secure environment injection
// ⚠️ SECURITY: Use env option instead of shell export to prevent key exposure in logs
const result = await sandbox.exec(
`claude -p "${task}" --permission-mode acceptEdits`,
{
cwd: '/workspace/repo',
timeout: 300000, // 5 minutes
env: {
ANTHROPIC_API_KEY: env.ANTHROPIC_API_KEY
}
}
);
// Get diff of changes
const diff = await sandbox.exec('git diff', {
cwd: '/workspace/repo'
});
return {
diff: diff.stdout,
logs: result.stdout
};
} finally {
await sandbox.destroy();
}
}When to use: Automated code refactoring, code generation, AI-powered development
setup-sandbox-binding.sh - Interactive wrangler.jsonc configurationtest-sandbox.ts - Validation script to test sandbox setupExample Usage:
# Setup wrangler config
./scripts/setup-sandbox-binding.sh
# Test sandbox
npx tsx scripts/test-sandbox.tsreferences/persistence-guide.md - Deep dive on container lifecycle and persistencereferences/session-management.md - Advanced session patterns and best practicesreferences/common-errors.md - Complete list of errors with solutionsreferences/naming-strategies.md - Choosing sandbox IDs for different use casesWhen Claude should load these:
persistence-guide.md when debugging state issues or cold startssession-management.md when building multi-step workflows or chat agentscommon-errors.md when encountering specific errorsnaming-strategies.md when designing sandbox architectureFirst request to a sandbox ID determines its geographic location (via Durable Objects).
For Global Apps:
// Option 1: Multiple sandboxes per user (better latency)
const region = request.cf?.colo || 'default';
const sandbox = getSandbox(env.Sandbox, `user-${userId}-${region}`);
// Option 2: Single sandbox (simpler, higher latency for distant users)
const sandbox = getSandbox(env.Sandbox, `user-${userId}`);async function safeSandboxExec(
sandbox: Sandbox,
cmd: string,
options?: any
) {
try {
const result = await sandbox.exec(cmd, {
...options,
timeout: options?.timeout || 30000
});
if (!result.success) {
console.error(`Command failed: ${cmd}`, {
exitCode: result.exitCode,
stderr: result.stderr
});
return {
success: false,
error: result.stderr,
exitCode: result.exitCode
};
}
return {
success: true,
output: result.stdout,
exitCode: 0
};
} catch (error) {
console.error(`Sandbox error:`, error);
return {
success: false,
error: error.message,
exitCode: -1
};
}
}// Input validation
function sanitizeCommand(input: string): string {
const dangerous = ['rm -rf', '$(', '`', '&&', '||', ';', '|'];
for (const pattern of dangerous) {
if (input.includes(pattern)) {
throw new Error(`Dangerous pattern detected: ${pattern}`);
}
}
return input;
}
// Use code interpreter instead of direct exec for untrusted code
async function executeUntrustedCode(code: string, sandbox: Sandbox) {
const ctx = await sandbox.createCodeContext({ language: 'python' });
return await sandbox.runCode(code, { context: ctx, timeout: 10000 });
}Required:
@cloudflare/[email protected] - Sandbox SDKwrangler@latest - Deployment CLIOptional:
@anthropic-ai/sdk - For AI features@cloudflare/workers-types - TypeScript types{
"dependencies": {
"@cloudflare/sandbox": "^0.4.12"
},
"devDependencies": {
"wrangler": "^3.80.0",
"@cloudflare/workers-types": "^4.20241106.0"
}
}Docker Image: cloudflare/sandbox:0.4.12
This skill is based on official Cloudflare tutorials:
Solution: Add migrations to wrangler.jsonc and ensure export { Sandbox } in Worker
Solution: Container goes idle after ~10 min. Use R2/KV for persistence or rebuild environment
Solution: Create session with createSession(), pass sessionId to all related commands
Solution: Ensure Docker Desktop is running before npm run dev
Solution: Add "compatibility_flags": ["nodejs_compat"] to wrangler.jsonc
npm install @cloudflare/sandbox@latestnodejs_compat to compatibility_flagsexport { Sandbox } from '@cloudflare/sandbox'Questions? Issues?
references/common-errors.md for specific error solutions~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.