@cloudflare/sandbox 0.0.0-fd5ec7f → 0.0.0-feafd32
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 +129 -0
- package/Dockerfile +41 -15
- package/README.md +297 -11
- package/container_src/bun.lock +76 -0
- package/container_src/circuit-breaker.ts +121 -0
- package/container_src/control-process.ts +784 -0
- package/container_src/handler/exec.ts +99 -252
- 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 +354 -123
- package/container_src/interpreter-service.ts +276 -0
- package/container_src/isolation.ts +1049 -0
- package/container_src/mime-processor.ts +255 -0
- package/container_src/package.json +9 -0
- 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 +11 -0
- package/container_src/types.ts +35 -12
- package/package.json +2 -2
- package/src/client.ts +204 -233
- package/src/errors.ts +219 -0
- package/src/index.ts +70 -15
- package/src/interpreter-client.ts +352 -0
- package/src/interpreter-types.ts +390 -0
- package/src/interpreter.ts +150 -0
- package/src/sandbox.ts +502 -405
- package/src/types.ts +140 -24
|
@@ -1,337 +1,184 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type {
|
|
1
|
+
import type { SessionManager } from "../isolation";
|
|
2
|
+
import type { SessionExecRequest } from "../types";
|
|
3
3
|
|
|
4
|
-
function
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
exitCode: number;
|
|
13
|
-
}> {
|
|
14
|
-
return new Promise((resolve, reject) => {
|
|
15
|
-
const spawnOptions: SpawnOptions = {
|
|
16
|
-
shell: true,
|
|
17
|
-
stdio: ["pipe", "pipe", "pipe"] as const,
|
|
18
|
-
detached: options.background || false,
|
|
19
|
-
cwd: options.cwd,
|
|
20
|
-
env: options.env ? { ...process.env, ...options.env } : process.env
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const child = spawn(command, spawnOptions);
|
|
24
|
-
|
|
25
|
-
// Store the process reference for cleanup if sessionId is provided
|
|
26
|
-
if (options.sessionId && sessions.has(options.sessionId)) {
|
|
27
|
-
const session = sessions.get(options.sessionId)!;
|
|
28
|
-
session.activeProcess = child;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
let stdout = "";
|
|
32
|
-
let stderr = "";
|
|
33
|
-
|
|
34
|
-
child.stdout?.on("data", (data) => {
|
|
35
|
-
stdout += data.toString();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
child.stderr?.on("data", (data) => {
|
|
39
|
-
stderr += data.toString();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
if (options.background) {
|
|
43
|
-
// For background processes, unref and return quickly
|
|
44
|
-
child.unref();
|
|
45
|
-
|
|
46
|
-
// Collect initial output for 100ms then return
|
|
47
|
-
setTimeout(() => {
|
|
48
|
-
resolve({
|
|
49
|
-
exitCode: 0, // Process is still running
|
|
50
|
-
stderr,
|
|
51
|
-
stdout,
|
|
52
|
-
success: true,
|
|
53
|
-
});
|
|
54
|
-
}, 100);
|
|
55
|
-
|
|
56
|
-
// Still handle errors
|
|
57
|
-
child.on("error", (error) => {
|
|
58
|
-
console.error(`[Server] Background process error: ${command}`, error);
|
|
59
|
-
// Don't reject since we might have already resolved
|
|
60
|
-
});
|
|
61
|
-
} else {
|
|
62
|
-
// Normal synchronous execution
|
|
63
|
-
child.on("close", (code) => {
|
|
64
|
-
// Clear the active process reference
|
|
65
|
-
if (options.sessionId && sessions.has(options.sessionId)) {
|
|
66
|
-
const session = sessions.get(options.sessionId)!;
|
|
67
|
-
session.activeProcess = null;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
console.log(`[Server] Command completed: ${command}, Exit code: ${code}`);
|
|
4
|
+
export async function handleExecuteRequest(
|
|
5
|
+
req: Request,
|
|
6
|
+
corsHeaders: Record<string, string>,
|
|
7
|
+
sessionManager: SessionManager
|
|
8
|
+
) {
|
|
9
|
+
try {
|
|
10
|
+
const body = (await req.json()) as SessionExecRequest;
|
|
11
|
+
const { id, command } = body;
|
|
71
12
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
stdout,
|
|
76
|
-
success: code === 0,
|
|
77
|
-
});
|
|
78
|
-
});
|
|
13
|
+
console.log(
|
|
14
|
+
`[Container] Session exec request for '${id}': ${command}`
|
|
15
|
+
);
|
|
79
16
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
17
|
+
if (!id || !command) {
|
|
18
|
+
return new Response(
|
|
19
|
+
JSON.stringify({
|
|
20
|
+
error: "Session ID and command are required",
|
|
21
|
+
}),
|
|
22
|
+
{
|
|
23
|
+
status: 400,
|
|
24
|
+
headers: {
|
|
25
|
+
"Content-Type": "application/json",
|
|
26
|
+
...corsHeaders,
|
|
27
|
+
},
|
|
85
28
|
}
|
|
86
|
-
|
|
87
|
-
reject(error);
|
|
88
|
-
});
|
|
29
|
+
);
|
|
89
30
|
}
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
31
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
32
|
+
const session = sessionManager.getSession(id);
|
|
33
|
+
if (!session) {
|
|
34
|
+
console.error(`[Container] Session '${id}' not found!`);
|
|
35
|
+
const availableSessions = sessionManager.listSessions();
|
|
36
|
+
console.log(
|
|
37
|
+
`[Container] Available sessions: ${
|
|
38
|
+
availableSessions.join(", ") || "none"
|
|
39
|
+
}`
|
|
40
|
+
);
|
|
101
41
|
|
|
102
|
-
if (!command || typeof command !== "string") {
|
|
103
42
|
return new Response(
|
|
104
43
|
JSON.stringify({
|
|
105
|
-
error:
|
|
44
|
+
error: `Session '${id}' not found`,
|
|
45
|
+
availableSessions,
|
|
106
46
|
}),
|
|
107
47
|
{
|
|
48
|
+
status: 404,
|
|
108
49
|
headers: {
|
|
109
50
|
"Content-Type": "application/json",
|
|
110
51
|
...corsHeaders,
|
|
111
52
|
},
|
|
112
|
-
status: 400,
|
|
113
53
|
}
|
|
114
54
|
);
|
|
115
55
|
}
|
|
116
56
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const result = await executeCommand(sessions, command, { sessionId, background, cwd, env });
|
|
57
|
+
const result = await session.exec(command);
|
|
120
58
|
|
|
121
|
-
return new Response(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
exitCode: result.exitCode,
|
|
125
|
-
stderr: result.stderr,
|
|
126
|
-
stdout: result.stdout,
|
|
127
|
-
success: result.success,
|
|
128
|
-
timestamp: new Date().toISOString(),
|
|
129
|
-
}),
|
|
130
|
-
{
|
|
131
|
-
headers: {
|
|
132
|
-
"Content-Type": "application/json",
|
|
133
|
-
...corsHeaders,
|
|
134
|
-
},
|
|
135
|
-
}
|
|
136
|
-
);
|
|
59
|
+
return new Response(JSON.stringify(result), {
|
|
60
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
61
|
+
});
|
|
137
62
|
} catch (error) {
|
|
138
|
-
console.error("[
|
|
63
|
+
console.error("[Container] Session exec failed:", error);
|
|
139
64
|
return new Response(
|
|
140
65
|
JSON.stringify({
|
|
141
|
-
error: "
|
|
142
|
-
message:
|
|
66
|
+
error: "Command execution failed",
|
|
67
|
+
message:
|
|
68
|
+
error instanceof Error ? error.message : String(error),
|
|
143
69
|
}),
|
|
144
70
|
{
|
|
71
|
+
status: 500,
|
|
145
72
|
headers: {
|
|
146
73
|
"Content-Type": "application/json",
|
|
147
74
|
...corsHeaders,
|
|
148
75
|
},
|
|
149
|
-
status: 500,
|
|
150
76
|
}
|
|
151
77
|
);
|
|
152
78
|
}
|
|
153
79
|
}
|
|
154
80
|
|
|
155
81
|
export async function handleStreamingExecuteRequest(
|
|
156
|
-
sessions: Map<string, SessionData>,
|
|
157
82
|
req: Request,
|
|
83
|
+
sessionManager: SessionManager,
|
|
158
84
|
corsHeaders: Record<string, string>
|
|
159
|
-
)
|
|
85
|
+
) {
|
|
160
86
|
try {
|
|
161
|
-
const body = (await req.json()) as
|
|
162
|
-
const {
|
|
87
|
+
const body = (await req.json()) as SessionExecRequest;
|
|
88
|
+
const { id, command } = body;
|
|
89
|
+
|
|
90
|
+
console.log(
|
|
91
|
+
`[Container] Session streaming exec request for '${id}': ${command}`
|
|
92
|
+
);
|
|
163
93
|
|
|
164
|
-
if (!
|
|
94
|
+
if (!id || !command) {
|
|
165
95
|
return new Response(
|
|
166
96
|
JSON.stringify({
|
|
167
|
-
error: "
|
|
97
|
+
error: "Session ID and command are required",
|
|
168
98
|
}),
|
|
169
99
|
{
|
|
100
|
+
status: 400,
|
|
170
101
|
headers: {
|
|
171
102
|
"Content-Type": "application/json",
|
|
172
103
|
...corsHeaders,
|
|
173
104
|
},
|
|
174
|
-
status: 400,
|
|
175
105
|
}
|
|
176
106
|
);
|
|
177
107
|
}
|
|
178
108
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const stream = new ReadableStream({
|
|
184
|
-
start(controller) {
|
|
185
|
-
const spawnOptions: SpawnOptions = {
|
|
186
|
-
shell: true,
|
|
187
|
-
stdio: ["pipe", "pipe", "pipe"] as const,
|
|
188
|
-
detached: background || false,
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
const child = spawn(command, spawnOptions);
|
|
109
|
+
const session = sessionManager.getSession(id);
|
|
110
|
+
if (!session) {
|
|
111
|
+
console.error(`[Container] Session '${id}' not found!`);
|
|
112
|
+
const availableSessions = sessionManager.listSessions();
|
|
192
113
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
114
|
+
return new Response(
|
|
115
|
+
JSON.stringify({
|
|
116
|
+
error: `Session '${id}' not found`,
|
|
117
|
+
availableSessions,
|
|
118
|
+
}),
|
|
119
|
+
{
|
|
120
|
+
status: 404,
|
|
121
|
+
headers: {
|
|
122
|
+
"Content-Type": "application/json",
|
|
123
|
+
...corsHeaders,
|
|
124
|
+
},
|
|
202
125
|
}
|
|
126
|
+
);
|
|
127
|
+
}
|
|
203
128
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
)
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
child.stdout?.on("data", (data) => {
|
|
220
|
-
const output = data.toString();
|
|
221
|
-
stdout += output;
|
|
222
|
-
|
|
223
|
-
// Send real-time output
|
|
224
|
-
controller.enqueue(
|
|
225
|
-
new TextEncoder().encode(
|
|
226
|
-
`data: ${JSON.stringify({
|
|
227
|
-
type: "stdout",
|
|
228
|
-
timestamp: new Date().toISOString(),
|
|
229
|
-
data: output,
|
|
230
|
-
command,
|
|
231
|
-
})}\n\n`
|
|
232
|
-
)
|
|
233
|
-
);
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
child.stderr?.on("data", (data) => {
|
|
237
|
-
const output = data.toString();
|
|
238
|
-
stderr += output;
|
|
239
|
-
|
|
240
|
-
// Send real-time error output
|
|
241
|
-
controller.enqueue(
|
|
242
|
-
new TextEncoder().encode(
|
|
243
|
-
`data: ${JSON.stringify({
|
|
244
|
-
type: "stderr",
|
|
245
|
-
timestamp: new Date().toISOString(),
|
|
246
|
-
data: output,
|
|
247
|
-
command,
|
|
248
|
-
})}\n\n`
|
|
249
|
-
)
|
|
250
|
-
);
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
child.on("close", (code) => {
|
|
254
|
-
// Clear the active process reference
|
|
255
|
-
if (sessionId && sessions.has(sessionId)) {
|
|
256
|
-
const session = sessions.get(sessionId)!;
|
|
257
|
-
session.activeProcess = null;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
console.log(
|
|
261
|
-
`[Server] Command completed: ${command}, Exit code: ${code}`
|
|
262
|
-
);
|
|
263
|
-
|
|
264
|
-
// Send command completion event
|
|
265
|
-
controller.enqueue(
|
|
266
|
-
new TextEncoder().encode(
|
|
267
|
-
`data: ${JSON.stringify({
|
|
268
|
-
type: "complete",
|
|
269
|
-
timestamp: new Date().toISOString(),
|
|
270
|
-
command,
|
|
271
|
-
exitCode: code,
|
|
272
|
-
result: {
|
|
273
|
-
success: code === 0,
|
|
274
|
-
exitCode: code,
|
|
275
|
-
stdout,
|
|
276
|
-
stderr,
|
|
277
|
-
command,
|
|
278
|
-
timestamp: new Date().toISOString(),
|
|
279
|
-
},
|
|
280
|
-
})}\n\n`
|
|
281
|
-
)
|
|
282
|
-
);
|
|
283
|
-
|
|
284
|
-
// For non-background processes, close the stream
|
|
285
|
-
// For background processes with streaming, the stream stays open
|
|
286
|
-
if (!background) {
|
|
287
|
-
controller.close();
|
|
288
|
-
}
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
child.on("error", (error) => {
|
|
292
|
-
// Clear the active process reference
|
|
293
|
-
if (sessionId && sessions.has(sessionId)) {
|
|
294
|
-
const session = sessions.get(sessionId)!;
|
|
295
|
-
session.activeProcess = null;
|
|
129
|
+
// Create a streaming response using the actual streaming method
|
|
130
|
+
const stream = new ReadableStream({
|
|
131
|
+
async start(controller) {
|
|
132
|
+
try {
|
|
133
|
+
// Use the streaming generator method
|
|
134
|
+
for await (const event of session.execStream(command)) {
|
|
135
|
+
// Forward each event as SSE
|
|
136
|
+
controller.enqueue(
|
|
137
|
+
new TextEncoder().encode(
|
|
138
|
+
`data: ${JSON.stringify(event)}\n\n`
|
|
139
|
+
)
|
|
140
|
+
);
|
|
296
141
|
}
|
|
297
|
-
|
|
142
|
+
controller.close();
|
|
143
|
+
} catch (error) {
|
|
298
144
|
controller.enqueue(
|
|
299
145
|
new TextEncoder().encode(
|
|
300
146
|
`data: ${JSON.stringify({
|
|
301
147
|
type: "error",
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
148
|
+
message:
|
|
149
|
+
error instanceof Error
|
|
150
|
+
? error.message
|
|
151
|
+
: String(error),
|
|
305
152
|
})}\n\n`
|
|
306
153
|
)
|
|
307
154
|
);
|
|
308
|
-
|
|
309
155
|
controller.close();
|
|
310
|
-
}
|
|
156
|
+
}
|
|
311
157
|
},
|
|
312
158
|
});
|
|
313
159
|
|
|
314
160
|
return new Response(stream, {
|
|
315
161
|
headers: {
|
|
162
|
+
"Content-Type": "text/event-stream",
|
|
316
163
|
"Cache-Control": "no-cache",
|
|
317
164
|
Connection: "keep-alive",
|
|
318
|
-
"Content-Type": "text/event-stream",
|
|
319
165
|
...corsHeaders,
|
|
320
166
|
},
|
|
321
167
|
});
|
|
322
168
|
} catch (error) {
|
|
323
|
-
console.error("[
|
|
169
|
+
console.error("[Container] Session stream exec failed:", error);
|
|
324
170
|
return new Response(
|
|
325
171
|
JSON.stringify({
|
|
326
|
-
error: "
|
|
327
|
-
message:
|
|
172
|
+
error: "Stream execution failed",
|
|
173
|
+
message:
|
|
174
|
+
error instanceof Error ? error.message : String(error),
|
|
328
175
|
}),
|
|
329
176
|
{
|
|
177
|
+
status: 500,
|
|
330
178
|
headers: {
|
|
331
179
|
"Content-Type": "application/json",
|
|
332
180
|
...corsHeaders,
|
|
333
181
|
},
|
|
334
|
-
status: 500,
|
|
335
182
|
}
|
|
336
183
|
);
|
|
337
184
|
}
|