@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
@@ -1,185 +0,0 @@
1
- import type { SessionManager } from "../isolation";
2
- import type { SessionExecRequest } from "../types";
3
-
4
- export async function handleExecuteRequest(
5
- req: Request,
6
- corsHeaders: Record<string, string>,
7
- sessionManager: SessionManager
8
- ) {
9
- try {
10
- const body = (await req.json()) as SessionExecRequest;
11
- const { id, command } = body;
12
-
13
- console.log(
14
- `[Container] Session exec request for '${id}': ${command}`
15
- );
16
-
17
- if (!id || !command) {
18
- return new Response(
19
- JSON.stringify({
20
- error: "Session ID and command are required",
21
- }),
22
- {
23
- status: 400,
24
- headers: {
25
- "Content-Type": "application/json",
26
- ...corsHeaders,
27
- },
28
- }
29
- );
30
- }
31
-
32
- const session = sessionManager.getSession(id);
33
- if (!session) {
34
- console.error(`[Container] Session '${id}' not found!`);
35
- const availableSessions = sessionManager.listSessions();
36
- console.log(
37
- `[Container] Available sessions: ${
38
- availableSessions.join(", ") || "none"
39
- }`
40
- );
41
-
42
- return new Response(
43
- JSON.stringify({
44
- error: `Session '${id}' not found`,
45
- availableSessions,
46
- }),
47
- {
48
- status: 404,
49
- headers: {
50
- "Content-Type": "application/json",
51
- ...corsHeaders,
52
- },
53
- }
54
- );
55
- }
56
-
57
- const result = await session.exec(command);
58
-
59
- return new Response(JSON.stringify(result), {
60
- headers: { "Content-Type": "application/json", ...corsHeaders },
61
- });
62
- } catch (error) {
63
- console.error("[Container] Session exec failed:", error);
64
- return new Response(
65
- JSON.stringify({
66
- error: "Command execution failed",
67
- message:
68
- error instanceof Error ? error.message : String(error),
69
- }),
70
- {
71
- status: 500,
72
- headers: {
73
- "Content-Type": "application/json",
74
- ...corsHeaders,
75
- },
76
- }
77
- );
78
- }
79
- }
80
-
81
- export async function handleStreamingExecuteRequest(
82
- req: Request,
83
- sessionManager: SessionManager,
84
- corsHeaders: Record<string, string>
85
- ) {
86
- try {
87
- const body = (await req.json()) as SessionExecRequest;
88
- const { id, command } = body;
89
-
90
- console.log(
91
- `[Container] Session streaming exec request for '${id}': ${command}`
92
- );
93
-
94
- if (!id || !command) {
95
- return new Response(
96
- JSON.stringify({
97
- error: "Session ID and command are required",
98
- }),
99
- {
100
- status: 400,
101
- headers: {
102
- "Content-Type": "application/json",
103
- ...corsHeaders,
104
- },
105
- }
106
- );
107
- }
108
-
109
- const session = sessionManager.getSession(id);
110
- if (!session) {
111
- console.error(`[Container] Session '${id}' not found!`);
112
- const availableSessions = sessionManager.listSessions();
113
-
114
- return new Response(
115
- JSON.stringify({
116
- error: `Session '${id}' not found`,
117
- availableSessions,
118
- }),
119
- {
120
- status: 404,
121
- headers: {
122
- "Content-Type": "application/json",
123
- ...corsHeaders,
124
- },
125
- }
126
- );
127
- }
128
-
129
- // Create a streaming response using the actual streaming method
130
- const stream = new ReadableStream({
131
- async start(controller) {
132
- try {
133
- // Use the streaming generator method
134
- for await (const event of session.execStream(command)) {
135
- // Forward each event as SSE
136
- controller.enqueue(
137
- new TextEncoder().encode(
138
- `data: ${JSON.stringify(event)}\n\n`
139
- )
140
- );
141
- }
142
- controller.close();
143
- } catch (error) {
144
- controller.enqueue(
145
- new TextEncoder().encode(
146
- `data: ${JSON.stringify({
147
- type: "error",
148
- message:
149
- error instanceof Error
150
- ? error.message
151
- : String(error),
152
- })}\n\n`
153
- )
154
- );
155
- controller.close();
156
- }
157
- },
158
- });
159
-
160
- return new Response(stream, {
161
- headers: {
162
- "Content-Type": "text/event-stream",
163
- "Cache-Control": "no-cache",
164
- Connection: "keep-alive",
165
- ...corsHeaders,
166
- },
167
- });
168
- } catch (error) {
169
- console.error("[Container] Session stream exec failed:", error);
170
- return new Response(
171
- JSON.stringify({
172
- error: "Stream execution failed",
173
- message:
174
- error instanceof Error ? error.message : String(error),
175
- }),
176
- {
177
- status: 500,
178
- headers: {
179
- "Content-Type": "application/json",
180
- ...corsHeaders,
181
- },
182
- }
183
- );
184
- }
185
- }
@@ -1,406 +0,0 @@
1
- import type { SessionManager } from "../isolation";
2
- import type {
3
- DeleteFileRequest,
4
- ListFilesRequest,
5
- MkdirRequest,
6
- MoveFileRequest,
7
- ReadFileRequest,
8
- RenameFileRequest,
9
- WriteFileRequest
10
- } from "../types";
11
-
12
- // Common path validation patterns
13
- const DANGEROUS_PATH_PATTERNS = [
14
- /^\/$/, // Root directory
15
- /^\/etc/, // System directories
16
- /^\/var/, // System directories
17
- /^\/usr/, // System directories
18
- /^\/bin/, // System directories
19
- /^\/sbin/, // System directories
20
- /^\/boot/, // System directories
21
- /^\/dev/, // System directories
22
- /^\/proc/, // System directories
23
- /^\/sys/, // System directories
24
- /^\/tmp\/\.\./, // Path traversal attempts
25
- /\.\./, // Path traversal attempts
26
- ];
27
-
28
- // Path validation utility
29
- function validatePath(...paths: string[]): string | null {
30
- for (const path of paths) {
31
- if (!path || typeof path !== "string") {
32
- return "Path is required and must be a string";
33
- }
34
-
35
- if (DANGEROUS_PATH_PATTERNS.some((pattern) => pattern.test(path))) {
36
- return "Dangerous path not allowed";
37
- }
38
- }
39
- return null;
40
- }
41
-
42
- // Common error response utility
43
- function createPathErrorResponse(
44
- error: string,
45
- corsHeaders: Record<string, string>
46
- ): Response {
47
- return new Response(
48
- JSON.stringify({ error }),
49
- {
50
- headers: {
51
- "Content-Type": "application/json",
52
- ...corsHeaders,
53
- },
54
- status: 400,
55
- }
56
- );
57
- }
58
-
59
- // Common server error response utility
60
- function createServerErrorResponse(
61
- operation: string,
62
- error: unknown,
63
- corsHeaders: Record<string, string>
64
- ): Response {
65
- console.error(`[Server] Error in ${operation}:`, error);
66
- return new Response(
67
- JSON.stringify({
68
- error: `Failed to ${operation.replace('handle', '').replace('Request', '').toLowerCase().replace('file', ' file')}`,
69
- message: error instanceof Error ? error.message : "Unknown error",
70
- }),
71
- {
72
- headers: {
73
- "Content-Type": "application/json",
74
- ...corsHeaders,
75
- },
76
- status: 500,
77
- }
78
- );
79
- }
80
-
81
- export async function handleMkdirRequest(
82
- req: Request,
83
- corsHeaders: Record<string, string>,
84
- sessionManager: SessionManager
85
- ): Promise<Response> {
86
- try {
87
- const body = (await req.json()) as MkdirRequest;
88
- const { path, recursive = false, sessionId } = body;
89
-
90
- // Validate path
91
- const pathError = validatePath(path);
92
- if (pathError) {
93
- return createPathErrorResponse(pathError, corsHeaders);
94
- }
95
-
96
- console.log(`[Server] Creating directory: ${path} (recursive: ${recursive})${sessionId ? ` in session: ${sessionId}` : ''}`);
97
-
98
- // Use specific session if provided, otherwise default session
99
- const result = sessionId
100
- ? await sessionManager.getSession(sessionId)?.mkdirOperation(path, recursive)
101
- : await sessionManager.mkdir(path, recursive);
102
-
103
- if (!result) {
104
- return createServerErrorResponse("handleMkdirRequest", new Error(`Session '${sessionId}' not found`), corsHeaders);
105
- }
106
-
107
- return new Response(
108
- JSON.stringify({
109
- exitCode: result.exitCode,
110
- path,
111
- recursive,
112
- stderr: "",
113
- stdout: "",
114
- success: result.success,
115
- timestamp: new Date().toISOString(),
116
- }),
117
- {
118
- headers: {
119
- "Content-Type": "application/json",
120
- ...corsHeaders,
121
- },
122
- }
123
- );
124
- } catch (error) {
125
- return createServerErrorResponse("handleMkdirRequest", error, corsHeaders);
126
- }
127
- }
128
-
129
- export async function handleWriteFileRequest(
130
- req: Request,
131
- corsHeaders: Record<string, string>,
132
- sessionManager: SessionManager
133
- ): Promise<Response> {
134
- try {
135
- const body = (await req.json()) as WriteFileRequest;
136
- const { path, content, encoding = "utf-8", sessionId } = body;
137
-
138
- // Validate path
139
- const pathError = validatePath(path);
140
- if (pathError) {
141
- return createPathErrorResponse(pathError, corsHeaders);
142
- }
143
-
144
- console.log(`[Server] Writing file: ${path} (content length: ${content.length})${sessionId ? ` in session: ${sessionId}` : ''}`);
145
-
146
- // Use specific session if provided, otherwise default session
147
- const result = sessionId
148
- ? await sessionManager.getSession(sessionId)?.writeFileOperation(path, content, encoding)
149
- : await sessionManager.writeFile(path, content, encoding);
150
-
151
- if (!result) {
152
- return createServerErrorResponse("handleWriteFileRequest", new Error(`Session '${sessionId}' not found`), corsHeaders);
153
- }
154
-
155
- return new Response(
156
- JSON.stringify({
157
- exitCode: result.exitCode,
158
- path,
159
- success: result.success,
160
- timestamp: new Date().toISOString(),
161
- }),
162
- {
163
- headers: {
164
- "Content-Type": "application/json",
165
- ...corsHeaders,
166
- },
167
- }
168
- );
169
- } catch (error) {
170
- return createServerErrorResponse("handleWriteFileRequest", error, corsHeaders);
171
- }
172
- }
173
-
174
- export async function handleReadFileRequest(
175
- req: Request,
176
- corsHeaders: Record<string, string>,
177
- sessionManager: SessionManager
178
- ): Promise<Response> {
179
- try {
180
- const body = (await req.json()) as ReadFileRequest;
181
- const { path, encoding = "utf-8", sessionId } = body;
182
-
183
- // Validate path
184
- const pathError = validatePath(path);
185
- if (pathError) {
186
- return createPathErrorResponse(pathError, corsHeaders);
187
- }
188
-
189
- console.log(`[Server] Reading file: ${path}${sessionId ? ` in session: ${sessionId}` : ''}`);
190
-
191
- // Use specific session if provided, otherwise default session
192
- const result = sessionId
193
- ? await sessionManager.getSession(sessionId)?.readFileOperation(path, encoding)
194
- : await sessionManager.readFile(path, encoding);
195
-
196
- if (!result) {
197
- return createServerErrorResponse("handleReadFileRequest", new Error(`Session '${sessionId}' not found`), corsHeaders);
198
- }
199
-
200
- return new Response(
201
- JSON.stringify({
202
- content: result.content,
203
- exitCode: result.exitCode,
204
- path,
205
- success: result.success,
206
- timestamp: new Date().toISOString(),
207
- }),
208
- {
209
- headers: {
210
- "Content-Type": "application/json",
211
- ...corsHeaders,
212
- },
213
- }
214
- );
215
- } catch (error) {
216
- return createServerErrorResponse("handleReadFileRequest", error, corsHeaders);
217
- }
218
- }
219
-
220
- export async function handleDeleteFileRequest(
221
- req: Request,
222
- corsHeaders: Record<string, string>,
223
- sessionManager: SessionManager
224
- ): Promise<Response> {
225
- try {
226
- const body = (await req.json()) as DeleteFileRequest;
227
- const { path, sessionId } = body;
228
-
229
- // Validate path
230
- const pathError = validatePath(path);
231
- if (pathError) {
232
- return createPathErrorResponse(pathError, corsHeaders);
233
- }
234
-
235
- console.log(`[Server] Deleting file: ${path}${sessionId ? ` in session: ${sessionId}` : ''}`);
236
-
237
- // Use specific session if provided, otherwise default session
238
- const result = sessionId
239
- ? await sessionManager.getSession(sessionId)?.deleteFileOperation(path)
240
- : await sessionManager.deleteFile(path);
241
-
242
- if (!result) {
243
- return createServerErrorResponse("handleDeleteFileRequest", new Error(`Session '${sessionId}' not found`), corsHeaders);
244
- }
245
-
246
- return new Response(
247
- JSON.stringify({
248
- exitCode: result.exitCode,
249
- path,
250
- success: result.success,
251
- timestamp: new Date().toISOString(),
252
- }),
253
- {
254
- headers: {
255
- "Content-Type": "application/json",
256
- ...corsHeaders,
257
- },
258
- }
259
- );
260
- } catch (error) {
261
- return createServerErrorResponse("handleDeleteFileRequest", error, corsHeaders);
262
- }
263
- }
264
-
265
- export async function handleRenameFileRequest(
266
- req: Request,
267
- corsHeaders: Record<string, string>,
268
- sessionManager: SessionManager
269
- ): Promise<Response> {
270
- try {
271
- const body = (await req.json()) as RenameFileRequest;
272
- const { oldPath, newPath, sessionId } = body;
273
-
274
- // Validate paths
275
- const pathError = validatePath(oldPath, newPath);
276
- if (pathError) {
277
- return createPathErrorResponse(pathError, corsHeaders);
278
- }
279
-
280
- console.log(`[Server] Renaming file: ${oldPath} -> ${newPath}${sessionId ? ` in session: ${sessionId}` : ''}`);
281
-
282
- // Use specific session if provided, otherwise default session
283
- const result = sessionId
284
- ? await sessionManager.getSession(sessionId)?.renameFileOperation(oldPath, newPath)
285
- : await sessionManager.renameFile(oldPath, newPath);
286
-
287
- if (!result) {
288
- return createServerErrorResponse("handleRenameFileRequest", new Error(`Session '${sessionId}' not found`), corsHeaders);
289
- }
290
-
291
- return new Response(
292
- JSON.stringify({
293
- exitCode: result.exitCode,
294
- newPath,
295
- oldPath,
296
- success: result.success,
297
- timestamp: new Date().toISOString(),
298
- }),
299
- {
300
- headers: {
301
- "Content-Type": "application/json",
302
- ...corsHeaders,
303
- },
304
- }
305
- );
306
- } catch (error) {
307
- return createServerErrorResponse("handleRenameFileRequest", error, corsHeaders);
308
- }
309
- }
310
-
311
- export async function handleMoveFileRequest(
312
- req: Request,
313
- corsHeaders: Record<string, string>,
314
- sessionManager: SessionManager
315
- ): Promise<Response> {
316
- try {
317
- const body = (await req.json()) as MoveFileRequest;
318
- const { sourcePath, destinationPath, sessionId } = body;
319
-
320
- // Validate paths
321
- const pathError = validatePath(sourcePath, destinationPath);
322
- if (pathError) {
323
- return createPathErrorResponse(pathError, corsHeaders);
324
- }
325
-
326
- console.log(`[Server] Moving file: ${sourcePath} -> ${destinationPath}${sessionId ? ` in session: ${sessionId}` : ''}`);
327
-
328
- // Use specific session if provided, otherwise default session
329
- const result = sessionId
330
- ? await sessionManager.getSession(sessionId)?.moveFileOperation(sourcePath, destinationPath)
331
- : await sessionManager.moveFile(sourcePath, destinationPath);
332
-
333
- if (!result) {
334
- return createServerErrorResponse("handleMoveFileRequest", new Error(`Session '${sessionId}' not found`), corsHeaders);
335
- }
336
-
337
- return new Response(
338
- JSON.stringify({
339
- destinationPath,
340
- exitCode: result.exitCode,
341
- sourcePath,
342
- success: result.success,
343
- timestamp: new Date().toISOString(),
344
- }),
345
- {
346
- headers: {
347
- "Content-Type": "application/json",
348
- ...corsHeaders,
349
- },
350
- }
351
- );
352
- } catch (error) {
353
- return createServerErrorResponse("handleMoveFileRequest", error, corsHeaders);
354
- }
355
- }
356
-
357
- export async function handleListFilesRequest(
358
- req: Request,
359
- corsHeaders: Record<string, string>,
360
- sessionManager: SessionManager
361
- ): Promise<Response> {
362
- try {
363
- const body = (await req.json()) as ListFilesRequest;
364
- const { path, options, sessionId } = body;
365
-
366
- // Validate path (note: listFiles allows root directory listing)
367
- const pathError = validatePath(path);
368
- if (pathError && pathError !== "Dangerous path not allowed") {
369
- return createPathErrorResponse(pathError, corsHeaders);
370
- }
371
-
372
- // For listFiles, we allow root directory but still check other dangerous patterns
373
- if (path !== "/" && DANGEROUS_PATH_PATTERNS.slice(1).some((pattern) => pattern.test(path))) {
374
- return createPathErrorResponse("Dangerous path not allowed", corsHeaders);
375
- }
376
-
377
- console.log(`[Server] Listing files in: ${path}${sessionId ? ` in session: ${sessionId}` : ''}`);
378
-
379
- // Use specific session if provided, otherwise default session
380
- const result = sessionId
381
- ? await sessionManager.getSession(sessionId)?.listFilesOperation(path, options)
382
- : await sessionManager.listFiles(path, options);
383
-
384
- if (!result) {
385
- return createServerErrorResponse("handleListFilesRequest", new Error(`Session '${sessionId}' not found`), corsHeaders);
386
- }
387
-
388
- return new Response(
389
- JSON.stringify({
390
- exitCode: result.exitCode,
391
- files: result.files,
392
- path,
393
- success: result.success,
394
- timestamp: new Date().toISOString(),
395
- }),
396
- {
397
- headers: {
398
- "Content-Type": "application/json",
399
- ...corsHeaders,
400
- },
401
- }
402
- );
403
- } catch (error) {
404
- return createServerErrorResponse("handleListFilesRequest", error, corsHeaders);
405
- }
406
- }