@cloudflare/sandbox 0.0.0-c87db11 → 0.0.0-cdb8197
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 +117 -0
- package/Dockerfile +32 -29
- package/README.md +127 -12
- package/container_src/bun.lock +31 -77
- package/container_src/control-process.ts +784 -0
- package/container_src/handler/exec.ts +99 -254
- package/container_src/handler/file.ts +253 -640
- package/container_src/handler/git.ts +28 -80
- package/container_src/handler/process.ts +443 -515
- package/container_src/handler/session.ts +92 -0
- package/container_src/index.ts +108 -163
- package/container_src/interpreter-service.ts +276 -0
- package/container_src/isolation.ts +1213 -0
- package/container_src/mime-processor.ts +1 -1
- package/container_src/package.json +4 -4
- package/container_src/runtime/executors/javascript/node_executor.ts +123 -0
- package/container_src/runtime/executors/python/ipython_executor.py +338 -0
- package/container_src/runtime/executors/typescript/ts_executor.ts +138 -0
- package/container_src/runtime/process-pool.ts +464 -0
- package/container_src/shell-escape.ts +42 -0
- package/container_src/startup.sh +6 -79
- package/container_src/types.ts +35 -12
- package/package.json +2 -2
- package/src/client.ts +214 -187
- package/src/errors.ts +15 -14
- package/src/file-stream.ts +162 -0
- package/src/index.ts +43 -16
- package/src/{jupyter-client.ts → interpreter-client.ts} +6 -3
- package/src/interpreter-types.ts +102 -95
- package/src/interpreter.ts +8 -8
- package/src/sandbox.ts +314 -336
- package/src/types.ts +194 -24
- package/container_src/jupyter-server.ts +0 -579
- package/container_src/jupyter-service.ts +0 -458
- package/container_src/jupyter_config.py +0 -48
|
@@ -1,160 +1,116 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (
|
|
34
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
85
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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:
|
|
157
|
-
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
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
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
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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
|
-
|
|
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({
|
|
@@ -409,6 +204,11 @@ export async function handleReadFileRequest(
|
|
|
409
204
|
path,
|
|
410
205
|
success: result.success,
|
|
411
206
|
timestamp: new Date().toISOString(),
|
|
207
|
+
// New metadata fields for binary file support
|
|
208
|
+
encoding: result.encoding,
|
|
209
|
+
isBinary: result.isBinary,
|
|
210
|
+
mimeType: result.mimeType,
|
|
211
|
+
size: result.size,
|
|
412
212
|
}),
|
|
413
213
|
{
|
|
414
214
|
headers: {
|
|
@@ -418,107 +218,35 @@ export async function handleReadFileRequest(
|
|
|
418
218
|
}
|
|
419
219
|
);
|
|
420
220
|
} catch (error) {
|
|
421
|
-
|
|
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
|
-
);
|
|
221
|
+
return createServerErrorResponse("handleReadFileRequest", error, corsHeaders);
|
|
435
222
|
}
|
|
436
223
|
}
|
|
437
224
|
|
|
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
225
|
export async function handleDeleteFileRequest(
|
|
466
226
|
req: Request,
|
|
467
|
-
corsHeaders: Record<string, string
|
|
227
|
+
corsHeaders: Record<string, string>,
|
|
228
|
+
sessionManager: SessionManager
|
|
468
229
|
): Promise<Response> {
|
|
469
230
|
try {
|
|
470
231
|
const body = (await req.json()) as DeleteFileRequest;
|
|
471
232
|
const { path, sessionId } = body;
|
|
472
233
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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
|
-
);
|
|
234
|
+
// Validate path
|
|
235
|
+
const pathError = validatePath(path);
|
|
236
|
+
if (pathError) {
|
|
237
|
+
return createPathErrorResponse(pathError, corsHeaders);
|
|
517
238
|
}
|
|
518
239
|
|
|
519
|
-
console.log(`[Server] Deleting file: ${path}`);
|
|
240
|
+
console.log(`[Server] Deleting file: ${path}${sessionId ? ` in session: ${sessionId}` : ''}`);
|
|
520
241
|
|
|
521
|
-
|
|
242
|
+
// Use specific session if provided, otherwise default session
|
|
243
|
+
const result = sessionId
|
|
244
|
+
? await sessionManager.getSession(sessionId)?.deleteFileOperation(path)
|
|
245
|
+
: await sessionManager.deleteFile(path);
|
|
246
|
+
|
|
247
|
+
if (!result) {
|
|
248
|
+
return createServerErrorResponse("handleDeleteFileRequest", new Error(`Session '${sessionId}' not found`), corsHeaders);
|
|
249
|
+
}
|
|
522
250
|
|
|
523
251
|
return new Response(
|
|
524
252
|
JSON.stringify({
|
|
@@ -535,133 +263,36 @@ export async function handleDeleteFileRequest(
|
|
|
535
263
|
}
|
|
536
264
|
);
|
|
537
265
|
} catch (error) {
|
|
538
|
-
|
|
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
|
-
);
|
|
266
|
+
return createServerErrorResponse("handleDeleteFileRequest", error, corsHeaders);
|
|
552
267
|
}
|
|
553
268
|
}
|
|
554
269
|
|
|
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
270
|
export async function handleRenameFileRequest(
|
|
589
271
|
req: Request,
|
|
590
|
-
corsHeaders: Record<string, string
|
|
272
|
+
corsHeaders: Record<string, string>,
|
|
273
|
+
sessionManager: SessionManager
|
|
591
274
|
): Promise<Response> {
|
|
592
275
|
try {
|
|
593
276
|
const body = (await req.json()) as RenameFileRequest;
|
|
594
277
|
const { oldPath, newPath, sessionId } = body;
|
|
595
278
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
}),
|
|
601
|
-
{
|
|
602
|
-
headers: {
|
|
603
|
-
"Content-Type": "application/json",
|
|
604
|
-
...corsHeaders,
|
|
605
|
-
},
|
|
606
|
-
status: 400,
|
|
607
|
-
}
|
|
608
|
-
);
|
|
279
|
+
// Validate paths
|
|
280
|
+
const pathError = validatePath(oldPath, newPath);
|
|
281
|
+
if (pathError) {
|
|
282
|
+
return createPathErrorResponse(pathError, corsHeaders);
|
|
609
283
|
}
|
|
610
284
|
|
|
611
|
-
|
|
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
|
-
}
|
|
285
|
+
console.log(`[Server] Renaming file: ${oldPath} -> ${newPath}${sessionId ? ` in session: ${sessionId}` : ''}`);
|
|
625
286
|
|
|
626
|
-
//
|
|
627
|
-
const
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
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
|
-
);
|
|
287
|
+
// Use specific session if provided, otherwise default session
|
|
288
|
+
const result = sessionId
|
|
289
|
+
? await sessionManager.getSession(sessionId)?.renameFileOperation(oldPath, newPath)
|
|
290
|
+
: await sessionManager.renameFile(oldPath, newPath);
|
|
291
|
+
|
|
292
|
+
if (!result) {
|
|
293
|
+
return createServerErrorResponse("handleRenameFileRequest", new Error(`Session '${sessionId}' not found`), corsHeaders);
|
|
659
294
|
}
|
|
660
295
|
|
|
661
|
-
console.log(`[Server] Renaming file: ${oldPath} -> ${newPath}`);
|
|
662
|
-
|
|
663
|
-
const result = await executeRenameFile(oldPath, newPath, sessionId);
|
|
664
|
-
|
|
665
296
|
return new Response(
|
|
666
297
|
JSON.stringify({
|
|
667
298
|
exitCode: result.exitCode,
|
|
@@ -678,137 +309,36 @@ export async function handleRenameFileRequest(
|
|
|
678
309
|
}
|
|
679
310
|
);
|
|
680
311
|
} catch (error) {
|
|
681
|
-
|
|
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
|
-
);
|
|
312
|
+
return createServerErrorResponse("handleRenameFileRequest", error, corsHeaders);
|
|
695
313
|
}
|
|
696
314
|
}
|
|
697
315
|
|
|
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
316
|
export async function handleMoveFileRequest(
|
|
732
317
|
req: Request,
|
|
733
|
-
corsHeaders: Record<string, string
|
|
318
|
+
corsHeaders: Record<string, string>,
|
|
319
|
+
sessionManager: SessionManager
|
|
734
320
|
): Promise<Response> {
|
|
735
321
|
try {
|
|
736
322
|
const body = (await req.json()) as MoveFileRequest;
|
|
737
323
|
const { sourcePath, destinationPath, sessionId } = body;
|
|
738
324
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
}),
|
|
744
|
-
{
|
|
745
|
-
headers: {
|
|
746
|
-
"Content-Type": "application/json",
|
|
747
|
-
...corsHeaders,
|
|
748
|
-
},
|
|
749
|
-
status: 400,
|
|
750
|
-
}
|
|
751
|
-
);
|
|
325
|
+
// Validate paths
|
|
326
|
+
const pathError = validatePath(sourcePath, destinationPath);
|
|
327
|
+
if (pathError) {
|
|
328
|
+
return createPathErrorResponse(pathError, corsHeaders);
|
|
752
329
|
}
|
|
753
330
|
|
|
754
|
-
|
|
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
|
-
}
|
|
331
|
+
console.log(`[Server] Moving file: ${sourcePath} -> ${destinationPath}${sessionId ? ` in session: ${sessionId}` : ''}`);
|
|
768
332
|
|
|
769
|
-
//
|
|
770
|
-
const
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
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
|
-
);
|
|
333
|
+
// Use specific session if provided, otherwise default session
|
|
334
|
+
const result = sessionId
|
|
335
|
+
? await sessionManager.getSession(sessionId)?.moveFileOperation(sourcePath, destinationPath)
|
|
336
|
+
: await sessionManager.moveFile(sourcePath, destinationPath);
|
|
337
|
+
|
|
338
|
+
if (!result) {
|
|
339
|
+
return createServerErrorResponse("handleMoveFileRequest", new Error(`Session '${sessionId}' not found`), corsHeaders);
|
|
802
340
|
}
|
|
803
341
|
|
|
804
|
-
console.log(`[Server] Moving file: ${sourcePath} -> ${destinationPath}`);
|
|
805
|
-
|
|
806
|
-
const result = await executeMoveFile(
|
|
807
|
-
sourcePath,
|
|
808
|
-
destinationPath,
|
|
809
|
-
sessionId
|
|
810
|
-
);
|
|
811
|
-
|
|
812
342
|
return new Response(
|
|
813
343
|
JSON.stringify({
|
|
814
344
|
destinationPath,
|
|
@@ -825,20 +355,103 @@ export async function handleMoveFileRequest(
|
|
|
825
355
|
}
|
|
826
356
|
);
|
|
827
357
|
} catch (error) {
|
|
828
|
-
|
|
358
|
+
return createServerErrorResponse("handleMoveFileRequest", error, corsHeaders);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export async function handleListFilesRequest(
|
|
363
|
+
req: Request,
|
|
364
|
+
corsHeaders: Record<string, string>,
|
|
365
|
+
sessionManager: SessionManager
|
|
366
|
+
): Promise<Response> {
|
|
367
|
+
try {
|
|
368
|
+
const body = (await req.json()) as ListFilesRequest;
|
|
369
|
+
const { path, options, sessionId } = body;
|
|
370
|
+
|
|
371
|
+
// Validate path (note: listFiles allows root directory listing)
|
|
372
|
+
const pathError = validatePath(path);
|
|
373
|
+
if (pathError && pathError !== "Dangerous path not allowed") {
|
|
374
|
+
return createPathErrorResponse(pathError, corsHeaders);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// For listFiles, we allow root directory but still check other dangerous patterns
|
|
378
|
+
if (path !== "/" && DANGEROUS_PATH_PATTERNS.slice(1).some((pattern) => pattern.test(path))) {
|
|
379
|
+
return createPathErrorResponse("Dangerous path not allowed", corsHeaders);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
console.log(`[Server] Listing files in: ${path}${sessionId ? ` in session: ${sessionId}` : ''}`);
|
|
383
|
+
|
|
384
|
+
// Use specific session if provided, otherwise default session
|
|
385
|
+
const result = sessionId
|
|
386
|
+
? await sessionManager.getSession(sessionId)?.listFilesOperation(path, options)
|
|
387
|
+
: await sessionManager.listFiles(path, options);
|
|
388
|
+
|
|
389
|
+
if (!result) {
|
|
390
|
+
return createServerErrorResponse("handleListFilesRequest", new Error(`Session '${sessionId}' not found`), corsHeaders);
|
|
391
|
+
}
|
|
392
|
+
|
|
829
393
|
return new Response(
|
|
830
394
|
JSON.stringify({
|
|
831
|
-
|
|
832
|
-
|
|
395
|
+
exitCode: result.exitCode,
|
|
396
|
+
files: result.files,
|
|
397
|
+
path,
|
|
398
|
+
success: result.success,
|
|
399
|
+
timestamp: new Date().toISOString(),
|
|
833
400
|
}),
|
|
834
401
|
{
|
|
835
402
|
headers: {
|
|
836
403
|
"Content-Type": "application/json",
|
|
837
404
|
...corsHeaders,
|
|
838
405
|
},
|
|
839
|
-
status: 500,
|
|
840
406
|
}
|
|
841
407
|
);
|
|
408
|
+
} catch (error) {
|
|
409
|
+
return createServerErrorResponse("handleListFilesRequest", error, corsHeaders);
|
|
842
410
|
}
|
|
843
411
|
}
|
|
844
412
|
|
|
413
|
+
export async function handleReadFileStreamRequest(
|
|
414
|
+
req: Request,
|
|
415
|
+
corsHeaders: Record<string, string>,
|
|
416
|
+
sessionManager: SessionManager
|
|
417
|
+
): Promise<Response> {
|
|
418
|
+
try {
|
|
419
|
+
const body = (await req.json()) as ReadFileRequest;
|
|
420
|
+
const { path, sessionId } = body;
|
|
421
|
+
|
|
422
|
+
// Validate path
|
|
423
|
+
const pathError = validatePath(path);
|
|
424
|
+
if (pathError) {
|
|
425
|
+
return createPathErrorResponse(pathError, corsHeaders);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
console.log(`[Server] Streaming file: ${path}${sessionId ? ` in session: ${sessionId}` : ''}`);
|
|
429
|
+
|
|
430
|
+
// Get the appropriate session
|
|
431
|
+
const session = sessionId
|
|
432
|
+
? sessionManager.getSession(sessionId)
|
|
433
|
+
: await sessionManager.getOrCreateDefaultSession();
|
|
434
|
+
|
|
435
|
+
if (!session) {
|
|
436
|
+
return createServerErrorResponse(
|
|
437
|
+
"handleReadFileStreamRequest",
|
|
438
|
+
new Error(`Session '${sessionId}' not found`),
|
|
439
|
+
corsHeaders
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Create SSE stream
|
|
444
|
+
const stream = await session.readFileStreamOperation(path);
|
|
445
|
+
|
|
446
|
+
return new Response(stream, {
|
|
447
|
+
headers: {
|
|
448
|
+
"Content-Type": "text/event-stream",
|
|
449
|
+
"Cache-Control": "no-cache",
|
|
450
|
+
"Connection": "keep-alive",
|
|
451
|
+
...corsHeaders,
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
} catch (error) {
|
|
455
|
+
return createServerErrorResponse("handleReadFileStreamRequest", error, corsHeaders);
|
|
456
|
+
}
|
|
457
|
+
}
|