Sandbox SDK Sandbox SDK Sandbox SDK · Lab 6/7 Lab 6/7 Lab 6/7 · 25 phút · 25 min · 25 នាទី

06

Thực hành bảo mật Security Best Practices ការអនុវត្តសុវត្ថិភាព

Bảo mật production: validate input, isolate theo user, quản lý env và secret — code untrusted không được phá. Implement production security patterns: input validation, per-user isolation, environment variable management, and secret protection. Keep untrusted code from doing damage. សុវត្ថិភាព production៖ validate input បំបែកតាម user គ្រប់គ្រង env និង secret — code untrusted មិនត្រូវបំផ្លាញ។

Nội dung bước (lệnh, code) giữ nguyên tiếng Anh từ nguồn chính thức. Step body (commands, code) stays in English from the official source. ខ្លឹមសារជំហាន (ពាក្យបញ្ជា និង code) រក្សាភាសាអង់គ្លេសពីប្រភពផ្លូវការ។ labs.cloudflare.dev ↗

Cần trước Prerequisites តម្រូវការជាមុន

  • Đã xong bước 5 Completed Step 5 បានបញ្ចប់ជំហាន 5
  • Hiểu API sandbox Understanding of sandbox APIs យល់ API sandbox
  • Có ý thức bảo mật cơ bản Basic security awareness មានការយល់ដឹងសុវត្ថិភាពមូលដ្ឋាន

Bạn sẽ làm được Learning objectives គោលបំណងសិក្សា

  • Validate và sanitize input để chống injection Validate and sanitize user input to prevent injection attacks Validate និង sanitize input ដើម្បីទប់ injection
  • Cô lập sandbox theo user — không chia state Implement per-user sandbox isolation so users never share state បំបែក sandbox តាម user — មិនចែក state
  • Quản lý biến môi trường ở mức sandbox, lệnh và session Manage environment variables at sandbox, command, and session level គ្រប់គ្រងអថេរបរិស្ថាននៅកម្រិត sandbox ពាក្យបញ្ជា និង session
  • Giữ secret trong Worker — không đưa vào code sandbox Keep secrets in the Worker — never expose them to sandbox code ទុក secret ក្នុង Worker — កុំដាក់ចូល code sandbox
  • Làm pattern xác thực cho production Implement an authentication pattern for production ធ្វើ pattern ផ្ទៀងផ្ទាត់សម្រាប់ production

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:

typescript
// 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:

typescript
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:

typescript
// 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:

typescript
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:

typescript
// ✅ Good: each user has a completely isolated sandbox
const userId = await authenticateUser(request);
const sandbox = getSandbox(env.Sandbox, `user-${userId}`);
typescript
// ❌ 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:

typescript
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

typescript
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

typescript
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

typescript
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:

typescript
// ❌ 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:

typescript
// ✅ 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.
typescript
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.