@cloudflare/sandbox 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @cloudflare/sandbox
2
2
 
3
+ ## 0.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#71](https://github.com/cloudflare/sandbox-sdk/pull/71) [`fb3c9c2`](https://github.com/cloudflare/sandbox-sdk/commit/fb3c9c22242d9d4f157c26f547f1e697ef7875f9) Thanks [@ghostwriternr](https://github.com/ghostwriternr)! - Bump containers package version
8
+
9
+ - [#70](https://github.com/cloudflare/sandbox-sdk/pull/70) [`e1fa354`](https://github.com/cloudflare/sandbox-sdk/commit/e1fa354ab1bc7b0e89db4901b67028ebf1a93d0a) Thanks [@ghostwriternr](https://github.com/ghostwriternr)! - Fix escaped quotes in file write operations
10
+
11
+ - [#68](https://github.com/cloudflare/sandbox-sdk/pull/68) [`69b91d1`](https://github.com/cloudflare/sandbox-sdk/commit/69b91d1a8f6afb63262cc381ea93e94a033ed5e8) Thanks [@CyrusNuevoDia](https://github.com/CyrusNuevoDia)! - Configurable timeouts via environment variables in isolation.ts
12
+
13
+ - [#66](https://github.com/cloudflare/sandbox-sdk/pull/66) [`eca93b9`](https://github.com/cloudflare/sandbox-sdk/commit/eca93b97e40fa0d3bd9dc27af2cc214ec355b696) Thanks [@peterp](https://github.com/peterp)! - Determine if the port is specified in the URL.
14
+
3
15
  ## 0.3.0
4
16
 
5
17
  ### Minor Changes
package/README.md CHANGED
@@ -72,7 +72,7 @@ npm install @cloudflare/sandbox
72
72
  1. **Create a Dockerfile** (temporary requirement, will be removed in future releases):
73
73
 
74
74
  ```dockerfile
75
- FROM docker.io/cloudflare/sandbox:0.3.0
75
+ FROM docker.io/cloudflare/sandbox:0.3.1
76
76
 
77
77
  # Expose the ports you want to expose
78
78
  EXPOSE 3000
@@ -1,5 +1,5 @@
1
- import { SessionManager } from "../isolation";
2
- import { SessionExecRequest } from "../types";
1
+ import type { SessionManager } from "../isolation";
2
+ import type { SessionExecRequest } from "../types";
3
3
 
4
4
  export async function handleExecuteRequest(
5
5
  req: Request,
@@ -1,5 +1,5 @@
1
- import { SessionManager } from "../isolation";
2
- import { CreateSessionRequest } from "../types";
1
+ import type { SessionManager } from "../isolation";
2
+ import type { CreateSessionRequest } from "../types";
3
3
 
4
4
  export async function handleCreateSession(
5
5
  req: Request,
@@ -35,11 +35,11 @@ import type { ProcessRecord, ProcessStatus } from './types';
35
35
  // Configuration constants
36
36
  const CONFIG = {
37
37
  // Timeouts (in milliseconds)
38
- COMMAND_TIMEOUT_MS: 30000, // 30 seconds for command execution
39
38
  READY_TIMEOUT_MS: 5000, // 5 seconds for control process to initialize
40
- CLEANUP_INTERVAL_MS: 30000, // Run cleanup every 30 seconds
41
- TEMP_FILE_MAX_AGE_MS: 60000, // Delete temp files older than 60 seconds
42
39
  SHUTDOWN_GRACE_PERIOD_MS: 500, // Grace period for cleanup on shutdown
40
+ COMMAND_TIMEOUT_MS: parseInt(process.env.COMMAND_TIMEOUT_MS || '30000'), // 30 seconds for command execution
41
+ CLEANUP_INTERVAL_MS: parseInt(process.env.CLEANUP_INTERVAL_MS || '30000'), // Run cleanup every 30 seconds
42
+ TEMP_FILE_MAX_AGE_MS: parseInt(process.env.TEMP_FILE_MAX_AGE_MS || '60000'), // Delete temp files older than 60 seconds
43
43
 
44
44
  // Default paths
45
45
  DEFAULT_CWD: '/workspace',
@@ -423,12 +423,11 @@ export class Session {
423
423
 
424
424
  // File Operations - Execute as shell commands to inherit session context
425
425
  async writeFileOperation(path: string, content: string, encoding: string = 'utf-8'): Promise<{ success: boolean; exitCode: number; path: string }> {
426
- // Escape content for safe heredoc usage
427
- const safeContent = content.replace(/'/g, "'\\''");
428
-
429
426
  // Create parent directory if needed, then write file using heredoc
427
+ // Note: The quoted heredoc delimiter 'SANDBOX_EOF' prevents variable expansion
428
+ // and treats the content literally, so no escaping is required
430
429
  const command = `mkdir -p "$(dirname "${path}")" && cat > "${path}" << 'SANDBOX_EOF'
431
- ${safeContent}
430
+ ${content}
432
431
  SANDBOX_EOF`;
433
432
 
434
433
  const result = await this.exec(command);
@@ -1036,4 +1035,4 @@ export class SessionManager {
1036
1035
  }
1037
1036
  this.sessions.clear();
1038
1037
  }
1039
- }
1038
+ }
@@ -196,6 +196,9 @@ var Sandbox = class extends Container {
196
196
  if (proxyMatch) {
197
197
  return parseInt(proxyMatch[1]);
198
198
  }
199
+ if (url.port) {
200
+ return parseInt(url.port);
201
+ }
199
202
  return 3e3;
200
203
  }
201
204
  // Helper to ensure default session is initialized
@@ -661,4 +664,4 @@ export {
661
664
  proxyToSandbox,
662
665
  isLocalhostPattern
663
666
  };
664
- //# sourceMappingURL=chunk-GTGWAEED.js.map
667
+ //# sourceMappingURL=chunk-LFLJGISB.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/sandbox.ts","../src/request-handler.ts"],"sourcesContent":["import { Container, getContainer } from \"@cloudflare/containers\";\nimport { CodeInterpreter } from \"./interpreter\";\nimport type {\n CodeContext,\n CreateContextOptions,\n ExecutionResult,\n RunCodeOptions,\n} from \"./interpreter-types\";\nimport { JupyterClient } from \"./jupyter-client\";\nimport { isLocalhostPattern } from \"./request-handler\";\nimport {\n logSecurityEvent,\n SecurityError,\n sanitizeSandboxId,\n validatePort,\n} from \"./security\";\nimport { parseSSEStream } from \"./sse-parser\";\nimport type {\n ExecEvent,\n ExecOptions,\n ExecResult,\n ExecuteResponse,\n ExecutionSession,\n ISandbox,\n Process,\n ProcessOptions,\n ProcessStatus,\n StreamOptions,\n} from \"./types\";\nimport { ProcessNotFoundError, SandboxError } from \"./types\";\n\nexport function getSandbox(ns: DurableObjectNamespace<Sandbox>, id: string) {\n const stub = getContainer(ns, id);\n\n // Store the name on first access\n stub.setSandboxName?.(id);\n\n return stub;\n}\n\nexport class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {\n defaultPort = 3000; // Default port for the container's Bun server\n sleepAfter = \"20m\"; // Keep container warm for 20 minutes to avoid cold starts\n client: JupyterClient;\n private sandboxName: string | null = null;\n private codeInterpreter: CodeInterpreter;\n private defaultSession: ExecutionSession | null = null;\n\n constructor(ctx: DurableObjectState, env: Env) {\n super(ctx, env);\n this.client = new JupyterClient({\n onCommandComplete: (success, exitCode, _stdout, _stderr, command) => {\n console.log(\n `[Container] Command completed: ${command}, Success: ${success}, Exit code: ${exitCode}`\n );\n },\n onCommandStart: (command) => {\n console.log(`[Container] Command started: ${command}`);\n },\n onError: (error, _command) => {\n console.error(`[Container] Command error: ${error}`);\n },\n onOutput: (stream, data, _command) => {\n console.log(`[Container] [${stream}] ${data}`);\n },\n port: 3000, // Control plane port\n stub: this,\n });\n\n // Initialize code interpreter\n this.codeInterpreter = new CodeInterpreter(this);\n\n // Load the sandbox name from storage on initialization\n this.ctx.blockConcurrencyWhile(async () => {\n this.sandboxName =\n (await this.ctx.storage.get<string>(\"sandboxName\")) || null;\n });\n }\n\n // RPC method to set the sandbox name\n async setSandboxName(name: string): Promise<void> {\n if (!this.sandboxName) {\n this.sandboxName = name;\n await this.ctx.storage.put(\"sandboxName\", name);\n console.log(`[Sandbox] Stored sandbox name via RPC: ${name}`);\n }\n }\n\n // RPC method to set environment variables\n async setEnvVars(envVars: Record<string, string>): Promise<void> {\n this.envVars = { ...this.envVars, ...envVars };\n console.log(`[Sandbox] Updated environment variables`);\n \n // If we have a default session, update its environment too\n if (this.defaultSession) {\n await this.defaultSession.setEnvVars(envVars);\n }\n }\n\n override onStart() {\n console.log(\"Sandbox successfully started\");\n }\n\n override onStop() {\n console.log(\"Sandbox successfully shut down\");\n }\n\n override onError(error: unknown) {\n console.log(\"Sandbox error:\", error);\n }\n\n // Override fetch to route internal container requests to appropriate ports\n override async fetch(request: Request): Promise<Response> {\n const url = new URL(request.url);\n\n // Capture and store the sandbox name from the header if present\n if (!this.sandboxName && request.headers.has(\"X-Sandbox-Name\")) {\n const name = request.headers.get(\"X-Sandbox-Name\")!;\n this.sandboxName = name;\n await this.ctx.storage.put(\"sandboxName\", name);\n console.log(`[Sandbox] Stored sandbox name: ${this.sandboxName}`);\n }\n\n // Determine which port to route to\n const port = this.determinePort(url);\n\n // Route to the appropriate port\n return await this.containerFetch(request, port);\n }\n\n private determinePort(url: URL): number {\n // Extract port from proxy requests (e.g., /proxy/8080/*)\n const proxyMatch = url.pathname.match(/^\\/proxy\\/(\\d+)/);\n if (proxyMatch) {\n return parseInt(proxyMatch[1]);\n }\n\n // All other requests go to control plane on port 3000\n // This includes /api/* endpoints and any other control requests\n return 3000;\n }\n\n // Helper to ensure default session is initialized\n private async ensureDefaultSession(): Promise<ExecutionSession> {\n if (!this.defaultSession) {\n const sessionId = `sandbox-${this.sandboxName || 'default'}`;\n this.defaultSession = await this.createSession({\n id: sessionId,\n env: this.envVars || {},\n cwd: '/workspace',\n isolation: true\n });\n console.log(`[Sandbox] Default session initialized: ${sessionId}`);\n }\n return this.defaultSession;\n }\n\n\n async exec(command: string, options?: ExecOptions): Promise<ExecResult> {\n const session = await this.ensureDefaultSession();\n return session.exec(command, options);\n }\n\n async startProcess(\n command: string,\n options?: ProcessOptions\n ): Promise<Process> {\n const session = await this.ensureDefaultSession();\n return session.startProcess(command, options);\n }\n\n async listProcesses(): Promise<Process[]> {\n const session = await this.ensureDefaultSession();\n return session.listProcesses();\n }\n\n async getProcess(id: string): Promise<Process | null> {\n const session = await this.ensureDefaultSession();\n return session.getProcess(id);\n }\n\n async killProcess(id: string, signal?: string): Promise<void> {\n const session = await this.ensureDefaultSession();\n return session.killProcess(id, signal);\n }\n\n async killAllProcesses(): Promise<number> {\n const session = await this.ensureDefaultSession();\n return session.killAllProcesses();\n }\n\n async cleanupCompletedProcesses(): Promise<number> {\n const session = await this.ensureDefaultSession();\n return session.cleanupCompletedProcesses();\n }\n\n async getProcessLogs(\n id: string\n ): Promise<{ stdout: string; stderr: string }> {\n const session = await this.ensureDefaultSession();\n return session.getProcessLogs(id);\n }\n\n // Streaming methods - delegates to default session\n async execStream(\n command: string,\n options?: StreamOptions\n ): Promise<ReadableStream<Uint8Array>> {\n const session = await this.ensureDefaultSession();\n return session.execStream(command, options);\n }\n\n async streamProcessLogs(\n processId: string,\n options?: { signal?: AbortSignal }\n ): Promise<ReadableStream<Uint8Array>> {\n const session = await this.ensureDefaultSession();\n return session.streamProcessLogs(processId, options);\n }\n\n async gitCheckout(\n repoUrl: string,\n options: { branch?: string; targetDir?: string }\n ) {\n const session = await this.ensureDefaultSession();\n return session.gitCheckout(repoUrl, options);\n }\n\n async mkdir(path: string, options: { recursive?: boolean } = {}) {\n const session = await this.ensureDefaultSession();\n return session.mkdir(path, options);\n }\n\n async writeFile(\n path: string,\n content: string,\n options: { encoding?: string } = {}\n ) {\n const session = await this.ensureDefaultSession();\n return session.writeFile(path, content, options);\n }\n\n async deleteFile(path: string) {\n const session = await this.ensureDefaultSession();\n return session.deleteFile(path);\n }\n\n async renameFile(oldPath: string, newPath: string) {\n const session = await this.ensureDefaultSession();\n return session.renameFile(oldPath, newPath);\n }\n\n async moveFile(sourcePath: string, destinationPath: string) {\n const session = await this.ensureDefaultSession();\n return session.moveFile(sourcePath, destinationPath);\n }\n\n async readFile(path: string, options: { encoding?: string } = {}) {\n const session = await this.ensureDefaultSession();\n return session.readFile(path, options);\n }\n\n async listFiles(\n path: string,\n options: {\n recursive?: boolean;\n includeHidden?: boolean;\n } = {}\n ) {\n const session = await this.ensureDefaultSession();\n return session.listFiles(path, options);\n }\n\n async exposePort(port: number, options: { name?: string; hostname: string }) {\n await this.client.exposePort(port, options?.name);\n\n // We need the sandbox name to construct preview URLs\n if (!this.sandboxName) {\n throw new Error(\n \"Sandbox name not available. Ensure sandbox is accessed through getSandbox()\"\n );\n }\n\n const url = this.constructPreviewUrl(\n port,\n this.sandboxName,\n options.hostname\n );\n\n return {\n url,\n port,\n name: options?.name,\n };\n }\n\n async unexposePort(port: number) {\n if (!validatePort(port)) {\n logSecurityEvent(\n \"INVALID_PORT_UNEXPOSE\",\n {\n port,\n },\n \"high\"\n );\n throw new SecurityError(\n `Invalid port number: ${port}. Must be between 1024-65535 and not reserved.`\n );\n }\n\n await this.client.unexposePort(port);\n\n logSecurityEvent(\n \"PORT_UNEXPOSED\",\n {\n port,\n },\n \"low\"\n );\n }\n\n async getExposedPorts(hostname: string) {\n const response = await this.client.getExposedPorts();\n\n // We need the sandbox name to construct preview URLs\n if (!this.sandboxName) {\n throw new Error(\n \"Sandbox name not available. Ensure sandbox is accessed through getSandbox()\"\n );\n }\n\n return response.ports.map((port) => ({\n url: this.constructPreviewUrl(port.port, this.sandboxName!, hostname),\n port: port.port,\n name: port.name,\n exposedAt: port.exposedAt,\n }));\n }\n\n private constructPreviewUrl(\n port: number,\n sandboxId: string,\n hostname: string\n ): string {\n if (!validatePort(port)) {\n logSecurityEvent(\n \"INVALID_PORT_REJECTED\",\n {\n port,\n sandboxId,\n hostname,\n },\n \"high\"\n );\n throw new SecurityError(\n `Invalid port number: ${port}. Must be between 1024-65535 and not reserved.`\n );\n }\n\n let sanitizedSandboxId: string;\n try {\n sanitizedSandboxId = sanitizeSandboxId(sandboxId);\n } catch (error) {\n logSecurityEvent(\n \"INVALID_SANDBOX_ID_REJECTED\",\n {\n sandboxId,\n port,\n hostname,\n error: error instanceof Error ? error.message : \"Unknown error\",\n },\n \"high\"\n );\n throw error;\n }\n\n const isLocalhost = isLocalhostPattern(hostname);\n\n if (isLocalhost) {\n // Unified subdomain approach for localhost (RFC 6761)\n const [host, portStr] = hostname.split(\":\");\n const mainPort = portStr || \"80\";\n\n // Use URL constructor for safe URL building\n try {\n const baseUrl = new URL(`http://${host}:${mainPort}`);\n // Construct subdomain safely\n const subdomainHost = `${port}-${sanitizedSandboxId}.${host}`;\n baseUrl.hostname = subdomainHost;\n\n const finalUrl = baseUrl.toString();\n\n logSecurityEvent(\n \"PREVIEW_URL_CONSTRUCTED\",\n {\n port,\n sandboxId: sanitizedSandboxId,\n hostname,\n resultUrl: finalUrl,\n environment: \"localhost\",\n },\n \"low\"\n );\n\n return finalUrl;\n } catch (error) {\n logSecurityEvent(\n \"URL_CONSTRUCTION_FAILED\",\n {\n port,\n sandboxId: sanitizedSandboxId,\n hostname,\n error: error instanceof Error ? error.message : \"Unknown error\",\n },\n \"high\"\n );\n throw new SecurityError(\n `Failed to construct preview URL: ${\n error instanceof Error ? error.message : \"Unknown error\"\n }`\n );\n }\n }\n\n // Production subdomain logic - enforce HTTPS\n try {\n // Always use HTTPS for production (non-localhost)\n const protocol = \"https\";\n const baseUrl = new URL(`${protocol}://${hostname}`);\n\n // Construct subdomain safely\n const subdomainHost = `${port}-${sanitizedSandboxId}.${hostname}`;\n baseUrl.hostname = subdomainHost;\n\n const finalUrl = baseUrl.toString();\n\n logSecurityEvent(\n \"PREVIEW_URL_CONSTRUCTED\",\n {\n port,\n sandboxId: sanitizedSandboxId,\n hostname,\n resultUrl: finalUrl,\n environment: \"production\",\n },\n \"low\"\n );\n\n return finalUrl;\n } catch (error) {\n logSecurityEvent(\n \"URL_CONSTRUCTION_FAILED\",\n {\n port,\n sandboxId: sanitizedSandboxId,\n hostname,\n error: error instanceof Error ? error.message : \"Unknown error\",\n },\n \"high\"\n );\n throw new SecurityError(\n `Failed to construct preview URL: ${\n error instanceof Error ? error.message : \"Unknown error\"\n }`\n );\n }\n }\n\n // Code Interpreter Methods\n\n /**\n * Create a new code execution context\n */\n async createCodeContext(\n options?: CreateContextOptions\n ): Promise<CodeContext> {\n return this.codeInterpreter.createCodeContext(options);\n }\n\n /**\n * Run code with streaming callbacks\n */\n async runCode(\n code: string,\n options?: RunCodeOptions\n ): Promise<ExecutionResult> {\n const execution = await this.codeInterpreter.runCode(code, options);\n // Convert to plain object for RPC serialization\n return execution.toJSON();\n }\n\n /**\n * Run code and return a streaming response\n */\n async runCodeStream(\n code: string,\n options?: RunCodeOptions\n ): Promise<ReadableStream> {\n return this.codeInterpreter.runCodeStream(code, options);\n }\n\n /**\n * List all code contexts\n */\n async listCodeContexts(): Promise<CodeContext[]> {\n return this.codeInterpreter.listCodeContexts();\n }\n\n /**\n * Delete a code context\n */\n async deleteCodeContext(contextId: string): Promise<void> {\n return this.codeInterpreter.deleteCodeContext(contextId);\n }\n\n // ============================================================================\n // Session Management (Simple Isolation)\n // ============================================================================\n\n /**\n * Create a new execution session with isolation\n * Returns a session object with exec() method\n */\n\n async createSession(options: {\n id?: string;\n env?: Record<string, string>;\n cwd?: string;\n isolation?: boolean;\n }): Promise<ExecutionSession> {\n const sessionId = options.id || `session-${Date.now()}`;\n \n await this.client.createSession({\n id: sessionId,\n env: options.env,\n cwd: options.cwd,\n isolation: options.isolation\n });\n // Return comprehensive ExecutionSession object that implements all ISandbox methods\n return {\n id: sessionId,\n \n // Command execution - clean method names\n exec: async (command: string, options?: ExecOptions) => {\n const result = await this.client.exec(sessionId, command);\n return {\n ...result,\n command,\n duration: 0,\n timestamp: new Date().toISOString()\n };\n },\n \n execStream: async (command: string, options?: StreamOptions) => {\n return await this.client.execStream(sessionId, command);\n },\n \n // Process management - route to session-aware methods\n startProcess: async (command: string, options?: ProcessOptions) => {\n // Use session-specific process management\n const response = await this.client.startProcess(command, sessionId, {\n processId: options?.processId,\n timeout: options?.timeout,\n env: options?.env,\n cwd: options?.cwd,\n encoding: options?.encoding,\n autoCleanup: options?.autoCleanup,\n });\n \n // Convert response to Process object with bound methods\n const process = response.process;\n return {\n id: process.id,\n pid: process.pid,\n command: process.command,\n status: process.status as ProcessStatus,\n startTime: new Date(process.startTime),\n endTime: process.endTime ? new Date(process.endTime) : undefined,\n exitCode: process.exitCode ?? undefined,\n kill: async (signal?: string) => {\n await this.client.killProcess(process.id);\n },\n getStatus: async () => {\n const resp = await this.client.getProcess(process.id);\n return resp.process?.status as ProcessStatus || \"error\";\n },\n getLogs: async () => {\n return await this.client.getProcessLogs(process.id);\n },\n };\n },\n \n listProcesses: async () => {\n // Get processes for this specific session\n const response = await this.client.listProcesses(sessionId);\n \n // Convert to Process objects with bound methods\n return response.processes.map(p => ({\n id: p.id,\n pid: p.pid,\n command: p.command,\n status: p.status as ProcessStatus,\n startTime: new Date(p.startTime),\n endTime: p.endTime ? new Date(p.endTime) : undefined,\n exitCode: p.exitCode ?? undefined,\n kill: async (signal?: string) => {\n await this.client.killProcess(p.id);\n },\n getStatus: async () => {\n const processResp = await this.client.getProcess(p.id);\n return processResp.process?.status as ProcessStatus || \"error\";\n },\n getLogs: async () => {\n return this.client.getProcessLogs(p.id);\n },\n }));\n },\n \n getProcess: async (id: string) => {\n const response = await this.client.getProcess(id);\n if (!response.process) return null;\n \n const p = response.process;\n return {\n id: p.id,\n pid: p.pid,\n command: p.command,\n status: p.status as ProcessStatus,\n startTime: new Date(p.startTime),\n endTime: p.endTime ? new Date(p.endTime) : undefined,\n exitCode: p.exitCode ?? undefined,\n kill: async (signal?: string) => {\n await this.client.killProcess(p.id);\n },\n getStatus: async () => {\n const processResp = await this.client.getProcess(p.id);\n return processResp.process?.status as ProcessStatus || \"error\";\n },\n getLogs: async () => {\n return this.client.getProcessLogs(p.id);\n },\n };\n },\n \n killProcess: async (id: string, signal?: string) => {\n await this.client.killProcess(id);\n },\n \n killAllProcesses: async () => {\n // Kill all processes for this specific session\n const response = await this.client.killAllProcesses(sessionId);\n return response.killedCount;\n },\n \n streamProcessLogs: async (processId: string, options?: { signal?: AbortSignal }) => {\n return await this.client.streamProcessLogs(processId, options);\n },\n \n getProcessLogs: async (id: string) => {\n return await this.client.getProcessLogs(id);\n },\n \n cleanupCompletedProcesses: async () => {\n // This would need a new endpoint to cleanup processes for a specific session\n // For now, return 0 as no cleanup is performed\n return 0;\n },\n \n // File operations - clean method names (no \"InSession\" suffix)\n writeFile: async (path: string, content: string, options?: { encoding?: string }) => {\n return await this.client.writeFile(path, content, options?.encoding, sessionId);\n },\n \n readFile: async (path: string, options?: { encoding?: string }) => {\n return await this.client.readFile(path, options?.encoding, sessionId);\n },\n \n mkdir: async (path: string, options?: { recursive?: boolean }) => {\n return await this.client.mkdir(path, options?.recursive, sessionId);\n },\n \n deleteFile: async (path: string) => {\n return await this.client.deleteFile(path, sessionId);\n },\n \n renameFile: async (oldPath: string, newPath: string) => {\n return await this.client.renameFile(oldPath, newPath, sessionId);\n },\n \n moveFile: async (sourcePath: string, destinationPath: string) => {\n return await this.client.moveFile(sourcePath, destinationPath, sessionId);\n },\n \n listFiles: async (path: string, options?: { recursive?: boolean; includeHidden?: boolean }) => {\n return await this.client.listFiles(path, sessionId, options);\n },\n \n gitCheckout: async (repoUrl: string, options?: { branch?: string; targetDir?: string }) => {\n return await this.client.gitCheckout(repoUrl, sessionId, options?.branch, options?.targetDir);\n },\n \n // Port management\n exposePort: async (port: number, options: { name?: string; hostname: string }) => {\n return await this.exposePort(port, options);\n },\n \n unexposePort: async (port: number) => {\n return await this.unexposePort(port);\n },\n \n getExposedPorts: async (hostname: string) => {\n return await this.getExposedPorts(hostname);\n },\n \n // Environment management\n setEnvVars: async (envVars: Record<string, string>) => {\n // TODO: Implement session-specific environment updates\n console.log(`[Session ${sessionId}] Environment variables update not yet implemented`);\n },\n \n // Code Interpreter API\n createCodeContext: async (options?: any) => {\n return await this.createCodeContext(options);\n },\n \n runCode: async (code: string, options?: any) => {\n return await this.runCode(code, options);\n },\n \n runCodeStream: async (code: string, options?: any) => {\n return await this.runCodeStream(code, options);\n },\n \n listCodeContexts: async () => {\n return await this.listCodeContexts();\n },\n \n deleteCodeContext: async (contextId: string) => {\n return await this.deleteCodeContext(contextId);\n }\n };\n }\n}\n","import { getSandbox, type Sandbox } from \"./sandbox\";\nimport {\n logSecurityEvent,\n sanitizeSandboxId,\n validatePort\n} from \"./security\";\n\nexport interface SandboxEnv {\n Sandbox: DurableObjectNamespace<Sandbox>;\n}\n\nexport interface RouteInfo {\n port: number;\n sandboxId: string;\n path: string;\n}\n\nexport async function proxyToSandbox<E extends SandboxEnv>(\n request: Request,\n env: E\n): Promise<Response | null> {\n try {\n const url = new URL(request.url);\n const routeInfo = extractSandboxRoute(url);\n\n if (!routeInfo) {\n return null; // Not a request to an exposed container port\n }\n\n const { sandboxId, port, path } = routeInfo;\n const sandbox = getSandbox(env.Sandbox, sandboxId);\n\n // Build proxy request with proper headers\n let proxyUrl: string;\n\n // Route based on the target port\n if (port !== 3000) {\n // Route directly to user's service on the specified port\n proxyUrl = `http://localhost:${port}${path}${url.search}`;\n } else {\n // Port 3000 is our control plane - route normally\n proxyUrl = `http://localhost:3000${path}${url.search}`;\n }\n\n const proxyRequest = new Request(proxyUrl, {\n method: request.method,\n headers: {\n ...Object.fromEntries(request.headers),\n 'X-Original-URL': request.url,\n 'X-Forwarded-Host': url.hostname,\n 'X-Forwarded-Proto': url.protocol.replace(':', ''),\n 'X-Sandbox-Name': sandboxId, // Pass the friendly name\n },\n body: request.body,\n });\n\n return sandbox.containerFetch(proxyRequest, port);\n } catch (error) {\n console.error('[Sandbox] Proxy routing error:', error);\n return new Response('Proxy routing error', { status: 500 });\n }\n}\n\nfunction extractSandboxRoute(url: URL): RouteInfo | null {\n // Parse subdomain pattern: port-sandboxId.domain\n const subdomainMatch = url.hostname.match(/^(\\d{4,5})-([^.-][^.]*[^.-]|[^.-])\\.(.+)$/);\n\n if (!subdomainMatch) {\n // Log malformed subdomain attempts\n if (url.hostname.includes('-') && url.hostname.includes('.')) {\n logSecurityEvent('MALFORMED_SUBDOMAIN_ATTEMPT', {\n hostname: url.hostname,\n url: url.toString()\n }, 'medium');\n }\n return null;\n }\n\n const portStr = subdomainMatch[1];\n const sandboxId = subdomainMatch[2];\n const domain = subdomainMatch[3];\n\n const port = parseInt(portStr, 10);\n if (!validatePort(port)) {\n logSecurityEvent('INVALID_PORT_IN_SUBDOMAIN', {\n port,\n portStr,\n sandboxId,\n hostname: url.hostname,\n url: url.toString()\n }, 'high');\n return null;\n }\n\n let sanitizedSandboxId: string;\n try {\n sanitizedSandboxId = sanitizeSandboxId(sandboxId);\n } catch (error) {\n logSecurityEvent('INVALID_SANDBOX_ID_IN_SUBDOMAIN', {\n sandboxId,\n port,\n hostname: url.hostname,\n url: url.toString(),\n error: error instanceof Error ? error.message : 'Unknown error'\n }, 'high');\n return null;\n }\n\n // DNS subdomain length limit is 63 characters\n if (sandboxId.length > 63) {\n logSecurityEvent('SANDBOX_ID_LENGTH_VIOLATION', {\n sandboxId,\n length: sandboxId.length,\n port,\n hostname: url.hostname\n }, 'medium');\n return null;\n }\n\n logSecurityEvent('SANDBOX_ROUTE_EXTRACTED', {\n port,\n sandboxId: sanitizedSandboxId,\n domain,\n path: url.pathname || \"/\",\n hostname: url.hostname\n }, 'low');\n\n return {\n port,\n sandboxId: sanitizedSandboxId,\n path: url.pathname || \"/\",\n };\n}\n\nexport function isLocalhostPattern(hostname: string): boolean {\n const hostPart = hostname.split(\":\")[0];\n return (\n hostPart === \"localhost\" ||\n hostPart === \"127.0.0.1\" ||\n hostPart === \"::1\" ||\n hostPart === \"[::1]\" ||\n hostPart === \"0.0.0.0\"\n );\n}\n"],"mappings":"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","names":["options"]}
1
+ {"version":3,"sources":["../src/sandbox.ts","../src/request-handler.ts"],"sourcesContent":["import { Container, getContainer } from \"@cloudflare/containers\";\nimport { CodeInterpreter } from \"./interpreter\";\nimport type {\n CodeContext,\n CreateContextOptions,\n ExecutionResult,\n RunCodeOptions,\n} from \"./interpreter-types\";\nimport { JupyterClient } from \"./jupyter-client\";\nimport { isLocalhostPattern } from \"./request-handler\";\nimport {\n logSecurityEvent,\n SecurityError,\n sanitizeSandboxId,\n validatePort,\n} from \"./security\";\nimport { parseSSEStream } from \"./sse-parser\";\nimport type {\n ExecEvent,\n ExecOptions,\n ExecResult,\n ExecuteResponse,\n ExecutionSession,\n ISandbox,\n Process,\n ProcessOptions,\n ProcessStatus,\n StreamOptions,\n} from \"./types\";\nimport { ProcessNotFoundError, SandboxError } from \"./types\";\n\nexport function getSandbox(ns: DurableObjectNamespace<Sandbox>, id: string) {\n const stub = getContainer(ns, id);\n\n // Store the name on first access\n stub.setSandboxName?.(id);\n\n return stub;\n}\n\nexport class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {\n defaultPort = 3000; // Default port for the container's Bun server\n sleepAfter = \"20m\"; // Keep container warm for 20 minutes to avoid cold starts\n client: JupyterClient;\n private sandboxName: string | null = null;\n private codeInterpreter: CodeInterpreter;\n private defaultSession: ExecutionSession | null = null;\n\n constructor(ctx: DurableObjectState, env: Env) {\n super(ctx, env);\n this.client = new JupyterClient({\n onCommandComplete: (success, exitCode, _stdout, _stderr, command) => {\n console.log(\n `[Container] Command completed: ${command}, Success: ${success}, Exit code: ${exitCode}`\n );\n },\n onCommandStart: (command) => {\n console.log(`[Container] Command started: ${command}`);\n },\n onError: (error, _command) => {\n console.error(`[Container] Command error: ${error}`);\n },\n onOutput: (stream, data, _command) => {\n console.log(`[Container] [${stream}] ${data}`);\n },\n port: 3000, // Control plane port\n stub: this,\n });\n\n // Initialize code interpreter\n this.codeInterpreter = new CodeInterpreter(this);\n\n // Load the sandbox name from storage on initialization\n this.ctx.blockConcurrencyWhile(async () => {\n this.sandboxName =\n (await this.ctx.storage.get<string>(\"sandboxName\")) || null;\n });\n }\n\n // RPC method to set the sandbox name\n async setSandboxName(name: string): Promise<void> {\n if (!this.sandboxName) {\n this.sandboxName = name;\n await this.ctx.storage.put(\"sandboxName\", name);\n console.log(`[Sandbox] Stored sandbox name via RPC: ${name}`);\n }\n }\n\n // RPC method to set environment variables\n async setEnvVars(envVars: Record<string, string>): Promise<void> {\n this.envVars = { ...this.envVars, ...envVars };\n console.log(`[Sandbox] Updated environment variables`);\n \n // If we have a default session, update its environment too\n if (this.defaultSession) {\n await this.defaultSession.setEnvVars(envVars);\n }\n }\n\n override onStart() {\n console.log(\"Sandbox successfully started\");\n }\n\n override onStop() {\n console.log(\"Sandbox successfully shut down\");\n }\n\n override onError(error: unknown) {\n console.log(\"Sandbox error:\", error);\n }\n\n // Override fetch to route internal container requests to appropriate ports\n override async fetch(request: Request): Promise<Response> {\n const url = new URL(request.url);\n\n // Capture and store the sandbox name from the header if present\n if (!this.sandboxName && request.headers.has(\"X-Sandbox-Name\")) {\n const name = request.headers.get(\"X-Sandbox-Name\")!;\n this.sandboxName = name;\n await this.ctx.storage.put(\"sandboxName\", name);\n console.log(`[Sandbox] Stored sandbox name: ${this.sandboxName}`);\n }\n\n // Determine which port to route to\n const port = this.determinePort(url);\n\n // Route to the appropriate port\n return await this.containerFetch(request, port);\n }\n\n private determinePort(url: URL): number {\n // Extract port from proxy requests (e.g., /proxy/8080/*)\n const proxyMatch = url.pathname.match(/^\\/proxy\\/(\\d+)/);\n if (proxyMatch) {\n return parseInt(proxyMatch[1]);\n }\n\n if (url.port) {\n return parseInt(url.port);\n }\n\n // All other requests go to control plane on port 3000\n // This includes /api/* endpoints and any other control requests\n return 3000;\n }\n\n // Helper to ensure default session is initialized\n private async ensureDefaultSession(): Promise<ExecutionSession> {\n if (!this.defaultSession) {\n const sessionId = `sandbox-${this.sandboxName || 'default'}`;\n this.defaultSession = await this.createSession({\n id: sessionId,\n env: this.envVars || {},\n cwd: '/workspace',\n isolation: true\n });\n console.log(`[Sandbox] Default session initialized: ${sessionId}`);\n }\n return this.defaultSession;\n }\n\n\n async exec(command: string, options?: ExecOptions): Promise<ExecResult> {\n const session = await this.ensureDefaultSession();\n return session.exec(command, options);\n }\n\n async startProcess(\n command: string,\n options?: ProcessOptions\n ): Promise<Process> {\n const session = await this.ensureDefaultSession();\n return session.startProcess(command, options);\n }\n\n async listProcesses(): Promise<Process[]> {\n const session = await this.ensureDefaultSession();\n return session.listProcesses();\n }\n\n async getProcess(id: string): Promise<Process | null> {\n const session = await this.ensureDefaultSession();\n return session.getProcess(id);\n }\n\n async killProcess(id: string, signal?: string): Promise<void> {\n const session = await this.ensureDefaultSession();\n return session.killProcess(id, signal);\n }\n\n async killAllProcesses(): Promise<number> {\n const session = await this.ensureDefaultSession();\n return session.killAllProcesses();\n }\n\n async cleanupCompletedProcesses(): Promise<number> {\n const session = await this.ensureDefaultSession();\n return session.cleanupCompletedProcesses();\n }\n\n async getProcessLogs(\n id: string\n ): Promise<{ stdout: string; stderr: string }> {\n const session = await this.ensureDefaultSession();\n return session.getProcessLogs(id);\n }\n\n // Streaming methods - delegates to default session\n async execStream(\n command: string,\n options?: StreamOptions\n ): Promise<ReadableStream<Uint8Array>> {\n const session = await this.ensureDefaultSession();\n return session.execStream(command, options);\n }\n\n async streamProcessLogs(\n processId: string,\n options?: { signal?: AbortSignal }\n ): Promise<ReadableStream<Uint8Array>> {\n const session = await this.ensureDefaultSession();\n return session.streamProcessLogs(processId, options);\n }\n\n async gitCheckout(\n repoUrl: string,\n options: { branch?: string; targetDir?: string }\n ) {\n const session = await this.ensureDefaultSession();\n return session.gitCheckout(repoUrl, options);\n }\n\n async mkdir(path: string, options: { recursive?: boolean } = {}) {\n const session = await this.ensureDefaultSession();\n return session.mkdir(path, options);\n }\n\n async writeFile(\n path: string,\n content: string,\n options: { encoding?: string } = {}\n ) {\n const session = await this.ensureDefaultSession();\n return session.writeFile(path, content, options);\n }\n\n async deleteFile(path: string) {\n const session = await this.ensureDefaultSession();\n return session.deleteFile(path);\n }\n\n async renameFile(oldPath: string, newPath: string) {\n const session = await this.ensureDefaultSession();\n return session.renameFile(oldPath, newPath);\n }\n\n async moveFile(sourcePath: string, destinationPath: string) {\n const session = await this.ensureDefaultSession();\n return session.moveFile(sourcePath, destinationPath);\n }\n\n async readFile(path: string, options: { encoding?: string } = {}) {\n const session = await this.ensureDefaultSession();\n return session.readFile(path, options);\n }\n\n async listFiles(\n path: string,\n options: {\n recursive?: boolean;\n includeHidden?: boolean;\n } = {}\n ) {\n const session = await this.ensureDefaultSession();\n return session.listFiles(path, options);\n }\n\n async exposePort(port: number, options: { name?: string; hostname: string }) {\n await this.client.exposePort(port, options?.name);\n\n // We need the sandbox name to construct preview URLs\n if (!this.sandboxName) {\n throw new Error(\n \"Sandbox name not available. Ensure sandbox is accessed through getSandbox()\"\n );\n }\n\n const url = this.constructPreviewUrl(\n port,\n this.sandboxName,\n options.hostname\n );\n\n return {\n url,\n port,\n name: options?.name,\n };\n }\n\n async unexposePort(port: number) {\n if (!validatePort(port)) {\n logSecurityEvent(\n \"INVALID_PORT_UNEXPOSE\",\n {\n port,\n },\n \"high\"\n );\n throw new SecurityError(\n `Invalid port number: ${port}. Must be between 1024-65535 and not reserved.`\n );\n }\n\n await this.client.unexposePort(port);\n\n logSecurityEvent(\n \"PORT_UNEXPOSED\",\n {\n port,\n },\n \"low\"\n );\n }\n\n async getExposedPorts(hostname: string) {\n const response = await this.client.getExposedPorts();\n\n // We need the sandbox name to construct preview URLs\n if (!this.sandboxName) {\n throw new Error(\n \"Sandbox name not available. Ensure sandbox is accessed through getSandbox()\"\n );\n }\n\n return response.ports.map((port) => ({\n url: this.constructPreviewUrl(port.port, this.sandboxName!, hostname),\n port: port.port,\n name: port.name,\n exposedAt: port.exposedAt,\n }));\n }\n\n private constructPreviewUrl(\n port: number,\n sandboxId: string,\n hostname: string\n ): string {\n if (!validatePort(port)) {\n logSecurityEvent(\n \"INVALID_PORT_REJECTED\",\n {\n port,\n sandboxId,\n hostname,\n },\n \"high\"\n );\n throw new SecurityError(\n `Invalid port number: ${port}. Must be between 1024-65535 and not reserved.`\n );\n }\n\n let sanitizedSandboxId: string;\n try {\n sanitizedSandboxId = sanitizeSandboxId(sandboxId);\n } catch (error) {\n logSecurityEvent(\n \"INVALID_SANDBOX_ID_REJECTED\",\n {\n sandboxId,\n port,\n hostname,\n error: error instanceof Error ? error.message : \"Unknown error\",\n },\n \"high\"\n );\n throw error;\n }\n\n const isLocalhost = isLocalhostPattern(hostname);\n\n if (isLocalhost) {\n // Unified subdomain approach for localhost (RFC 6761)\n const [host, portStr] = hostname.split(\":\");\n const mainPort = portStr || \"80\";\n\n // Use URL constructor for safe URL building\n try {\n const baseUrl = new URL(`http://${host}:${mainPort}`);\n // Construct subdomain safely\n const subdomainHost = `${port}-${sanitizedSandboxId}.${host}`;\n baseUrl.hostname = subdomainHost;\n\n const finalUrl = baseUrl.toString();\n\n logSecurityEvent(\n \"PREVIEW_URL_CONSTRUCTED\",\n {\n port,\n sandboxId: sanitizedSandboxId,\n hostname,\n resultUrl: finalUrl,\n environment: \"localhost\",\n },\n \"low\"\n );\n\n return finalUrl;\n } catch (error) {\n logSecurityEvent(\n \"URL_CONSTRUCTION_FAILED\",\n {\n port,\n sandboxId: sanitizedSandboxId,\n hostname,\n error: error instanceof Error ? error.message : \"Unknown error\",\n },\n \"high\"\n );\n throw new SecurityError(\n `Failed to construct preview URL: ${\n error instanceof Error ? error.message : \"Unknown error\"\n }`\n );\n }\n }\n\n // Production subdomain logic - enforce HTTPS\n try {\n // Always use HTTPS for production (non-localhost)\n const protocol = \"https\";\n const baseUrl = new URL(`${protocol}://${hostname}`);\n\n // Construct subdomain safely\n const subdomainHost = `${port}-${sanitizedSandboxId}.${hostname}`;\n baseUrl.hostname = subdomainHost;\n\n const finalUrl = baseUrl.toString();\n\n logSecurityEvent(\n \"PREVIEW_URL_CONSTRUCTED\",\n {\n port,\n sandboxId: sanitizedSandboxId,\n hostname,\n resultUrl: finalUrl,\n environment: \"production\",\n },\n \"low\"\n );\n\n return finalUrl;\n } catch (error) {\n logSecurityEvent(\n \"URL_CONSTRUCTION_FAILED\",\n {\n port,\n sandboxId: sanitizedSandboxId,\n hostname,\n error: error instanceof Error ? error.message : \"Unknown error\",\n },\n \"high\"\n );\n throw new SecurityError(\n `Failed to construct preview URL: ${\n error instanceof Error ? error.message : \"Unknown error\"\n }`\n );\n }\n }\n\n // Code Interpreter Methods\n\n /**\n * Create a new code execution context\n */\n async createCodeContext(\n options?: CreateContextOptions\n ): Promise<CodeContext> {\n return this.codeInterpreter.createCodeContext(options);\n }\n\n /**\n * Run code with streaming callbacks\n */\n async runCode(\n code: string,\n options?: RunCodeOptions\n ): Promise<ExecutionResult> {\n const execution = await this.codeInterpreter.runCode(code, options);\n // Convert to plain object for RPC serialization\n return execution.toJSON();\n }\n\n /**\n * Run code and return a streaming response\n */\n async runCodeStream(\n code: string,\n options?: RunCodeOptions\n ): Promise<ReadableStream> {\n return this.codeInterpreter.runCodeStream(code, options);\n }\n\n /**\n * List all code contexts\n */\n async listCodeContexts(): Promise<CodeContext[]> {\n return this.codeInterpreter.listCodeContexts();\n }\n\n /**\n * Delete a code context\n */\n async deleteCodeContext(contextId: string): Promise<void> {\n return this.codeInterpreter.deleteCodeContext(contextId);\n }\n\n // ============================================================================\n // Session Management (Simple Isolation)\n // ============================================================================\n\n /**\n * Create a new execution session with isolation\n * Returns a session object with exec() method\n */\n\n async createSession(options: {\n id?: string;\n env?: Record<string, string>;\n cwd?: string;\n isolation?: boolean;\n }): Promise<ExecutionSession> {\n const sessionId = options.id || `session-${Date.now()}`;\n \n await this.client.createSession({\n id: sessionId,\n env: options.env,\n cwd: options.cwd,\n isolation: options.isolation\n });\n // Return comprehensive ExecutionSession object that implements all ISandbox methods\n return {\n id: sessionId,\n \n // Command execution - clean method names\n exec: async (command: string, options?: ExecOptions) => {\n const result = await this.client.exec(sessionId, command);\n return {\n ...result,\n command,\n duration: 0,\n timestamp: new Date().toISOString()\n };\n },\n \n execStream: async (command: string, options?: StreamOptions) => {\n return await this.client.execStream(sessionId, command);\n },\n \n // Process management - route to session-aware methods\n startProcess: async (command: string, options?: ProcessOptions) => {\n // Use session-specific process management\n const response = await this.client.startProcess(command, sessionId, {\n processId: options?.processId,\n timeout: options?.timeout,\n env: options?.env,\n cwd: options?.cwd,\n encoding: options?.encoding,\n autoCleanup: options?.autoCleanup,\n });\n \n // Convert response to Process object with bound methods\n const process = response.process;\n return {\n id: process.id,\n pid: process.pid,\n command: process.command,\n status: process.status as ProcessStatus,\n startTime: new Date(process.startTime),\n endTime: process.endTime ? new Date(process.endTime) : undefined,\n exitCode: process.exitCode ?? undefined,\n kill: async (signal?: string) => {\n await this.client.killProcess(process.id);\n },\n getStatus: async () => {\n const resp = await this.client.getProcess(process.id);\n return resp.process?.status as ProcessStatus || \"error\";\n },\n getLogs: async () => {\n return await this.client.getProcessLogs(process.id);\n },\n };\n },\n \n listProcesses: async () => {\n // Get processes for this specific session\n const response = await this.client.listProcesses(sessionId);\n \n // Convert to Process objects with bound methods\n return response.processes.map(p => ({\n id: p.id,\n pid: p.pid,\n command: p.command,\n status: p.status as ProcessStatus,\n startTime: new Date(p.startTime),\n endTime: p.endTime ? new Date(p.endTime) : undefined,\n exitCode: p.exitCode ?? undefined,\n kill: async (signal?: string) => {\n await this.client.killProcess(p.id);\n },\n getStatus: async () => {\n const processResp = await this.client.getProcess(p.id);\n return processResp.process?.status as ProcessStatus || \"error\";\n },\n getLogs: async () => {\n return this.client.getProcessLogs(p.id);\n },\n }));\n },\n \n getProcess: async (id: string) => {\n const response = await this.client.getProcess(id);\n if (!response.process) return null;\n \n const p = response.process;\n return {\n id: p.id,\n pid: p.pid,\n command: p.command,\n status: p.status as ProcessStatus,\n startTime: new Date(p.startTime),\n endTime: p.endTime ? new Date(p.endTime) : undefined,\n exitCode: p.exitCode ?? undefined,\n kill: async (signal?: string) => {\n await this.client.killProcess(p.id);\n },\n getStatus: async () => {\n const processResp = await this.client.getProcess(p.id);\n return processResp.process?.status as ProcessStatus || \"error\";\n },\n getLogs: async () => {\n return this.client.getProcessLogs(p.id);\n },\n };\n },\n \n killProcess: async (id: string, signal?: string) => {\n await this.client.killProcess(id);\n },\n \n killAllProcesses: async () => {\n // Kill all processes for this specific session\n const response = await this.client.killAllProcesses(sessionId);\n return response.killedCount;\n },\n \n streamProcessLogs: async (processId: string, options?: { signal?: AbortSignal }) => {\n return await this.client.streamProcessLogs(processId, options);\n },\n \n getProcessLogs: async (id: string) => {\n return await this.client.getProcessLogs(id);\n },\n \n cleanupCompletedProcesses: async () => {\n // This would need a new endpoint to cleanup processes for a specific session\n // For now, return 0 as no cleanup is performed\n return 0;\n },\n \n // File operations - clean method names (no \"InSession\" suffix)\n writeFile: async (path: string, content: string, options?: { encoding?: string }) => {\n return await this.client.writeFile(path, content, options?.encoding, sessionId);\n },\n \n readFile: async (path: string, options?: { encoding?: string }) => {\n return await this.client.readFile(path, options?.encoding, sessionId);\n },\n \n mkdir: async (path: string, options?: { recursive?: boolean }) => {\n return await this.client.mkdir(path, options?.recursive, sessionId);\n },\n \n deleteFile: async (path: string) => {\n return await this.client.deleteFile(path, sessionId);\n },\n \n renameFile: async (oldPath: string, newPath: string) => {\n return await this.client.renameFile(oldPath, newPath, sessionId);\n },\n \n moveFile: async (sourcePath: string, destinationPath: string) => {\n return await this.client.moveFile(sourcePath, destinationPath, sessionId);\n },\n \n listFiles: async (path: string, options?: { recursive?: boolean; includeHidden?: boolean }) => {\n return await this.client.listFiles(path, sessionId, options);\n },\n \n gitCheckout: async (repoUrl: string, options?: { branch?: string; targetDir?: string }) => {\n return await this.client.gitCheckout(repoUrl, sessionId, options?.branch, options?.targetDir);\n },\n \n // Port management\n exposePort: async (port: number, options: { name?: string; hostname: string }) => {\n return await this.exposePort(port, options);\n },\n \n unexposePort: async (port: number) => {\n return await this.unexposePort(port);\n },\n \n getExposedPorts: async (hostname: string) => {\n return await this.getExposedPorts(hostname);\n },\n \n // Environment management\n setEnvVars: async (envVars: Record<string, string>) => {\n // TODO: Implement session-specific environment updates\n console.log(`[Session ${sessionId}] Environment variables update not yet implemented`);\n },\n \n // Code Interpreter API\n createCodeContext: async (options?: any) => {\n return await this.createCodeContext(options);\n },\n \n runCode: async (code: string, options?: any) => {\n return await this.runCode(code, options);\n },\n \n runCodeStream: async (code: string, options?: any) => {\n return await this.runCodeStream(code, options);\n },\n \n listCodeContexts: async () => {\n return await this.listCodeContexts();\n },\n \n deleteCodeContext: async (contextId: string) => {\n return await this.deleteCodeContext(contextId);\n }\n };\n }\n}\n","import { getSandbox, type Sandbox } from \"./sandbox\";\nimport {\n logSecurityEvent,\n sanitizeSandboxId,\n validatePort\n} from \"./security\";\n\nexport interface SandboxEnv {\n Sandbox: DurableObjectNamespace<Sandbox>;\n}\n\nexport interface RouteInfo {\n port: number;\n sandboxId: string;\n path: string;\n}\n\nexport async function proxyToSandbox<E extends SandboxEnv>(\n request: Request,\n env: E\n): Promise<Response | null> {\n try {\n const url = new URL(request.url);\n const routeInfo = extractSandboxRoute(url);\n\n if (!routeInfo) {\n return null; // Not a request to an exposed container port\n }\n\n const { sandboxId, port, path } = routeInfo;\n const sandbox = getSandbox(env.Sandbox, sandboxId);\n\n // Build proxy request with proper headers\n let proxyUrl: string;\n\n // Route based on the target port\n if (port !== 3000) {\n // Route directly to user's service on the specified port\n proxyUrl = `http://localhost:${port}${path}${url.search}`;\n } else {\n // Port 3000 is our control plane - route normally\n proxyUrl = `http://localhost:3000${path}${url.search}`;\n }\n\n const proxyRequest = new Request(proxyUrl, {\n method: request.method,\n headers: {\n ...Object.fromEntries(request.headers),\n 'X-Original-URL': request.url,\n 'X-Forwarded-Host': url.hostname,\n 'X-Forwarded-Proto': url.protocol.replace(':', ''),\n 'X-Sandbox-Name': sandboxId, // Pass the friendly name\n },\n body: request.body,\n });\n\n return sandbox.containerFetch(proxyRequest, port);\n } catch (error) {\n console.error('[Sandbox] Proxy routing error:', error);\n return new Response('Proxy routing error', { status: 500 });\n }\n}\n\nfunction extractSandboxRoute(url: URL): RouteInfo | null {\n // Parse subdomain pattern: port-sandboxId.domain\n const subdomainMatch = url.hostname.match(/^(\\d{4,5})-([^.-][^.]*[^.-]|[^.-])\\.(.+)$/);\n\n if (!subdomainMatch) {\n // Log malformed subdomain attempts\n if (url.hostname.includes('-') && url.hostname.includes('.')) {\n logSecurityEvent('MALFORMED_SUBDOMAIN_ATTEMPT', {\n hostname: url.hostname,\n url: url.toString()\n }, 'medium');\n }\n return null;\n }\n\n const portStr = subdomainMatch[1];\n const sandboxId = subdomainMatch[2];\n const domain = subdomainMatch[3];\n\n const port = parseInt(portStr, 10);\n if (!validatePort(port)) {\n logSecurityEvent('INVALID_PORT_IN_SUBDOMAIN', {\n port,\n portStr,\n sandboxId,\n hostname: url.hostname,\n url: url.toString()\n }, 'high');\n return null;\n }\n\n let sanitizedSandboxId: string;\n try {\n sanitizedSandboxId = sanitizeSandboxId(sandboxId);\n } catch (error) {\n logSecurityEvent('INVALID_SANDBOX_ID_IN_SUBDOMAIN', {\n sandboxId,\n port,\n hostname: url.hostname,\n url: url.toString(),\n error: error instanceof Error ? error.message : 'Unknown error'\n }, 'high');\n return null;\n }\n\n // DNS subdomain length limit is 63 characters\n if (sandboxId.length > 63) {\n logSecurityEvent('SANDBOX_ID_LENGTH_VIOLATION', {\n sandboxId,\n length: sandboxId.length,\n port,\n hostname: url.hostname\n }, 'medium');\n return null;\n }\n\n logSecurityEvent('SANDBOX_ROUTE_EXTRACTED', {\n port,\n sandboxId: sanitizedSandboxId,\n domain,\n path: url.pathname || \"/\",\n hostname: url.hostname\n }, 'low');\n\n return {\n port,\n sandboxId: sanitizedSandboxId,\n path: url.pathname || \"/\",\n };\n}\n\nexport function isLocalhostPattern(hostname: string): boolean {\n const hostPart = hostname.split(\":\")[0];\n return (\n hostPart === \"localhost\" ||\n hostPart === \"127.0.0.1\" ||\n hostPart === \"::1\" ||\n hostPart === \"[::1]\" ||\n hostPart === \"0.0.0.0\"\n );\n}\n"],"mappings":"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","names":["options"]}
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  Sandbox,
8
8
  getSandbox,
9
9
  proxyToSandbox
10
- } from "./chunk-GTGWAEED.js";
10
+ } from "./chunk-LFLJGISB.js";
11
11
  import "./chunk-6UAWTJ5S.js";
12
12
  import "./chunk-FKBV7CZS.js";
13
13
  import {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  isLocalhostPattern,
3
3
  proxyToSandbox
4
- } from "./chunk-GTGWAEED.js";
4
+ } from "./chunk-LFLJGISB.js";
5
5
  import "./chunk-6UAWTJ5S.js";
6
6
  import "./chunk-FKBV7CZS.js";
7
7
  import "./chunk-EGC5IYXA.js";
package/dist/sandbox.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Sandbox,
3
3
  getSandbox
4
- } from "./chunk-GTGWAEED.js";
4
+ } from "./chunk-LFLJGISB.js";
5
5
  import "./chunk-6UAWTJ5S.js";
6
6
  import "./chunk-FKBV7CZS.js";
7
7
  import "./chunk-EGC5IYXA.js";
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@cloudflare/sandbox",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/cloudflare/sandbox-sdk"
7
7
  },
8
8
  "description": "A sandboxed environment for running commands",
9
9
  "dependencies": {
10
- "@cloudflare/containers": "^0.0.25"
10
+ "@cloudflare/containers": "^0.0.27"
11
11
  },
12
12
  "tags": [
13
13
  "sandbox",
package/src/sandbox.ts CHANGED
@@ -135,6 +135,10 @@ export class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
135
135
  return parseInt(proxyMatch[1]);
136
136
  }
137
137
 
138
+ if (url.port) {
139
+ return parseInt(url.port);
140
+ }
141
+
138
142
  // All other requests go to control plane on port 3000
139
143
  // This includes /api/* endpoints and any other control requests
140
144
  return 3000;