@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.
- package/CHANGELOG.md +69 -0
- package/Dockerfile +9 -11
- package/README.md +69 -7
- package/container_src/control-process.ts +784 -0
- package/container_src/handler/exec.ts +99 -254
- package/container_src/handler/file.ts +204 -642
- 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 +74 -129
- package/container_src/isolation.ts +1039 -0
- package/container_src/jupyter-service.ts +8 -5
- package/container_src/shell-escape.ts +42 -0
- package/container_src/types.ts +35 -12
- package/dist/{chunk-VTKZL632.js → chunk-BEQUGUY4.js} +2 -2
- package/dist/{chunk-4KELYYKS.js → chunk-GTGWAEED.js} +239 -265
- package/dist/chunk-GTGWAEED.js.map +1 -0
- package/dist/{chunk-CUHYLCMT.js → chunk-SMUEY5JR.js} +111 -99
- package/dist/chunk-SMUEY5JR.js.map +1 -0
- package/dist/{client-bzEV222a.d.ts → client-Dny_ro_v.d.ts} +48 -84
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +8 -9
- package/dist/interpreter.d.ts +2 -2
- package/dist/jupyter-client.d.ts +2 -2
- package/dist/jupyter-client.js +2 -2
- package/dist/request-handler.d.ts +3 -3
- package/dist/request-handler.js +3 -5
- package/dist/sandbox.d.ts +2 -2
- package/dist/sandbox.js +3 -5
- package/dist/types.d.ts +127 -21
- package/dist/types.js +35 -9
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +175 -187
- package/src/index.ts +23 -13
- package/src/sandbox.ts +297 -332
- package/src/types.ts +125 -24
- package/dist/chunk-4KELYYKS.js.map +0 -1
- package/dist/chunk-CUHYLCMT.js.map +0 -1
- package/dist/chunk-S5FFBU4Y.js +0 -46
- package/dist/chunk-S5FFBU4Y.js.map +0 -1
- /package/dist/{chunk-VTKZL632.js.map → chunk-BEQUGUY4.js.map} +0 -0
|
@@ -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({
|
|
@@ -418,107 +213,35 @@ export async function handleReadFileRequest(
|
|
|
418
213
|
}
|
|
419
214
|
);
|
|
420
215
|
} 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
|
-
);
|
|
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
|
-
|
|
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
|
-
);
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
);
|
|
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
|
-
|
|
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
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
);
|
|
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
|
-
|
|
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
|
-
|
|
832
|
-
|
|
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
|
+
}
|