Bước 1: Validate input Step 1: Input Validation ជំហាន 1: Validate input
- Chúng ta đang xây What we're building អ្វីដែលយើងកំពុងសង់
- Robust input sanitization that prevents command injection and path traversal attacks.
- Vì sao quan trọng Why this matters ហេតុអ្វីសំខាន់
- Even inside a sandbox, command injection can cause unexpected behavior, access unintended files, or consume resources.
The problem — direct interpolation is dangerous:
The problem — direct interpolation is dangerous:
The problem — direct interpolation is dangerous:
// DANGEROUS: User controls the path
const filename = req.body.filename;
await sandbox.exec(`cat /workspace/${filename}`);
// User sends: ../../etc/passwd → reads outside workspace!
// User sends: foo; rm -rf / → executes extra commands! The fix — validate before use:
The fix — validate before use:
The fix — validate before use:
function sanitizePath(input: string): string {
// Strip everything except safe characters
const safe = input.replace(/[^a-zA-Z0-9._-]/g, "");
// Block path traversal
if (safe.includes("..") || safe.startsWith("/")) {
throw new Error("Invalid path");
}
return safe;
}
// Now use it:
const filename = sanitizePath(req.body.filename);
await sandbox.exec(`cat /workspace/${filename}`); Even better — use file APIs instead of shell commands:
Even better — use file APIs instead of shell commands:
Even better — use file APIs instead of shell commands:
// Avoid shell interpolation entirely for file operations
const filename = sanitizePath(req.body.filename);
const file = await sandbox.readFile(`/workspace/${filename}`); Validate code input length and type:
Validate code input length and type:
Validate code input length and type:
if (url.pathname === "/code" && request.method === "POST") {
const { code, language } = (await request.json()) as {
code: unknown;
language: unknown;
};
// Type checks
if (typeof code !== "string") {
return Response.json({ error: "code must be a string" }, { status: 400 });
}
if (
typeof language !== "string" ||
!["javascript", "python"].includes(language)
) {
return Response.json(
{ error: "language must be javascript or python" },
{ status: 400 },
);
}
// Size limit — prevent huge inputs from consuming resources
if (code.length > 50_000) {
return Response.json(
{ error: "code too large (max 50KB)" },
{ status: 413 },
);
}
// ... proceed with execution
} Bước 2: Cô lập sandbox theo user Step 2: Per-User Sandbox Isolation ជំហាន 2: បំបែក sandbox តាម user
- Chúng ta đang xây What we're building អ្វីដែលយើងកំពុងសង់
- Every user gets their own separate sandbox with no shared state between them.
- Vì sao quan trọng Why this matters ហេតុអ្វីសំខាន់
- A shared sandbox would let User A read User B's files, see their environment variables, and interfere with their running processes.
The sandbox id passed to getSandbox() is the isolation boundary. Always tie it to the authenticated user:
The sandbox id passed to getSandbox() is the isolation boundary. Always tie it to the authenticated user:
The sandbox id passed to getSandbox() is the isolation boundary. Always tie it to the authenticated user:
// ✅ Good: each user has a completely isolated sandbox
const userId = await authenticateUser(request);
const sandbox = getSandbox(env.Sandbox, `user-${userId}`); // ❌ Bad: all users share one sandbox and each other's files
const sandbox = getSandbox(env.Sandbox, "shared"); For anonymous (unauthenticated) users, use a session ID:
For anonymous (unauthenticated) users, use a session ID:
For anonymous (unauthenticated) users, use a session ID:
function getSandboxForRequest(request: Request, env: Env) {
// Try to get a user ID from auth
const authHeader = request.headers.get("Authorization");
if (authHeader) {
const userId = validateToken(authHeader);
if (userId) return getSandbox(env.Sandbox, `user-${userId}`);
}
// Fall back to session-based isolation
const sessionId = request.headers.get("X-Session-ID") ?? crypto.randomUUID();
return getSandbox(env.Sandbox, `session-${sessionId}`);
} Bước 3: Quản lý biến môi trường Step 3: Environment Variable Management ជំហាន 3: គ្រប់គ្រងអថេរបរិស្ថាន
- Chúng ta đang xây What we're building អ្វីដែលយើងកំពុងសង់
- Secure patterns for passing configuration into sandbox commands at the right scope.
- Vì sao quan trọng Why this matters ហេតុអ្វីសំខាន់
- Scoping env vars to the narrowest context — per-command or per-session — reduces the risk of leaking config across unrelated executions.
Per-command: scoped to a single exec() or startProcess() call
Per-command: scoped to a single exec() or startProcess() call
Per-command: scoped to a single exec() or startProcess() call
await sandbox.exec("node app.js", {
env: {
NODE_ENV: "production",
FEATURE_FLAG: "beta",
REQUEST_ID: crypto.randomUUID(),
},
});
// These env vars are gone after this command finishes Per-context: scoped to a createCodeContext() for runCode() calls
Per-context: scoped to a createCodeContext() for runCode() calls
Per-context: scoped to a createCodeContext() for runCode() calls
const ctx = await sandbox.createCodeContext({
language: "javascript",
envVars: {
API_URL: "https://api.example.com",
LOG_LEVEL: "info",
},
});
// All runCode() calls on this context share the env vars
await sandbox.runCode("console.log(process.env.API_URL)", { context: ctx }); Session-level: shared across a group of related commands
Session-level: shared across a group of related commands
Session-level: shared across a group of related commands
const session = await sandbox.createSession({
env: {
DATABASE_URL: env.DATABASE_URL,
REDIS_URL: env.REDIS_URL,
},
});
// All session commands share the env vars
await session.exec("node migrate.js");
await session.exec("node seed.js");
await session.exec("node server.js"); Bước 4: Quản lý secret Step 4: Secret Management ជំហាន 4: គ្រប់គ្រង secret
- Chúng ta đang xây What we're building អ្វីដែលយើងកំពុងសង់
- A pattern where secrets stay in your Worker and are never exposed to sandbox code.
- Vì sao quan trọng Why this matters ហេតុអ្វីសំខាន់
- AI-generated or user-supplied code could read environment variables and exfiltrate secrets to an external server.
Never pass real secrets to the sandbox:
Never pass real secrets to the sandbox:
Never pass real secrets to the sandbox:
// ❌ NEVER DO THIS — the sandbox code can read STRIPE_KEY
const ctx = await sandbox.createCodeContext({
language: "javascript",
envVars: { STRIPE_KEY: env.STRIPE_KEY },
});
await sandbox.runCode(untrustedAiGeneratedCode, { context: ctx }); // Could steal the key! Instead, proxy API calls through your Worker. In this pattern, code running inside the sandbox calls your Worker’s proxy endpoint (e.g., fetch("https://your-worker.dev/proxy/stripe", ...)). The Worker validates the request, attaches authentication using secrets that never leave the Worker, and forwards it to the external API:
Instead, proxy API calls through your Worker. In this pattern, code running inside the sandbox calls your Worker’s proxy endpoint (e.g., fetch("https://your-worker.dev/proxy/stripe", ...)). The Worker validates the request, attaches authentication using secrets that never leave the Worker, and forwards it to the external API:
Instead, proxy API calls through your Worker. In this pattern, code running inside the sandbox calls your Worker’s proxy endpoint (e.g., fetch("https://your-worker.dev/proxy/stripe", ...)). The Worker validates the request, attaches authentication using secrets that never leave the Worker, and forwards it to the external API:
// ✅ The Worker holds the secret and makes authenticated calls on behalf of sandbox code
if (url.pathname === "/proxy/stripe" && request.method === "POST") {
const { endpoint, payload } = (await request.json()) as {
endpoint: string;
payload: unknown;
};
// Whitelist: only allow specific Stripe endpoints
const allowedEndpoints = [
"https://api.stripe.com/v1/payment_intents",
"https://api.stripe.com/v1/customers",
];
if (!allowedEndpoints.includes(endpoint)) {
return Response.json({ error: "Endpoint not allowed" }, { status: 403 });
}
// Worker makes the authenticated request — secret never leaves the Worker
const response = await fetch(endpoint, {
method: "POST",
headers: {
Authorization: `Bearer ${env.STRIPE_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
return response;
} Bước 5: Pattern xác thực Step 5: Authentication Pattern ជំហាន 5: Pattern ផ្ទៀងផ្ទាត់
- Chúng ta đang xây What we're building អ្វីដែលយើងកំពុងសង់
- A complete authentication flow that gates sandbox access to verified users.
- Vì sao quan trọng Why this matters ហេតុអ្វីសំខាន់
- Without authentication, anyone who discovers your Worker URL can execute arbitrary code in your sandboxes.
export default {
async fetch(request: Request, env: Env): Promise<Response> {
// Authenticate before doing anything else
const authHeader = request.headers.get("Authorization");
if (!authHeader?.startsWith("Bearer ")) {
return new Response("Unauthorized", {
status: 401,
headers: { "WWW-Authenticate": "Bearer" },
});
}
const token = authHeader.slice(7);
const userId = await validateToken(token, env);
if (!userId) {
return new Response("Invalid or expired token", { status: 401 });
}
// Each user gets their own isolated sandbox — they can only affect their own
const sandbox = getSandbox(env.Sandbox, `user-${userId}`);
const url = new URL(request.url);
if (url.pathname === "/code" && request.method === "POST") {
const { code } = (await request.json()) as { code: string };
const ctx = await sandbox.createCodeContext({ language: "javascript" });
const result = await sandbox.runCode(code, { context: ctx });
return Response.json({
success: result.code === 0,
stdout: result.logs.stdout,
error: result.error,
});
}
return new Response("Not found", { status: 404 });
},
};
async function validateToken(token: string, env: Env): Promise<string | null> {
// Implement your token strategy here:
// - JWT verification with a signing secret
// - KV lookup for session tokens
// - Workers Access service token validation
return null; // replace with real implementation
} Bước 6: Sandbox cô lập gì (và không cô lập gì) Step 6: What the Sandbox Isolates (and What It Doesn’t) ជំហាន 6: Sandbox បំបែកអ្វី (និងអ្វីដែលមិនបំបែក)
- Chúng ta đang xây What we're building អ្វីដែលយើងកំពុងសង់
- A clear mental model of the sandbox's isolation boundaries.
- Vì sao quan trọng Why this matters ហេតុអ្វីសំខាន់
- Knowing what is and isn't isolated helps you design your security model correctly.
What you must handle yourself
What you must handle yourself
What you must handle yourself
- User data separation (use per-user sandbox IDs)
- Preventing secret exposure (proxy pattern)
- Rate limiting (prevent resource abuse)
- Authentication (gate who can run code)
- Audit logging (track what was executed)
✅ Security patterns in place! Your sandbox applications are now ready for the final step: deploying to production.
✅ Security patterns in place! Your sandbox applications are now ready for the final step: deploying to production.
✅ Security patterns in place! Your sandbox applications are now ready for the final step: deploying to production.