@cloudflare/sandbox 0.2.3 → 0.3.0

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 (44) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/Dockerfile +9 -11
  3. package/README.md +69 -7
  4. package/container_src/control-process.ts +784 -0
  5. package/container_src/handler/exec.ts +99 -254
  6. package/container_src/handler/file.ts +204 -642
  7. package/container_src/handler/git.ts +28 -80
  8. package/container_src/handler/process.ts +443 -515
  9. package/container_src/handler/session.ts +92 -0
  10. package/container_src/index.ts +74 -129
  11. package/container_src/isolation.ts +1039 -0
  12. package/container_src/jupyter-service.ts +8 -5
  13. package/container_src/shell-escape.ts +42 -0
  14. package/container_src/types.ts +35 -12
  15. package/dist/{chunk-VTKZL632.js → chunk-BEQUGUY4.js} +2 -2
  16. package/dist/{chunk-4KELYYKS.js → chunk-GTGWAEED.js} +239 -265
  17. package/dist/chunk-GTGWAEED.js.map +1 -0
  18. package/dist/{chunk-CUHYLCMT.js → chunk-SMUEY5JR.js} +111 -99
  19. package/dist/chunk-SMUEY5JR.js.map +1 -0
  20. package/dist/{client-bzEV222a.d.ts → client-Dny_ro_v.d.ts} +48 -84
  21. package/dist/client.d.ts +1 -1
  22. package/dist/client.js +1 -1
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.js +8 -9
  25. package/dist/interpreter.d.ts +2 -2
  26. package/dist/jupyter-client.d.ts +2 -2
  27. package/dist/jupyter-client.js +2 -2
  28. package/dist/request-handler.d.ts +3 -3
  29. package/dist/request-handler.js +3 -5
  30. package/dist/sandbox.d.ts +2 -2
  31. package/dist/sandbox.js +3 -5
  32. package/dist/types.d.ts +127 -21
  33. package/dist/types.js +35 -9
  34. package/dist/types.js.map +1 -1
  35. package/package.json +1 -1
  36. package/src/client.ts +175 -187
  37. package/src/index.ts +23 -13
  38. package/src/sandbox.ts +297 -332
  39. package/src/types.ts +125 -24
  40. package/dist/chunk-4KELYYKS.js.map +0 -1
  41. package/dist/chunk-CUHYLCMT.js.map +0 -1
  42. package/dist/chunk-S5FFBU4Y.js +0 -46
  43. package/dist/chunk-S5FFBU4Y.js.map +0 -1
  44. /package/dist/{chunk-VTKZL632.js.map → chunk-BEQUGUY4.js.map} +0 -0
@@ -1,160 +1,116 @@
1
- import { spawn } from "node:child_process";
2
- import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
3
- import { dirname } from "node:path";
1
+ import type { SessionManager } from "../isolation";
4
2
  import type {
5
3
  DeleteFileRequest,
4
+ ListFilesRequest,
6
5
  MkdirRequest,
7
6
  MoveFileRequest,
8
7
  ReadFileRequest,
9
8
  RenameFileRequest,
10
- SessionData,
11
9
  WriteFileRequest
12
10
  } from "../types";
13
11
 
14
- function executeMkdir(
15
- sessions: Map<string, SessionData>,
16
- path: string,
17
- recursive: boolean,
18
- sessionId?: string
19
- ): Promise<{
20
- success: boolean;
21
- stdout: string;
22
- stderr: string;
23
- exitCode: number;
24
- }> {
25
- return new Promise((resolve, reject) => {
26
- const args = `${recursive ? "-p " : ""} ${path}`;
27
- const mkdirChild = spawn(`mkdir ${args}`, {
28
- shell: true,
29
- stdio: ["pipe", "pipe", "pipe"],
30
- });
31
-
32
- // Store the process reference for cleanup if sessionId is provided
33
- if (sessionId && sessions.has(sessionId)) {
34
- const session = sessions.get(sessionId)!;
35
- session.activeProcess = mkdirChild;
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";
36
33
  }
34
+
35
+ if (DANGEROUS_PATH_PATTERNS.some((pattern) => pattern.test(path))) {
36
+ return "Dangerous path not allowed";
37
+ }
38
+ }
39
+ return null;
40
+ }
37
41
 
38
- let stdout = "";
39
- let stderr = "";
40
-
41
- mkdirChild.stdout?.on("data", (data) => {
42
- stdout += data.toString();
43
- });
44
-
45
- mkdirChild.stderr?.on("data", (data) => {
46
- stderr += data.toString();
47
- });
48
-
49
- mkdirChild.on("close", (code) => {
50
- // Clear the active process reference
51
- if (sessionId && sessions.has(sessionId)) {
52
- const session = sessions.get(sessionId)!;
53
- session.activeProcess = null;
54
- }
55
-
56
- if (code === 0) {
57
- console.log(`[Server] Directory created successfully: ${path}`);
58
- resolve({
59
- exitCode: code || 0,
60
- stderr,
61
- stdout,
62
- success: true,
63
- });
64
- } else {
65
- console.error(
66
- `[Server] Failed to create directory: ${path}, Exit code: ${code}`
67
- );
68
- resolve({
69
- exitCode: code || 1,
70
- stderr,
71
- stdout,
72
- success: false,
73
- });
74
- }
75
- });
76
-
77
- mkdirChild.on("error", (error) => {
78
- // Clear the active process reference
79
- if (sessionId && sessions.has(sessionId)) {
80
- const session = sessions.get(sessionId)!;
81
- session.activeProcess = null;
82
- }
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
+ }
83
58
 
84
- console.error(`[Server] Error creating directory: ${path}`, error);
85
- reject(error);
86
- });
87
- });
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
+ );
88
79
  }
89
80
 
90
81
  export async function handleMkdirRequest(
91
- sessions: Map<string, SessionData>,
92
82
  req: Request,
93
- corsHeaders: Record<string, string>
83
+ corsHeaders: Record<string, string>,
84
+ sessionManager: SessionManager
94
85
  ): Promise<Response> {
95
86
  try {
96
87
  const body = (await req.json()) as MkdirRequest;
97
88
  const { path, recursive = false, sessionId } = body;
98
89
 
99
- if (!path || typeof path !== "string") {
100
- return new Response(
101
- JSON.stringify({
102
- error: "Path is required and must be a string",
103
- }),
104
- {
105
- headers: {
106
- "Content-Type": "application/json",
107
- ...corsHeaders,
108
- },
109
- status: 400,
110
- }
111
- );
90
+ // Validate path
91
+ const pathError = validatePath(path);
92
+ if (pathError) {
93
+ return createPathErrorResponse(pathError, corsHeaders);
112
94
  }
113
95
 
114
- // Basic safety check - prevent dangerous paths
115
- const dangerousPatterns = [
116
- /^\/$/, // Root directory
117
- /^\/etc/, // System directories
118
- /^\/var/, // System directories
119
- /^\/usr/, // System directories
120
- /^\/bin/, // System directories
121
- /^\/sbin/, // System directories
122
- /^\/boot/, // System directories
123
- /^\/dev/, // System directories
124
- /^\/proc/, // System directories
125
- /^\/sys/, // System directories
126
- /^\/tmp\/\.\./, // Path traversal attempts
127
- /\.\./, // Path traversal attempts
128
- ];
129
-
130
- if (dangerousPatterns.some((pattern) => pattern.test(path))) {
131
- return new Response(
132
- JSON.stringify({
133
- error: "Dangerous path not allowed",
134
- }),
135
- {
136
- headers: {
137
- "Content-Type": "application/json",
138
- ...corsHeaders,
139
- },
140
- status: 400,
141
- }
142
- );
143
- }
96
+ console.log(`[Server] Creating directory: ${path} (recursive: ${recursive})${sessionId ? ` in session: ${sessionId}` : ''}`);
144
97
 
145
- console.log(
146
- `[Server] Creating directory: ${path} (recursive: ${recursive})`
147
- );
148
-
149
- const result = await executeMkdir(sessions, path, recursive, sessionId);
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
+ }
150
106
 
151
107
  return new Response(
152
108
  JSON.stringify({
153
109
  exitCode: result.exitCode,
154
110
  path,
155
111
  recursive,
156
- stderr: result.stderr,
157
- stdout: result.stdout,
112
+ stderr: "",
113
+ stdout: "",
158
114
  success: result.success,
159
115
  timestamp: new Date().toISOString(),
160
116
  }),
@@ -166,119 +122,35 @@ export async function handleMkdirRequest(
166
122
  }
167
123
  );
168
124
  } catch (error) {
169
- console.error("[Server] Error in handleMkdirRequest:", error);
170
- return new Response(
171
- JSON.stringify({
172
- error: "Failed to create directory",
173
- message: error instanceof Error ? error.message : "Unknown error",
174
- }),
175
- {
176
- headers: {
177
- "Content-Type": "application/json",
178
- ...corsHeaders,
179
- },
180
- status: 500,
181
- }
182
- );
125
+ return createServerErrorResponse("handleMkdirRequest", error, corsHeaders);
183
126
  }
184
127
  }
185
128
 
186
-
187
- function executeWriteFile(
188
- path: string,
189
- content: string,
190
- encoding: string,
191
- sessionId?: string
192
- ): Promise<{
193
- success: boolean;
194
- exitCode: number;
195
- }> {
196
- return new Promise((resolve, reject) => {
197
- (async () => {
198
- try {
199
- // Ensure the directory exists
200
- const dir = dirname(path);
201
- if (dir !== ".") {
202
- await mkdir(dir, { recursive: true });
203
- }
204
-
205
- // Write the file
206
- await writeFile(path, content, {
207
- encoding: encoding as BufferEncoding,
208
- });
209
-
210
- console.log(`[Server] File written successfully: ${path}`);
211
- resolve({
212
- exitCode: 0,
213
- success: true,
214
- });
215
- } catch (error) {
216
- console.error(`[Server] Error writing file: ${path}`, error);
217
- reject(error);
218
- }
219
- })();
220
- });
221
- }
222
-
223
129
  export async function handleWriteFileRequest(
224
130
  req: Request,
225
- corsHeaders: Record<string, string>
131
+ corsHeaders: Record<string, string>,
132
+ sessionManager: SessionManager
226
133
  ): Promise<Response> {
227
134
  try {
228
135
  const body = (await req.json()) as WriteFileRequest;
229
136
  const { path, content, encoding = "utf-8", sessionId } = body;
230
137
 
231
- if (!path || typeof path !== "string") {
232
- return new Response(
233
- JSON.stringify({
234
- error: "Path is required and must be a string",
235
- }),
236
- {
237
- headers: {
238
- "Content-Type": "application/json",
239
- ...corsHeaders,
240
- },
241
- status: 400,
242
- }
243
- );
138
+ // Validate path
139
+ const pathError = validatePath(path);
140
+ if (pathError) {
141
+ return createPathErrorResponse(pathError, corsHeaders);
244
142
  }
245
143
 
246
- // Basic safety check - prevent dangerous paths
247
- const dangerousPatterns = [
248
- /^\/$/, // Root directory
249
- /^\/etc/, // System directories
250
- /^\/var/, // System directories
251
- /^\/usr/, // System directories
252
- /^\/bin/, // System directories
253
- /^\/sbin/, // System directories
254
- /^\/boot/, // System directories
255
- /^\/dev/, // System directories
256
- /^\/proc/, // System directories
257
- /^\/sys/, // System directories
258
- /^\/tmp\/\.\./, // Path traversal attempts
259
- /\.\./, // Path traversal attempts
260
- ];
261
-
262
- if (dangerousPatterns.some((pattern) => pattern.test(path))) {
263
- return new Response(
264
- JSON.stringify({
265
- error: "Dangerous path not allowed",
266
- }),
267
- {
268
- headers: {
269
- "Content-Type": "application/json",
270
- ...corsHeaders,
271
- },
272
- status: 400,
273
- }
274
- );
275
- }
144
+ console.log(`[Server] Writing file: ${path} (content length: ${content.length})${sessionId ? ` in session: ${sessionId}` : ''}`);
276
145
 
277
- console.log(
278
- `[Server] Writing file: ${path} (content length: ${content.length})`
279
- );
280
-
281
- const result = await executeWriteFile(path, content, encoding, sessionId);
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
+ }
282
154
 
283
155
  return new Response(
284
156
  JSON.stringify({
@@ -295,112 +167,35 @@ export async function handleWriteFileRequest(
295
167
  }
296
168
  );
297
169
  } catch (error) {
298
- console.error("[Server] Error in handleWriteFileRequest:", error);
299
- return new Response(
300
- JSON.stringify({
301
- error: "Failed to write file",
302
- message: error instanceof Error ? error.message : "Unknown error",
303
- }),
304
- {
305
- headers: {
306
- "Content-Type": "application/json",
307
- ...corsHeaders,
308
- },
309
- status: 500,
310
- }
311
- );
170
+ return createServerErrorResponse("handleWriteFileRequest", error, corsHeaders);
312
171
  }
313
172
  }
314
173
 
315
-
316
- function executeReadFile(
317
- path: string,
318
- encoding: string,
319
- sessionId?: string
320
- ): Promise<{
321
- success: boolean;
322
- exitCode: number;
323
- content: string;
324
- }> {
325
- return new Promise((resolve, reject) => {
326
- (async () => {
327
- try {
328
- // Read the file
329
- const content = await readFile(path, {
330
- encoding: encoding as BufferEncoding,
331
- });
332
-
333
- console.log(`[Server] File read successfully: ${path}`);
334
- resolve({
335
- content,
336
- exitCode: 0,
337
- success: true,
338
- });
339
- } catch (error) {
340
- console.error(`[Server] Error reading file: ${path}`, error);
341
- reject(error);
342
- }
343
- })();
344
- });
345
- }
346
-
347
174
  export async function handleReadFileRequest(
348
175
  req: Request,
349
- corsHeaders: Record<string, string>
176
+ corsHeaders: Record<string, string>,
177
+ sessionManager: SessionManager
350
178
  ): Promise<Response> {
351
179
  try {
352
180
  const body = (await req.json()) as ReadFileRequest;
353
181
  const { path, encoding = "utf-8", sessionId } = body;
354
182
 
355
- if (!path || typeof path !== "string") {
356
- return new Response(
357
- JSON.stringify({
358
- error: "Path is required and must be a string",
359
- }),
360
- {
361
- headers: {
362
- "Content-Type": "application/json",
363
- ...corsHeaders,
364
- },
365
- status: 400,
366
- }
367
- );
368
- }
369
-
370
- // Basic safety check - prevent dangerous paths
371
- const dangerousPatterns = [
372
- /^\/$/, // Root directory
373
- /^\/etc/, // System directories
374
- /^\/var/, // System directories
375
- /^\/usr/, // System directories
376
- /^\/bin/, // System directories
377
- /^\/sbin/, // System directories
378
- /^\/boot/, // System directories
379
- /^\/dev/, // System directories
380
- /^\/proc/, // System directories
381
- /^\/sys/, // System directories
382
- /^\/tmp\/\.\./, // Path traversal attempts
383
- /\.\./, // Path traversal attempts
384
- ];
385
-
386
- if (dangerousPatterns.some((pattern) => pattern.test(path))) {
387
- return new Response(
388
- JSON.stringify({
389
- error: "Dangerous path not allowed",
390
- }),
391
- {
392
- headers: {
393
- "Content-Type": "application/json",
394
- ...corsHeaders,
395
- },
396
- status: 400,
397
- }
398
- );
183
+ // Validate path
184
+ const pathError = validatePath(path);
185
+ if (pathError) {
186
+ return createPathErrorResponse(pathError, corsHeaders);
399
187
  }
400
188
 
401
- console.log(`[Server] Reading file: ${path}`);
189
+ console.log(`[Server] Reading file: ${path}${sessionId ? ` in session: ${sessionId}` : ''}`);
402
190
 
403
- const result = await executeReadFile(path, encoding, sessionId);
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
+ }
404
199
 
405
200
  return new Response(
406
201
  JSON.stringify({
@@ -418,107 +213,35 @@ export async function handleReadFileRequest(
418
213
  }
419
214
  );
420
215
  } catch (error) {
421
- console.error("[Server] Error in handleReadFileRequest:", error);
422
- return new Response(
423
- JSON.stringify({
424
- error: "Failed to read file",
425
- message: error instanceof Error ? error.message : "Unknown error",
426
- }),
427
- {
428
- headers: {
429
- "Content-Type": "application/json",
430
- ...corsHeaders,
431
- },
432
- status: 500,
433
- }
434
- );
216
+ return createServerErrorResponse("handleReadFileRequest", error, corsHeaders);
435
217
  }
436
218
  }
437
219
 
438
-
439
- function executeDeleteFile(
440
- path: string,
441
- sessionId?: string
442
- ): Promise<{
443
- success: boolean;
444
- exitCode: number;
445
- }> {
446
- return new Promise((resolve, reject) => {
447
- (async () => {
448
- try {
449
- // Delete the file
450
- await unlink(path);
451
-
452
- console.log(`[Server] File deleted successfully: ${path}`);
453
- resolve({
454
- exitCode: 0,
455
- success: true,
456
- });
457
- } catch (error) {
458
- console.error(`[Server] Error deleting file: ${path}`, error);
459
- reject(error);
460
- }
461
- })();
462
- });
463
- }
464
-
465
220
  export async function handleDeleteFileRequest(
466
221
  req: Request,
467
- corsHeaders: Record<string, string>
222
+ corsHeaders: Record<string, string>,
223
+ sessionManager: SessionManager
468
224
  ): Promise<Response> {
469
225
  try {
470
226
  const body = (await req.json()) as DeleteFileRequest;
471
227
  const { path, sessionId } = body;
472
228
 
473
- if (!path || typeof path !== "string") {
474
- return new Response(
475
- JSON.stringify({
476
- error: "Path is required and must be a string",
477
- }),
478
- {
479
- headers: {
480
- "Content-Type": "application/json",
481
- ...corsHeaders,
482
- },
483
- status: 400,
484
- }
485
- );
486
- }
487
-
488
- // Basic safety check - prevent dangerous paths
489
- const dangerousPatterns = [
490
- /^\/$/, // Root directory
491
- /^\/etc/, // System directories
492
- /^\/var/, // System directories
493
- /^\/usr/, // System directories
494
- /^\/bin/, // System directories
495
- /^\/sbin/, // System directories
496
- /^\/boot/, // System directories
497
- /^\/dev/, // System directories
498
- /^\/proc/, // System directories
499
- /^\/sys/, // System directories
500
- /^\/tmp\/\.\./, // Path traversal attempts
501
- /\.\./, // Path traversal attempts
502
- ];
503
-
504
- if (dangerousPatterns.some((pattern) => pattern.test(path))) {
505
- return new Response(
506
- JSON.stringify({
507
- error: "Dangerous path not allowed",
508
- }),
509
- {
510
- headers: {
511
- "Content-Type": "application/json",
512
- ...corsHeaders,
513
- },
514
- status: 400,
515
- }
516
- );
229
+ // Validate path
230
+ const pathError = validatePath(path);
231
+ if (pathError) {
232
+ return createPathErrorResponse(pathError, corsHeaders);
517
233
  }
518
234
 
519
- console.log(`[Server] Deleting file: ${path}`);
235
+ console.log(`[Server] Deleting file: ${path}${sessionId ? ` in session: ${sessionId}` : ''}`);
520
236
 
521
- const result = await executeDeleteFile(path, sessionId);
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
+ }
522
245
 
523
246
  return new Response(
524
247
  JSON.stringify({
@@ -535,133 +258,36 @@ export async function handleDeleteFileRequest(
535
258
  }
536
259
  );
537
260
  } catch (error) {
538
- console.error("[Server] Error in handleDeleteFileRequest:", error);
539
- return new Response(
540
- JSON.stringify({
541
- error: "Failed to delete file",
542
- message: error instanceof Error ? error.message : "Unknown error",
543
- }),
544
- {
545
- headers: {
546
- "Content-Type": "application/json",
547
- ...corsHeaders,
548
- },
549
- status: 500,
550
- }
551
- );
261
+ return createServerErrorResponse("handleDeleteFileRequest", error, corsHeaders);
552
262
  }
553
263
  }
554
264
 
555
-
556
- function executeRenameFile(
557
- oldPath: string,
558
- newPath: string,
559
- sessionId?: string
560
- ): Promise<{
561
- success: boolean;
562
- exitCode: number;
563
- }> {
564
- return new Promise((resolve, reject) => {
565
- (async () => {
566
- try {
567
- // Rename the file
568
- await rename(oldPath, newPath);
569
-
570
- console.log(
571
- `[Server] File renamed successfully: ${oldPath} -> ${newPath}`
572
- );
573
- resolve({
574
- exitCode: 0,
575
- success: true,
576
- });
577
- } catch (error) {
578
- console.error(
579
- `[Server] Error renaming file: ${oldPath} -> ${newPath}`,
580
- error
581
- );
582
- reject(error);
583
- }
584
- })();
585
- });
586
- }
587
-
588
265
  export async function handleRenameFileRequest(
589
266
  req: Request,
590
- corsHeaders: Record<string, string>
267
+ corsHeaders: Record<string, string>,
268
+ sessionManager: SessionManager
591
269
  ): Promise<Response> {
592
270
  try {
593
271
  const body = (await req.json()) as RenameFileRequest;
594
272
  const { oldPath, newPath, sessionId } = body;
595
273
 
596
- if (!oldPath || typeof oldPath !== "string") {
597
- return new Response(
598
- JSON.stringify({
599
- error: "Old path is required and must be a string",
600
- }),
601
- {
602
- headers: {
603
- "Content-Type": "application/json",
604
- ...corsHeaders,
605
- },
606
- status: 400,
607
- }
608
- );
274
+ // Validate paths
275
+ const pathError = validatePath(oldPath, newPath);
276
+ if (pathError) {
277
+ return createPathErrorResponse(pathError, corsHeaders);
609
278
  }
610
279
 
611
- if (!newPath || typeof newPath !== "string") {
612
- return new Response(
613
- JSON.stringify({
614
- error: "New path is required and must be a string",
615
- }),
616
- {
617
- headers: {
618
- "Content-Type": "application/json",
619
- ...corsHeaders,
620
- },
621
- status: 400,
622
- }
623
- );
624
- }
280
+ console.log(`[Server] Renaming file: ${oldPath} -> ${newPath}${sessionId ? ` in session: ${sessionId}` : ''}`);
625
281
 
626
- // Basic safety check - prevent dangerous paths
627
- const dangerousPatterns = [
628
- /^\/$/, // Root directory
629
- /^\/etc/, // System directories
630
- /^\/var/, // System directories
631
- /^\/usr/, // System directories
632
- /^\/bin/, // System directories
633
- /^\/sbin/, // System directories
634
- /^\/boot/, // System directories
635
- /^\/dev/, // System directories
636
- /^\/proc/, // System directories
637
- /^\/sys/, // System directories
638
- /^\/tmp\/\.\./, // Path traversal attempts
639
- /\.\./, // Path traversal attempts
640
- ];
641
-
642
- if (
643
- dangerousPatterns.some(
644
- (pattern) => pattern.test(oldPath) || pattern.test(newPath)
645
- )
646
- ) {
647
- return new Response(
648
- JSON.stringify({
649
- error: "Dangerous path not allowed",
650
- }),
651
- {
652
- headers: {
653
- "Content-Type": "application/json",
654
- ...corsHeaders,
655
- },
656
- status: 400,
657
- }
658
- );
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);
659
289
  }
660
290
 
661
- console.log(`[Server] Renaming file: ${oldPath} -> ${newPath}`);
662
-
663
- const result = await executeRenameFile(oldPath, newPath, sessionId);
664
-
665
291
  return new Response(
666
292
  JSON.stringify({
667
293
  exitCode: result.exitCode,
@@ -678,137 +304,36 @@ export async function handleRenameFileRequest(
678
304
  }
679
305
  );
680
306
  } catch (error) {
681
- console.error("[Server] Error in handleRenameFileRequest:", error);
682
- return new Response(
683
- JSON.stringify({
684
- error: "Failed to rename file",
685
- message: error instanceof Error ? error.message : "Unknown error",
686
- }),
687
- {
688
- headers: {
689
- "Content-Type": "application/json",
690
- ...corsHeaders,
691
- },
692
- status: 500,
693
- }
694
- );
307
+ return createServerErrorResponse("handleRenameFileRequest", error, corsHeaders);
695
308
  }
696
309
  }
697
310
 
698
-
699
- function executeMoveFile(
700
- sourcePath: string,
701
- destinationPath: string,
702
- sessionId?: string
703
- ): Promise<{
704
- success: boolean;
705
- exitCode: number;
706
- }> {
707
- return new Promise((resolve, reject) => {
708
- (async () => {
709
- try {
710
- // Move the file
711
- await rename(sourcePath, destinationPath);
712
-
713
- console.log(
714
- `[Server] File moved successfully: ${sourcePath} -> ${destinationPath}`
715
- );
716
- resolve({
717
- exitCode: 0,
718
- success: true,
719
- });
720
- } catch (error) {
721
- console.error(
722
- `[Server] Error moving file: ${sourcePath} -> ${destinationPath}`,
723
- error
724
- );
725
- reject(error);
726
- }
727
- })();
728
- });
729
- }
730
-
731
311
  export async function handleMoveFileRequest(
732
312
  req: Request,
733
- corsHeaders: Record<string, string>
313
+ corsHeaders: Record<string, string>,
314
+ sessionManager: SessionManager
734
315
  ): Promise<Response> {
735
316
  try {
736
317
  const body = (await req.json()) as MoveFileRequest;
737
318
  const { sourcePath, destinationPath, sessionId } = body;
738
319
 
739
- if (!sourcePath || typeof sourcePath !== "string") {
740
- return new Response(
741
- JSON.stringify({
742
- error: "Source path is required and must be a string",
743
- }),
744
- {
745
- headers: {
746
- "Content-Type": "application/json",
747
- ...corsHeaders,
748
- },
749
- status: 400,
750
- }
751
- );
320
+ // Validate paths
321
+ const pathError = validatePath(sourcePath, destinationPath);
322
+ if (pathError) {
323
+ return createPathErrorResponse(pathError, corsHeaders);
752
324
  }
753
325
 
754
- if (!destinationPath || typeof destinationPath !== "string") {
755
- return new Response(
756
- JSON.stringify({
757
- error: "Destination path is required and must be a string",
758
- }),
759
- {
760
- headers: {
761
- "Content-Type": "application/json",
762
- ...corsHeaders,
763
- },
764
- status: 400,
765
- }
766
- );
767
- }
326
+ console.log(`[Server] Moving file: ${sourcePath} -> ${destinationPath}${sessionId ? ` in session: ${sessionId}` : ''}`);
768
327
 
769
- // Basic safety check - prevent dangerous paths
770
- const dangerousPatterns = [
771
- /^\/$/, // Root directory
772
- /^\/etc/, // System directories
773
- /^\/var/, // System directories
774
- /^\/usr/, // System directories
775
- /^\/bin/, // System directories
776
- /^\/sbin/, // System directories
777
- /^\/boot/, // System directories
778
- /^\/dev/, // System directories
779
- /^\/proc/, // System directories
780
- /^\/sys/, // System directories
781
- /^\/tmp\/\.\./, // Path traversal attempts
782
- /\.\./, // Path traversal attempts
783
- ];
784
-
785
- if (
786
- dangerousPatterns.some(
787
- (pattern) => pattern.test(sourcePath) || pattern.test(destinationPath)
788
- )
789
- ) {
790
- return new Response(
791
- JSON.stringify({
792
- error: "Dangerous path not allowed",
793
- }),
794
- {
795
- headers: {
796
- "Content-Type": "application/json",
797
- ...corsHeaders,
798
- },
799
- status: 400,
800
- }
801
- );
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);
802
335
  }
803
336
 
804
- console.log(`[Server] Moving file: ${sourcePath} -> ${destinationPath}`);
805
-
806
- const result = await executeMoveFile(
807
- sourcePath,
808
- destinationPath,
809
- sessionId
810
- );
811
-
812
337
  return new Response(
813
338
  JSON.stringify({
814
339
  destinationPath,
@@ -825,20 +350,57 @@ export async function handleMoveFileRequest(
825
350
  }
826
351
  );
827
352
  } catch (error) {
828
- console.error("[Server] Error in handleMoveFileRequest:", 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
+
829
388
  return new Response(
830
389
  JSON.stringify({
831
- error: "Failed to move file",
832
- message: error instanceof Error ? error.message : "Unknown error",
390
+ exitCode: result.exitCode,
391
+ files: result.files,
392
+ path,
393
+ success: result.success,
394
+ timestamp: new Date().toISOString(),
833
395
  }),
834
396
  {
835
397
  headers: {
836
398
  "Content-Type": "application/json",
837
399
  ...corsHeaders,
838
400
  },
839
- status: 500,
840
401
  }
841
402
  );
403
+ } catch (error) {
404
+ return createServerErrorResponse("handleListFilesRequest", error, corsHeaders);
842
405
  }
843
- }
844
-
406
+ }