Skip to content
Cloudflare Docs

Security model

The Sandbox SDK is built on Containers, which run each sandbox in its own VM for strong isolation.

Container isolation

Each sandbox runs in a separate VM, providing complete isolation:

  • Filesystem isolation - Sandboxes cannot access other sandboxes' files
  • Process isolation - Processes in one sandbox cannot see or affect others
  • Network isolation - Sandboxes have separate network stacks
  • Resource limits - CPU, memory, and disk quotas are enforced per sandbox

For complete security details about the underlying container platform, see Containers architecture.

Within a sandbox

All code within a single sandbox shares resources:

  • Filesystem - All processes see the same files
  • Processes - All sessions can see all processes
  • Network - Processes can communicate via localhost

For complete isolation, use separate sandboxes per user:

TypeScript
// Good - Each user in separate sandbox
const userSandbox = getSandbox(env.Sandbox, `user-${userId}`);
// Bad - Users sharing one sandbox
const shared = getSandbox(env.Sandbox, 'shared');
// Users can read each other's files!

Input validation

Command injection

Always validate user input before using it in commands:

TypeScript
// Dangerous - user input directly in command
const filename = userInput;
await sandbox.exec(`cat ${filename}`);
// User could input: "file.txt; rm -rf /"
// Safe - validate input
const filename = userInput.replace(/[^a-zA-Z0-9._-]/g, '');
await sandbox.exec(`cat ${filename}`);
// Better - use file API
await sandbox.writeFile('/tmp/input', userInput);
await sandbox.exec('cat /tmp/input');

Authentication

Sandbox access

Sandbox IDs provide basic access control but aren't cryptographically secure. Add application-level authentication:

TypeScript
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const userId = await authenticate(request);
if (!userId) {
return new Response('Unauthorized', { status: 401 });
}
// User can only access their sandbox
const sandbox = getSandbox(env.Sandbox, userId);
return Response.json({ authorized: true });
}
};

Preview URLs

Preview URLs are public. Add authentication in your service:

Python
from flask import Flask, request, abort
import os
app = Flask(__name__)
def check_auth():
token = request.headers.get('Authorization')
if token != f"Bearer {os.environ['AUTH_TOKEN']}":
abort(401)
@app.route('/api/data')
def get_data():
check_auth()
return {'data': 'protected'}

Secrets management

Use environment variables, not hardcoded secrets:

TypeScript
// Bad - hardcoded in file
await sandbox.writeFile('/workspace/config.js', `
const API_KEY = 'sk_live_abc123';
`);
// Good - use environment variables
await sandbox.startProcess('node app.js', {
env: {
API_KEY: env.API_KEY, // From Worker environment binding
}
});

Clean up temporary sensitive data:

TypeScript
try {
await sandbox.writeFile('/tmp/sensitive.txt', secretData);
await sandbox.exec('python process.py /tmp/sensitive.txt');
} finally {
await sandbox.deleteFile('/tmp/sensitive.txt');
}

What the SDK protects against

  • Sandbox-to-sandbox access (VM isolation)
  • Resource exhaustion (enforced quotas)
  • Container escapes (VM-based isolation)

What you must implement

  • Authentication and authorization
  • Input validation and sanitization
  • Rate limiting
  • Application-level security (SQL injection, XSS, etc.)

Best practices

Use separate sandboxes for isolation:

TypeScript
const sandbox = getSandbox(env.Sandbox, `user-${userId}`);

Validate all inputs:

TypeScript
const safe = input.replace(/[^a-zA-Z0-9._-]/g, '');
await sandbox.exec(`command ${safe}`);

Use environment variables for secrets:

TypeScript
await sandbox.startProcess('node app.js', {
env: { API_KEY: env.API_KEY }
});

Clean up temporary resources:

TypeScript
try {
const sandbox = getSandbox(env.Sandbox, sessionId);
await sandbox.exec('npm test');
} finally {
await sandbox.destroy();
}