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 include randomly generated tokens. Anyone with the URL can access the service.

To revoke access, unexpose the port:

TypeScript
await sandbox.unexposePort(8080);
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');
}

Proxying external API requests

Passing credentials directly to a sandbox — via environment variables or files — means the sandbox process holds a live credential that any code running inside it can read. A Worker proxy removes that exposure by keeping credentials exclusively in the Worker and giving the sandbox a short-lived JWT instead.

The flow works as follows:

Sandbox (short-lived JWT) → Worker proxy (validates JWT, injects real credentials) → External API

The sandbox never sees the real credential. If the JWT is compromised, it expires after a short window and cannot be reused.

This pattern is useful when accessing GitHub for private repository operations, AI services, or object storage where you want to keep credentials out of the container entirely. Refer to Proxy requests to external APIs for a complete implementation.

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();
}