@cloudflare/sandbox 0.0.9 → 0.1.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 +10 -0
- package/Dockerfile +1 -14
- package/container_src/handler/exec.ts +337 -0
- package/container_src/handler/file.ts +844 -0
- package/container_src/handler/git.ts +182 -0
- package/container_src/handler/ports.ts +314 -0
- package/container_src/handler/process.ts +640 -0
- package/container_src/index.ts +82 -2973
- package/container_src/types.ts +103 -0
- package/dist/chunk-6THNBO4S.js +46 -0
- package/dist/chunk-6THNBO4S.js.map +1 -0
- package/dist/chunk-6UAWTJ5S.js +85 -0
- package/dist/chunk-6UAWTJ5S.js.map +1 -0
- package/dist/chunk-G4XT4SP7.js +638 -0
- package/dist/chunk-G4XT4SP7.js.map +1 -0
- package/dist/chunk-ISFOIYQC.js +585 -0
- package/dist/chunk-ISFOIYQC.js.map +1 -0
- package/dist/chunk-NNGBXDMY.js +89 -0
- package/dist/chunk-NNGBXDMY.js.map +1 -0
- package/dist/client-Da-mLX4p.d.ts +210 -0
- package/dist/client.d.ts +2 -1
- package/dist/client.js +3 -37
- package/dist/index.d.ts +3 -1
- package/dist/index.js +13 -3
- package/dist/request-handler.d.ts +2 -1
- package/dist/request-handler.js +4 -2
- package/dist/sandbox.d.ts +2 -1
- package/dist/sandbox.js +4 -2
- package/dist/security.d.ts +30 -0
- package/dist/security.js +13 -0
- package/dist/security.js.map +1 -0
- package/dist/sse-parser.d.ts +28 -0
- package/dist/sse-parser.js +11 -0
- package/dist/sse-parser.js.map +1 -0
- package/dist/types.d.ts +284 -0
- package/dist/types.js +19 -0
- package/dist/types.js.map +1 -0
- package/package.json +2 -7
- package/src/client.ts +235 -1286
- package/src/index.ts +6 -0
- package/src/request-handler.ts +69 -20
- package/src/sandbox.ts +463 -70
- package/src/security.ts +113 -0
- package/src/sse-parser.ts +147 -0
- package/src/types.ts +386 -0
- package/README.md +0 -65
- package/dist/chunk-4J5LQCCN.js +0 -1446
- package/dist/chunk-4J5LQCCN.js.map +0 -1
- package/dist/chunk-5SZ3RVJZ.js +0 -250
- package/dist/chunk-5SZ3RVJZ.js.map +0 -1
- package/dist/client-BuVjqV00.d.ts +0 -247
- package/tests/client.example.ts +0 -308
- package/tests/connection-test.ts +0 -81
- package/tests/simple-test.ts +0 -81
- package/tests/test1.ts +0 -281
- package/tests/test2.ts +0 -929
|
@@ -0,0 +1,844 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import type {
|
|
5
|
+
DeleteFileRequest,
|
|
6
|
+
MkdirRequest,
|
|
7
|
+
MoveFileRequest,
|
|
8
|
+
ReadFileRequest,
|
|
9
|
+
RenameFileRequest,
|
|
10
|
+
SessionData,
|
|
11
|
+
WriteFileRequest
|
|
12
|
+
} from "../types";
|
|
13
|
+
|
|
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;
|
|
36
|
+
}
|
|
37
|
+
|
|
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
|
+
}
|
|
83
|
+
|
|
84
|
+
console.error(`[Server] Error creating directory: ${path}`, error);
|
|
85
|
+
reject(error);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function handleMkdirRequest(
|
|
91
|
+
sessions: Map<string, SessionData>,
|
|
92
|
+
req: Request,
|
|
93
|
+
corsHeaders: Record<string, string>
|
|
94
|
+
): Promise<Response> {
|
|
95
|
+
try {
|
|
96
|
+
const body = (await req.json()) as MkdirRequest;
|
|
97
|
+
const { path, recursive = false, sessionId } = body;
|
|
98
|
+
|
|
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
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
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
|
+
}
|
|
144
|
+
|
|
145
|
+
console.log(
|
|
146
|
+
`[Server] Creating directory: ${path} (recursive: ${recursive})`
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const result = await executeMkdir(sessions, path, recursive, sessionId);
|
|
150
|
+
|
|
151
|
+
return new Response(
|
|
152
|
+
JSON.stringify({
|
|
153
|
+
exitCode: result.exitCode,
|
|
154
|
+
path,
|
|
155
|
+
recursive,
|
|
156
|
+
stderr: result.stderr,
|
|
157
|
+
stdout: result.stdout,
|
|
158
|
+
success: result.success,
|
|
159
|
+
timestamp: new Date().toISOString(),
|
|
160
|
+
}),
|
|
161
|
+
{
|
|
162
|
+
headers: {
|
|
163
|
+
"Content-Type": "application/json",
|
|
164
|
+
...corsHeaders,
|
|
165
|
+
},
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
} 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
|
+
);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
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
|
+
export async function handleWriteFileRequest(
|
|
224
|
+
req: Request,
|
|
225
|
+
corsHeaders: Record<string, string>
|
|
226
|
+
): Promise<Response> {
|
|
227
|
+
try {
|
|
228
|
+
const body = (await req.json()) as WriteFileRequest;
|
|
229
|
+
const { path, content, encoding = "utf-8", sessionId } = body;
|
|
230
|
+
|
|
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
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
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
|
+
}
|
|
276
|
+
|
|
277
|
+
console.log(
|
|
278
|
+
`[Server] Writing file: ${path} (content length: ${content.length})`
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
const result = await executeWriteFile(path, content, encoding, sessionId);
|
|
282
|
+
|
|
283
|
+
return new Response(
|
|
284
|
+
JSON.stringify({
|
|
285
|
+
exitCode: result.exitCode,
|
|
286
|
+
path,
|
|
287
|
+
success: result.success,
|
|
288
|
+
timestamp: new Date().toISOString(),
|
|
289
|
+
}),
|
|
290
|
+
{
|
|
291
|
+
headers: {
|
|
292
|
+
"Content-Type": "application/json",
|
|
293
|
+
...corsHeaders,
|
|
294
|
+
},
|
|
295
|
+
}
|
|
296
|
+
);
|
|
297
|
+
} 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
|
+
);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
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
|
+
export async function handleReadFileRequest(
|
|
348
|
+
req: Request,
|
|
349
|
+
corsHeaders: Record<string, string>
|
|
350
|
+
): Promise<Response> {
|
|
351
|
+
try {
|
|
352
|
+
const body = (await req.json()) as ReadFileRequest;
|
|
353
|
+
const { path, encoding = "utf-8", sessionId } = body;
|
|
354
|
+
|
|
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
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
console.log(`[Server] Reading file: ${path}`);
|
|
402
|
+
|
|
403
|
+
const result = await executeReadFile(path, encoding, sessionId);
|
|
404
|
+
|
|
405
|
+
return new Response(
|
|
406
|
+
JSON.stringify({
|
|
407
|
+
content: result.content,
|
|
408
|
+
exitCode: result.exitCode,
|
|
409
|
+
path,
|
|
410
|
+
success: result.success,
|
|
411
|
+
timestamp: new Date().toISOString(),
|
|
412
|
+
}),
|
|
413
|
+
{
|
|
414
|
+
headers: {
|
|
415
|
+
"Content-Type": "application/json",
|
|
416
|
+
...corsHeaders,
|
|
417
|
+
},
|
|
418
|
+
}
|
|
419
|
+
);
|
|
420
|
+
} 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
|
+
);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
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
|
+
export async function handleDeleteFileRequest(
|
|
466
|
+
req: Request,
|
|
467
|
+
corsHeaders: Record<string, string>
|
|
468
|
+
): Promise<Response> {
|
|
469
|
+
try {
|
|
470
|
+
const body = (await req.json()) as DeleteFileRequest;
|
|
471
|
+
const { path, sessionId } = body;
|
|
472
|
+
|
|
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
|
+
);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
console.log(`[Server] Deleting file: ${path}`);
|
|
520
|
+
|
|
521
|
+
const result = await executeDeleteFile(path, sessionId);
|
|
522
|
+
|
|
523
|
+
return new Response(
|
|
524
|
+
JSON.stringify({
|
|
525
|
+
exitCode: result.exitCode,
|
|
526
|
+
path,
|
|
527
|
+
success: result.success,
|
|
528
|
+
timestamp: new Date().toISOString(),
|
|
529
|
+
}),
|
|
530
|
+
{
|
|
531
|
+
headers: {
|
|
532
|
+
"Content-Type": "application/json",
|
|
533
|
+
...corsHeaders,
|
|
534
|
+
},
|
|
535
|
+
}
|
|
536
|
+
);
|
|
537
|
+
} 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
|
+
);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
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
|
+
export async function handleRenameFileRequest(
|
|
589
|
+
req: Request,
|
|
590
|
+
corsHeaders: Record<string, string>
|
|
591
|
+
): Promise<Response> {
|
|
592
|
+
try {
|
|
593
|
+
const body = (await req.json()) as RenameFileRequest;
|
|
594
|
+
const { oldPath, newPath, sessionId } = body;
|
|
595
|
+
|
|
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
|
+
);
|
|
609
|
+
}
|
|
610
|
+
|
|
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
|
+
}
|
|
625
|
+
|
|
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
|
+
);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
console.log(`[Server] Renaming file: ${oldPath} -> ${newPath}`);
|
|
662
|
+
|
|
663
|
+
const result = await executeRenameFile(oldPath, newPath, sessionId);
|
|
664
|
+
|
|
665
|
+
return new Response(
|
|
666
|
+
JSON.stringify({
|
|
667
|
+
exitCode: result.exitCode,
|
|
668
|
+
newPath,
|
|
669
|
+
oldPath,
|
|
670
|
+
success: result.success,
|
|
671
|
+
timestamp: new Date().toISOString(),
|
|
672
|
+
}),
|
|
673
|
+
{
|
|
674
|
+
headers: {
|
|
675
|
+
"Content-Type": "application/json",
|
|
676
|
+
...corsHeaders,
|
|
677
|
+
},
|
|
678
|
+
}
|
|
679
|
+
);
|
|
680
|
+
} 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
|
+
);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
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
|
+
export async function handleMoveFileRequest(
|
|
732
|
+
req: Request,
|
|
733
|
+
corsHeaders: Record<string, string>
|
|
734
|
+
): Promise<Response> {
|
|
735
|
+
try {
|
|
736
|
+
const body = (await req.json()) as MoveFileRequest;
|
|
737
|
+
const { sourcePath, destinationPath, sessionId } = body;
|
|
738
|
+
|
|
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
|
+
);
|
|
752
|
+
}
|
|
753
|
+
|
|
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
|
+
}
|
|
768
|
+
|
|
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
|
+
);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
console.log(`[Server] Moving file: ${sourcePath} -> ${destinationPath}`);
|
|
805
|
+
|
|
806
|
+
const result = await executeMoveFile(
|
|
807
|
+
sourcePath,
|
|
808
|
+
destinationPath,
|
|
809
|
+
sessionId
|
|
810
|
+
);
|
|
811
|
+
|
|
812
|
+
return new Response(
|
|
813
|
+
JSON.stringify({
|
|
814
|
+
destinationPath,
|
|
815
|
+
exitCode: result.exitCode,
|
|
816
|
+
sourcePath,
|
|
817
|
+
success: result.success,
|
|
818
|
+
timestamp: new Date().toISOString(),
|
|
819
|
+
}),
|
|
820
|
+
{
|
|
821
|
+
headers: {
|
|
822
|
+
"Content-Type": "application/json",
|
|
823
|
+
...corsHeaders,
|
|
824
|
+
},
|
|
825
|
+
}
|
|
826
|
+
);
|
|
827
|
+
} catch (error) {
|
|
828
|
+
console.error("[Server] Error in handleMoveFileRequest:", error);
|
|
829
|
+
return new Response(
|
|
830
|
+
JSON.stringify({
|
|
831
|
+
error: "Failed to move file",
|
|
832
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
833
|
+
}),
|
|
834
|
+
{
|
|
835
|
+
headers: {
|
|
836
|
+
"Content-Type": "application/json",
|
|
837
|
+
...corsHeaders,
|
|
838
|
+
},
|
|
839
|
+
status: 500,
|
|
840
|
+
}
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|