Security model
The Sandbox SDK is built on Containers, which run each sandbox in its own VM for strong 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.
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:
// Good - Each user in separate sandboxconst userSandbox = getSandbox(env.Sandbox, `user-${userId}`);
// Bad - Users sharing one sandboxconst shared = getSandbox(env.Sandbox, 'shared');// Users can read each other's files!Always validate user input before using it in commands:
// Dangerous - user input directly in commandconst filename = userInput;await sandbox.exec(`cat ${filename}`);// User could input: "file.txt; rm -rf /"
// Safe - validate inputconst filename = userInput.replace(/[^a-zA-Z0-9._-]/g, '');await sandbox.exec(`cat ${filename}`);
// Better - use file APIawait sandbox.writeFile('/tmp/input', userInput);await sandbox.exec('cat /tmp/input');Sandbox IDs provide basic access control but aren't cryptographically secure. Add application-level authentication:
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 include randomly generated tokens. Anyone with the URL can access the service.
To revoke access, unexpose the port:
await sandbox.unexposePort(8080);from flask import Flask, request, abortimport 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'}Use environment variables, not hardcoded secrets:
// Bad - hardcoded in fileawait sandbox.writeFile('/workspace/config.js', ` const API_KEY = 'sk_live_abc123';`);
// Good - use environment variablesawait sandbox.startProcess('node app.js', { env: { API_KEY: env.API_KEY, // From Worker environment binding }});Clean up temporary sensitive data:
try { await sandbox.writeFile('/tmp/sensitive.txt', secretData); await sandbox.exec('python process.py /tmp/sensitive.txt');} finally { await sandbox.deleteFile('/tmp/sensitive.txt');}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 APIThe 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.
- Sandbox-to-sandbox access (VM isolation)
- Resource exhaustion (enforced quotas)
- Container escapes (VM-based isolation)
- Authentication and authorization
- Input validation and sanitization
- Rate limiting
- Application-level security (SQL injection, XSS, etc.)
Use separate sandboxes for isolation:
const sandbox = getSandbox(env.Sandbox, `user-${userId}`);Validate all inputs:
const safe = input.replace(/[^a-zA-Z0-9._-]/g, '');await sandbox.exec(`command ${safe}`);Use environment variables for secrets:
await sandbox.startProcess('node app.js', { env: { API_KEY: env.API_KEY }});Clean up temporary resources:
try { const sandbox = getSandbox(env.Sandbox, sessionId); await sandbox.exec('npm test');} finally { await sandbox.destroy();}- Containers architecture - Underlying platform security
- Sandbox lifecycle - Resource management