@cloudflare/sandbox 0.0.0-e1fa354 → 0.0.0-e489cbb

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.
Files changed (94) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/Dockerfile +107 -38
  3. package/README.md +89 -771
  4. package/dist/chunk-53JFOF7F.js +2352 -0
  5. package/dist/chunk-53JFOF7F.js.map +1 -0
  6. package/dist/chunk-BFVUNTP4.js +104 -0
  7. package/dist/chunk-BFVUNTP4.js.map +1 -0
  8. package/dist/chunk-EKSWCBCA.js +86 -0
  9. package/dist/chunk-EKSWCBCA.js.map +1 -0
  10. package/dist/chunk-JXZMAU2C.js +559 -0
  11. package/dist/chunk-JXZMAU2C.js.map +1 -0
  12. package/dist/chunk-Z532A7QC.js +78 -0
  13. package/dist/chunk-Z532A7QC.js.map +1 -0
  14. package/dist/file-stream.d.ts +43 -0
  15. package/dist/file-stream.js +9 -0
  16. package/dist/file-stream.js.map +1 -0
  17. package/dist/index.d.ts +9 -0
  18. package/dist/index.js +66 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/interpreter.d.ts +33 -0
  21. package/dist/interpreter.js +8 -0
  22. package/dist/interpreter.js.map +1 -0
  23. package/dist/request-handler.d.ts +18 -0
  24. package/dist/request-handler.js +12 -0
  25. package/dist/request-handler.js.map +1 -0
  26. package/dist/sandbox-D9K2ypln.d.ts +583 -0
  27. package/dist/sandbox.d.ts +4 -0
  28. package/dist/sandbox.js +12 -0
  29. package/dist/sandbox.js.map +1 -0
  30. package/dist/security.d.ts +31 -0
  31. package/dist/security.js +13 -0
  32. package/dist/security.js.map +1 -0
  33. package/dist/sse-parser.d.ts +28 -0
  34. package/dist/sse-parser.js +11 -0
  35. package/dist/sse-parser.js.map +1 -0
  36. package/package.json +13 -5
  37. package/src/clients/base-client.ts +280 -0
  38. package/src/clients/command-client.ts +115 -0
  39. package/src/clients/file-client.ts +269 -0
  40. package/src/clients/git-client.ts +92 -0
  41. package/src/clients/index.ts +63 -0
  42. package/src/{jupyter-client.ts → clients/interpreter-client.ts} +148 -168
  43. package/src/clients/port-client.ts +105 -0
  44. package/src/clients/process-client.ts +177 -0
  45. package/src/clients/sandbox-client.ts +41 -0
  46. package/src/clients/types.ts +84 -0
  47. package/src/clients/utility-client.ts +94 -0
  48. package/src/errors/adapter.ts +180 -0
  49. package/src/errors/classes.ts +469 -0
  50. package/src/errors/index.ts +105 -0
  51. package/src/file-stream.ts +164 -0
  52. package/src/index.ts +82 -53
  53. package/src/interpreter.ts +22 -13
  54. package/src/request-handler.ts +69 -43
  55. package/src/sandbox.ts +697 -527
  56. package/src/security.ts +14 -23
  57. package/src/sse-parser.ts +4 -8
  58. package/startup.sh +3 -0
  59. package/tests/base-client.test.ts +328 -0
  60. package/tests/command-client.test.ts +407 -0
  61. package/tests/file-client.test.ts +643 -0
  62. package/tests/file-stream.test.ts +306 -0
  63. package/tests/git-client.test.ts +328 -0
  64. package/tests/port-client.test.ts +301 -0
  65. package/tests/process-client.test.ts +658 -0
  66. package/tests/sandbox.test.ts +465 -0
  67. package/tests/sse-parser.test.ts +290 -0
  68. package/tests/utility-client.test.ts +266 -0
  69. package/tests/wrangler.jsonc +35 -0
  70. package/tsconfig.json +9 -1
  71. package/vitest.config.ts +31 -0
  72. package/container_src/bun.lock +0 -122
  73. package/container_src/circuit-breaker.ts +0 -121
  74. package/container_src/control-process.ts +0 -784
  75. package/container_src/handler/exec.ts +0 -185
  76. package/container_src/handler/file.ts +0 -406
  77. package/container_src/handler/git.ts +0 -130
  78. package/container_src/handler/ports.ts +0 -314
  79. package/container_src/handler/process.ts +0 -568
  80. package/container_src/handler/session.ts +0 -92
  81. package/container_src/index.ts +0 -601
  82. package/container_src/isolation.ts +0 -1038
  83. package/container_src/jupyter-server.ts +0 -579
  84. package/container_src/jupyter-service.ts +0 -461
  85. package/container_src/jupyter_config.py +0 -48
  86. package/container_src/mime-processor.ts +0 -255
  87. package/container_src/package.json +0 -18
  88. package/container_src/shell-escape.ts +0 -42
  89. package/container_src/startup.sh +0 -84
  90. package/container_src/types.ts +0 -131
  91. package/src/client.ts +0 -1009
  92. package/src/errors.ts +0 -218
  93. package/src/interpreter-types.ts +0 -383
  94. package/src/types.ts +0 -502
@@ -0,0 +1,78 @@
1
+ // src/security.ts
2
+ var SecurityError = class extends Error {
3
+ constructor(message, code) {
4
+ super(message);
5
+ this.code = code;
6
+ this.name = "SecurityError";
7
+ }
8
+ };
9
+ function validatePort(port) {
10
+ if (!Number.isInteger(port)) {
11
+ return false;
12
+ }
13
+ if (port < 1024 || port > 65535) {
14
+ return false;
15
+ }
16
+ const reservedPorts = [
17
+ 3e3,
18
+ // Control plane port
19
+ 8787
20
+ // Common wrangler dev port
21
+ ];
22
+ if (reservedPorts.includes(port)) {
23
+ return false;
24
+ }
25
+ return true;
26
+ }
27
+ function sanitizeSandboxId(id) {
28
+ if (!id || id.length > 63) {
29
+ throw new SecurityError(
30
+ "Sandbox ID must be 1-63 characters long.",
31
+ "INVALID_SANDBOX_ID_LENGTH"
32
+ );
33
+ }
34
+ if (id.startsWith("-") || id.endsWith("-")) {
35
+ throw new SecurityError(
36
+ "Sandbox ID cannot start or end with hyphens (DNS requirement).",
37
+ "INVALID_SANDBOX_ID_HYPHENS"
38
+ );
39
+ }
40
+ const reservedNames = [
41
+ "www",
42
+ "api",
43
+ "admin",
44
+ "root",
45
+ "system",
46
+ "cloudflare",
47
+ "workers"
48
+ ];
49
+ const lowerCaseId = id.toLowerCase();
50
+ if (reservedNames.includes(lowerCaseId)) {
51
+ throw new SecurityError(
52
+ `Reserved sandbox ID '${id}' is not allowed.`,
53
+ "RESERVED_SANDBOX_ID"
54
+ );
55
+ }
56
+ return id;
57
+ }
58
+ function validateLanguage(language) {
59
+ if (!language) {
60
+ return;
61
+ }
62
+ const supportedLanguages = ["python", "python3", "javascript", "js", "node", "typescript", "ts"];
63
+ const normalized = language.toLowerCase();
64
+ if (!supportedLanguages.includes(normalized)) {
65
+ throw new SecurityError(
66
+ `Unsupported language '${language}'. Supported languages: python, javascript, typescript`,
67
+ "INVALID_LANGUAGE"
68
+ );
69
+ }
70
+ }
71
+
72
+ export {
73
+ SecurityError,
74
+ validatePort,
75
+ sanitizeSandboxId,
76
+ validateLanguage
77
+ };
78
+ //# sourceMappingURL=chunk-Z532A7QC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/security.ts"],"sourcesContent":["/**\n * Security utilities for URL construction and input validation\n *\n * This module contains critical security functions to prevent:\n * - URL injection attacks\n * - SSRF (Server-Side Request Forgery) attacks\n * - DNS rebinding attacks\n * - Host header injection\n * - Open redirect vulnerabilities\n */\n\nexport class SecurityError extends Error {\n constructor(message: string, public readonly code?: string) {\n super(message);\n this.name = 'SecurityError';\n }\n}\n\n/**\n * Validates port numbers for sandbox services\n * Only allows non-system ports to prevent conflicts and security issues\n */\nexport function validatePort(port: number): boolean {\n // Must be a valid integer\n if (!Number.isInteger(port)) {\n return false;\n }\n\n // Only allow non-system ports (1024-65535)\n if (port < 1024 || port > 65535) {\n return false;\n }\n\n // Exclude ports reserved by our system\n const reservedPorts = [\n 3000, // Control plane port\n 8787, // Common wrangler dev port\n ];\n\n if (reservedPorts.includes(port)) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Sanitizes and validates sandbox IDs for DNS compliance and security\n * Only enforces critical requirements - allows maximum developer flexibility\n */\nexport function sanitizeSandboxId(id: string): string {\n // Basic validation: not empty, reasonable length limit (DNS subdomain limit is 63 chars)\n if (!id || id.length > 63) {\n throw new SecurityError(\n 'Sandbox ID must be 1-63 characters long.',\n 'INVALID_SANDBOX_ID_LENGTH'\n );\n }\n\n // DNS compliance: cannot start or end with hyphens (RFC requirement)\n if (id.startsWith('-') || id.endsWith('-')) {\n throw new SecurityError(\n 'Sandbox ID cannot start or end with hyphens (DNS requirement).',\n 'INVALID_SANDBOX_ID_HYPHENS'\n );\n }\n\n // Prevent reserved names that cause technical conflicts\n const reservedNames = [\n 'www', 'api', 'admin', 'root', 'system',\n 'cloudflare', 'workers'\n ];\n\n const lowerCaseId = id.toLowerCase();\n if (reservedNames.includes(lowerCaseId)) {\n throw new SecurityError(\n `Reserved sandbox ID '${id}' is not allowed.`,\n 'RESERVED_SANDBOX_ID'\n );\n }\n\n return id;\n}\n\n\n/**\n * Validates language for code interpreter\n * Only allows supported languages\n */\nexport function validateLanguage(language: string | undefined): void {\n if (!language) {\n return; // undefined is valid, will default to python\n }\n\n const supportedLanguages = ['python', 'python3', 'javascript', 'js', 'node', 'typescript', 'ts'];\n const normalized = language.toLowerCase();\n\n if (!supportedLanguages.includes(normalized)) {\n throw new SecurityError(\n `Unsupported language '${language}'. Supported languages: python, javascript, typescript`,\n 'INVALID_LANGUAGE'\n );\n }\n}\n"],"mappings":";AAWO,IAAM,gBAAN,cAA4B,MAAM;AAAA,EACvC,YAAY,SAAiC,MAAe;AAC1D,UAAM,OAAO;AAD8B;AAE3C,SAAK,OAAO;AAAA,EACd;AACF;AAMO,SAAS,aAAa,MAAuB;AAElD,MAAI,CAAC,OAAO,UAAU,IAAI,GAAG;AAC3B,WAAO;AAAA,EACT;AAGA,MAAI,OAAO,QAAQ,OAAO,OAAO;AAC/B,WAAO;AAAA,EACT;AAGA,QAAM,gBAAgB;AAAA,IACpB;AAAA;AAAA,IACA;AAAA;AAAA,EACF;AAEA,MAAI,cAAc,SAAS,IAAI,GAAG;AAChC,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAMO,SAAS,kBAAkB,IAAoB;AAEpD,MAAI,CAAC,MAAM,GAAG,SAAS,IAAI;AACzB,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAGA,MAAI,GAAG,WAAW,GAAG,KAAK,GAAG,SAAS,GAAG,GAAG;AAC1C,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAGA,QAAM,gBAAgB;AAAA,IACpB;AAAA,IAAO;AAAA,IAAO;AAAA,IAAS;AAAA,IAAQ;AAAA,IAC/B;AAAA,IAAc;AAAA,EAChB;AAEA,QAAM,cAAc,GAAG,YAAY;AACnC,MAAI,cAAc,SAAS,WAAW,GAAG;AACvC,UAAM,IAAI;AAAA,MACR,wBAAwB,EAAE;AAAA,MAC1B;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAOO,SAAS,iBAAiB,UAAoC;AACnE,MAAI,CAAC,UAAU;AACb;AAAA,EACF;AAEA,QAAM,qBAAqB,CAAC,UAAU,WAAW,cAAc,MAAM,QAAQ,cAAc,IAAI;AAC/F,QAAM,aAAa,SAAS,YAAY;AAExC,MAAI,CAAC,mBAAmB,SAAS,UAAU,GAAG;AAC5C,UAAM,IAAI;AAAA,MACR,yBAAyB,QAAQ;AAAA,MACjC;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,43 @@
1
+ import { FileChunk, FileMetadata } from '@repo/shared';
2
+
3
+ /**
4
+ * Stream a file from the sandbox with automatic base64 decoding for binary files
5
+ *
6
+ * @param stream - The ReadableStream from readFileStream()
7
+ * @returns AsyncGenerator that yields FileChunk (string for text, Uint8Array for binary)
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const stream = await sandbox.readFileStream('/path/to/file.png');
12
+ * for await (const chunk of streamFile(stream)) {
13
+ * if (chunk instanceof Uint8Array) {
14
+ * // Binary chunk
15
+ * console.log('Binary chunk:', chunk.length, 'bytes');
16
+ * } else {
17
+ * // Text chunk
18
+ * console.log('Text chunk:', chunk);
19
+ * }
20
+ * }
21
+ * ```
22
+ */
23
+ declare function streamFile(stream: ReadableStream<Uint8Array>): AsyncGenerator<FileChunk, FileMetadata>;
24
+ /**
25
+ * Collect an entire file into memory from a stream
26
+ *
27
+ * @param stream - The ReadableStream from readFileStream()
28
+ * @returns Object containing the file content and metadata
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * const stream = await sandbox.readFileStream('/path/to/file.txt');
33
+ * const { content, metadata } = await collectFile(stream);
34
+ * console.log('Content:', content);
35
+ * console.log('MIME type:', metadata.mimeType);
36
+ * ```
37
+ */
38
+ declare function collectFile(stream: ReadableStream<Uint8Array>): Promise<{
39
+ content: string | Uint8Array;
40
+ metadata: FileMetadata;
41
+ }>;
42
+
43
+ export { collectFile, streamFile };
@@ -0,0 +1,9 @@
1
+ import {
2
+ collectFile,
3
+ streamFile
4
+ } from "./chunk-BFVUNTP4.js";
5
+ export {
6
+ collectFile,
7
+ streamFile
8
+ };
9
+ //# sourceMappingURL=file-stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,9 @@
1
+ export { B as BaseApiResponse, C as CommandClient, f as CommandExecuteResponse, c as CommandsResponse, d as ContainerStub, E as ErrorResponse, e as ExecuteRequest, p as ExecutionCallbacks, h as ExposePortRequest, F as FileClient, i as FileOperationRequest, j as GitCheckoutRequest, G as GitClient, I as InterpreterClient, M as MkdirRequest, k as PingResponse, P as PortClient, a as ProcessClient, R as ReadFileRequest, l as RequestConfig, m as ResponseHandler, b as Sandbox, S as SandboxClient, H as SandboxClientOptions, n as SessionRequest, o as UnexposePortRequest, U as UtilityClient, W as WriteFileRequest, g as getSandbox } from './sandbox-D9K2ypln.js';
2
+ export * from '@repo/shared';
3
+ export { BaseExecOptions, ExecEvent, ExecOptions, ExecResult, FileChunk, FileMetadata, FileStreamEvent, GitCheckoutResult, ISandbox, LogEvent, PortCloseResult, PortExposeResult, PortListResult, Process, ProcessCleanupResult, ProcessInfoResult, ProcessKillResult, ProcessListResult, ProcessLogsResult, ProcessOptions, ProcessStartResult, ProcessStatus, StartProcessRequest, StreamOptions, isExecResult, isProcess, isProcessStatus } from '@repo/shared';
4
+ export { collectFile, streamFile } from './file-stream.js';
5
+ export { CodeInterpreter } from './interpreter.js';
6
+ export { RouteInfo, SandboxEnv, proxyToSandbox } from './request-handler.js';
7
+ export { asyncIterableToSSEStream, parseSSEStream, responseToAsyncIterable } from './sse-parser.js';
8
+ import 'cloudflare:workers';
9
+ import '@cloudflare/containers';
package/dist/index.js ADDED
@@ -0,0 +1,66 @@
1
+ import {
2
+ collectFile,
3
+ streamFile
4
+ } from "./chunk-BFVUNTP4.js";
5
+ import {
6
+ CommandClient,
7
+ FileClient,
8
+ GitClient,
9
+ PortClient,
10
+ ProcessClient,
11
+ Sandbox,
12
+ SandboxClient,
13
+ UtilityClient,
14
+ getSandbox,
15
+ proxyToSandbox
16
+ } from "./chunk-53JFOF7F.js";
17
+ import {
18
+ CodeInterpreter,
19
+ Execution,
20
+ LogLevel,
21
+ ResultImpl,
22
+ TraceContext,
23
+ createLogger,
24
+ createNoOpLogger,
25
+ getLogger,
26
+ isExecResult,
27
+ isProcess,
28
+ isProcessStatus,
29
+ runWithLogger
30
+ } from "./chunk-JXZMAU2C.js";
31
+ import "./chunk-Z532A7QC.js";
32
+ import {
33
+ asyncIterableToSSEStream,
34
+ parseSSEStream,
35
+ responseToAsyncIterable
36
+ } from "./chunk-EKSWCBCA.js";
37
+ export {
38
+ CodeInterpreter,
39
+ CommandClient,
40
+ Execution,
41
+ FileClient,
42
+ GitClient,
43
+ LogLevel as LogLevelEnum,
44
+ PortClient,
45
+ ProcessClient,
46
+ ResultImpl,
47
+ Sandbox,
48
+ SandboxClient,
49
+ TraceContext,
50
+ UtilityClient,
51
+ asyncIterableToSSEStream,
52
+ collectFile,
53
+ createLogger,
54
+ createNoOpLogger,
55
+ getLogger,
56
+ getSandbox,
57
+ isExecResult,
58
+ isProcess,
59
+ isProcessStatus,
60
+ parseSSEStream,
61
+ proxyToSandbox,
62
+ responseToAsyncIterable,
63
+ runWithLogger,
64
+ streamFile
65
+ };
66
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,33 @@
1
+ import { CreateContextOptions, CodeContext, RunCodeOptions, Execution } from '@repo/shared';
2
+ import { b as Sandbox } from './sandbox-D9K2ypln.js';
3
+ import 'cloudflare:workers';
4
+ import '@cloudflare/containers';
5
+
6
+ declare class CodeInterpreter {
7
+ private interpreterClient;
8
+ private contexts;
9
+ constructor(sandbox: Sandbox);
10
+ /**
11
+ * Create a new code execution context
12
+ */
13
+ createCodeContext(options?: CreateContextOptions): Promise<CodeContext>;
14
+ /**
15
+ * Run code with optional context
16
+ */
17
+ runCode(code: string, options?: RunCodeOptions): Promise<Execution>;
18
+ /**
19
+ * Run code and return a streaming response
20
+ */
21
+ runCodeStream(code: string, options?: RunCodeOptions): Promise<ReadableStream>;
22
+ /**
23
+ * List all code contexts
24
+ */
25
+ listCodeContexts(): Promise<CodeContext[]>;
26
+ /**
27
+ * Delete a code context
28
+ */
29
+ deleteCodeContext(contextId: string): Promise<void>;
30
+ private getOrCreateDefaultContext;
31
+ }
32
+
33
+ export { CodeInterpreter };
@@ -0,0 +1,8 @@
1
+ import {
2
+ CodeInterpreter
3
+ } from "./chunk-JXZMAU2C.js";
4
+ import "./chunk-Z532A7QC.js";
5
+ export {
6
+ CodeInterpreter
7
+ };
8
+ //# sourceMappingURL=interpreter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,18 @@
1
+ import { b as Sandbox } from './sandbox-D9K2ypln.js';
2
+ import '@repo/shared';
3
+ import 'cloudflare:workers';
4
+ import '@cloudflare/containers';
5
+
6
+ interface SandboxEnv {
7
+ Sandbox: DurableObjectNamespace<Sandbox>;
8
+ }
9
+ interface RouteInfo {
10
+ port: number;
11
+ sandboxId: string;
12
+ path: string;
13
+ token: string;
14
+ }
15
+ declare function proxyToSandbox<E extends SandboxEnv>(request: Request, env: E): Promise<Response | null>;
16
+ declare function isLocalhostPattern(hostname: string): boolean;
17
+
18
+ export { type RouteInfo, type SandboxEnv, isLocalhostPattern, proxyToSandbox };
@@ -0,0 +1,12 @@
1
+ import {
2
+ isLocalhostPattern,
3
+ proxyToSandbox
4
+ } from "./chunk-53JFOF7F.js";
5
+ import "./chunk-JXZMAU2C.js";
6
+ import "./chunk-Z532A7QC.js";
7
+ import "./chunk-EKSWCBCA.js";
8
+ export {
9
+ isLocalhostPattern,
10
+ proxyToSandbox
11
+ };
12
+ //# sourceMappingURL=request-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}