@cloudflare/sandbox 0.0.0-e1fa354 → 0.0.0-e489cbb

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/Dockerfile +107 -38
  3. package/README.md +89 -771
  4. package/dist/chunk-53JFOF7F.js +2352 -0
  5. package/dist/chunk-53JFOF7F.js.map +1 -0
  6. package/dist/chunk-BFVUNTP4.js +104 -0
  7. package/dist/chunk-BFVUNTP4.js.map +1 -0
  8. package/dist/chunk-EKSWCBCA.js +86 -0
  9. package/dist/chunk-EKSWCBCA.js.map +1 -0
  10. package/dist/chunk-JXZMAU2C.js +559 -0
  11. package/dist/chunk-JXZMAU2C.js.map +1 -0
  12. package/dist/chunk-Z532A7QC.js +78 -0
  13. package/dist/chunk-Z532A7QC.js.map +1 -0
  14. package/dist/file-stream.d.ts +43 -0
  15. package/dist/file-stream.js +9 -0
  16. package/dist/file-stream.js.map +1 -0
  17. package/dist/index.d.ts +9 -0
  18. package/dist/index.js +66 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/interpreter.d.ts +33 -0
  21. package/dist/interpreter.js +8 -0
  22. package/dist/interpreter.js.map +1 -0
  23. package/dist/request-handler.d.ts +18 -0
  24. package/dist/request-handler.js +12 -0
  25. package/dist/request-handler.js.map +1 -0
  26. package/dist/sandbox-D9K2ypln.d.ts +583 -0
  27. package/dist/sandbox.d.ts +4 -0
  28. package/dist/sandbox.js +12 -0
  29. package/dist/sandbox.js.map +1 -0
  30. package/dist/security.d.ts +31 -0
  31. package/dist/security.js +13 -0
  32. package/dist/security.js.map +1 -0
  33. package/dist/sse-parser.d.ts +28 -0
  34. package/dist/sse-parser.js +11 -0
  35. package/dist/sse-parser.js.map +1 -0
  36. package/package.json +13 -5
  37. package/src/clients/base-client.ts +280 -0
  38. package/src/clients/command-client.ts +115 -0
  39. package/src/clients/file-client.ts +269 -0
  40. package/src/clients/git-client.ts +92 -0
  41. package/src/clients/index.ts +63 -0
  42. package/src/{jupyter-client.ts → clients/interpreter-client.ts} +148 -168
  43. package/src/clients/port-client.ts +105 -0
  44. package/src/clients/process-client.ts +177 -0
  45. package/src/clients/sandbox-client.ts +41 -0
  46. package/src/clients/types.ts +84 -0
  47. package/src/clients/utility-client.ts +94 -0
  48. package/src/errors/adapter.ts +180 -0
  49. package/src/errors/classes.ts +469 -0
  50. package/src/errors/index.ts +105 -0
  51. package/src/file-stream.ts +164 -0
  52. package/src/index.ts +82 -53
  53. package/src/interpreter.ts +22 -13
  54. package/src/request-handler.ts +69 -43
  55. package/src/sandbox.ts +697 -527
  56. package/src/security.ts +14 -23
  57. package/src/sse-parser.ts +4 -8
  58. package/startup.sh +3 -0
  59. package/tests/base-client.test.ts +328 -0
  60. package/tests/command-client.test.ts +407 -0
  61. package/tests/file-client.test.ts +643 -0
  62. package/tests/file-stream.test.ts +306 -0
  63. package/tests/git-client.test.ts +328 -0
  64. package/tests/port-client.test.ts +301 -0
  65. package/tests/process-client.test.ts +658 -0
  66. package/tests/sandbox.test.ts +465 -0
  67. package/tests/sse-parser.test.ts +290 -0
  68. package/tests/utility-client.test.ts +266 -0
  69. package/tests/wrangler.jsonc +35 -0
  70. package/tsconfig.json +9 -1
  71. package/vitest.config.ts +31 -0
  72. package/container_src/bun.lock +0 -122
  73. package/container_src/circuit-breaker.ts +0 -121
  74. package/container_src/control-process.ts +0 -784
  75. package/container_src/handler/exec.ts +0 -185
  76. package/container_src/handler/file.ts +0 -406
  77. package/container_src/handler/git.ts +0 -130
  78. package/container_src/handler/ports.ts +0 -314
  79. package/container_src/handler/process.ts +0 -568
  80. package/container_src/handler/session.ts +0 -92
  81. package/container_src/index.ts +0 -601
  82. package/container_src/isolation.ts +0 -1038
  83. package/container_src/jupyter-server.ts +0 -579
  84. package/container_src/jupyter-service.ts +0 -461
  85. package/container_src/jupyter_config.py +0 -48
  86. package/container_src/mime-processor.ts +0 -255
  87. package/container_src/package.json +0 -18
  88. package/container_src/shell-escape.ts +0 -42
  89. package/container_src/startup.sh +0 -84
  90. package/container_src/types.ts +0 -131
  91. package/src/client.ts +0 -1009
  92. package/src/errors.ts +0 -218
  93. package/src/interpreter-types.ts +0 -383
  94. package/src/types.ts +0 -502
@@ -1,130 +0,0 @@
1
- import { randomBytes } from "node:crypto";
2
- import type { Session, SessionManager } from "../isolation";
3
- import type { GitCheckoutRequest } from "../types";
4
-
5
- async function executeGitCheckout(
6
- sessionManager: SessionManager,
7
- sessionId: string | undefined,
8
- repoUrl: string,
9
- branch: string,
10
- targetDir: string
11
- ): Promise<{
12
- success: boolean;
13
- stdout: string;
14
- stderr: string;
15
- exitCode: number;
16
- }> {
17
- // Execute git clone through the session to respect working directory
18
- const command = `git clone -b ${branch} ${repoUrl} ${targetDir}`;
19
-
20
- // Use specific session if provided, otherwise use default session
21
- let session: Session | undefined;
22
-
23
- if (sessionId) {
24
- session = sessionManager.getSession(sessionId);
25
- if (!session) {
26
- throw new Error(`Session '${sessionId}' not found`);
27
- }
28
- } else {
29
- // Use the centralized method to get or create default session
30
- session = await sessionManager.getOrCreateDefaultSession();
31
- }
32
-
33
- return session.exec(command);
34
- }
35
-
36
- export async function handleGitCheckoutRequest(
37
- req: Request,
38
- corsHeaders: Record<string, string>,
39
- sessionManager: SessionManager
40
- ): Promise<Response> {
41
- try {
42
- const body = (await req.json()) as GitCheckoutRequest;
43
- const { repoUrl, branch = "main", targetDir, sessionId } = body;
44
-
45
- if (!repoUrl || typeof repoUrl !== "string") {
46
- return new Response(
47
- JSON.stringify({
48
- error: "Repository URL is required and must be a string",
49
- }),
50
- {
51
- headers: {
52
- "Content-Type": "application/json",
53
- ...corsHeaders,
54
- },
55
- status: 400,
56
- }
57
- );
58
- }
59
-
60
- // Validate repository URL format
61
- const urlPattern =
62
- /^(https?:\/\/|git@|ssh:\/\/).*\.git$|^https?:\/\/.*\/.*$/;
63
- if (!urlPattern.test(repoUrl)) {
64
- return new Response(
65
- JSON.stringify({
66
- error: "Invalid repository URL format",
67
- }),
68
- {
69
- headers: {
70
- "Content-Type": "application/json",
71
- ...corsHeaders,
72
- },
73
- status: 400,
74
- }
75
- );
76
- }
77
-
78
- // Generate target directory if not provided using cryptographically secure randomness
79
- const checkoutDir =
80
- targetDir ||
81
- `repo_${Date.now()}_${randomBytes(6).toString('hex')}`;
82
-
83
- console.log(
84
- `[Server] Checking out repository: ${repoUrl} to ${checkoutDir}${sessionId ? ` in session: ${sessionId}` : ''}`
85
- );
86
-
87
- const result = await executeGitCheckout(
88
- sessionManager,
89
- sessionId,
90
- repoUrl,
91
- branch,
92
- checkoutDir
93
- );
94
-
95
- return new Response(
96
- JSON.stringify({
97
- branch,
98
- exitCode: result.exitCode,
99
- repoUrl,
100
- stderr: result.stderr,
101
- stdout: result.stdout,
102
- success: result.success,
103
- targetDir: checkoutDir,
104
- timestamp: new Date().toISOString(),
105
- }),
106
- {
107
- headers: {
108
- "Content-Type": "application/json",
109
- ...corsHeaders,
110
- },
111
- }
112
- );
113
- } catch (error) {
114
- console.error("[Server] Error in handleGitCheckoutRequest:", error);
115
- return new Response(
116
- JSON.stringify({
117
- error: "Failed to checkout repository",
118
- message: error instanceof Error ? error.message : "Unknown error",
119
- }),
120
- {
121
- headers: {
122
- "Content-Type": "application/json",
123
- ...corsHeaders,
124
- },
125
- status: 500,
126
- }
127
- );
128
- }
129
- }
130
-
@@ -1,314 +0,0 @@
1
- import type { ExposePortRequest, UnexposePortRequest } from "../types";
2
-
3
- export async function handleExposePortRequest(
4
- exposedPorts: Map<number, { name?: string; exposedAt: Date }>,
5
- req: Request,
6
- corsHeaders: Record<string, string>
7
- ): Promise<Response> {
8
- try {
9
- const body = (await req.json()) as ExposePortRequest;
10
- const { port, name } = body;
11
-
12
- if (!port || typeof port !== "number") {
13
- return new Response(
14
- JSON.stringify({
15
- error: "Port is required and must be a number",
16
- }),
17
- {
18
- headers: {
19
- "Content-Type": "application/json",
20
- ...corsHeaders,
21
- },
22
- status: 400,
23
- }
24
- );
25
- }
26
-
27
- // Validate port range
28
- if (port < 1 || port > 65535) {
29
- return new Response(
30
- JSON.stringify({
31
- error: "Port must be between 1 and 65535",
32
- }),
33
- {
34
- headers: {
35
- "Content-Type": "application/json",
36
- ...corsHeaders,
37
- },
38
- status: 400,
39
- }
40
- );
41
- }
42
-
43
- // Store the exposed port
44
- exposedPorts.set(port, { name, exposedAt: new Date() });
45
-
46
- console.log(`[Server] Exposed port: ${port}${name ? ` (${name})` : ""}`);
47
-
48
- return new Response(
49
- JSON.stringify({
50
- port,
51
- name,
52
- exposedAt: new Date().toISOString(),
53
- success: true,
54
- timestamp: new Date().toISOString(),
55
- }),
56
- {
57
- headers: {
58
- "Content-Type": "application/json",
59
- ...corsHeaders,
60
- },
61
- }
62
- );
63
- } catch (error) {
64
- console.error("[Server] Error in handleExposePortRequest:", error);
65
- return new Response(
66
- JSON.stringify({
67
- error: "Failed to expose port",
68
- message: error instanceof Error ? error.message : "Unknown error",
69
- }),
70
- {
71
- headers: {
72
- "Content-Type": "application/json",
73
- ...corsHeaders,
74
- },
75
- status: 500,
76
- }
77
- );
78
- }
79
- }
80
-
81
- export async function handleUnexposePortRequest(
82
- exposedPorts: Map<number, { name?: string; exposedAt: Date }>,
83
- req: Request,
84
- corsHeaders: Record<string, string>
85
- ): Promise<Response> {
86
- try {
87
- const body = (await req.json()) as UnexposePortRequest;
88
- const { port } = body;
89
-
90
- if (!port || typeof port !== "number") {
91
- return new Response(
92
- JSON.stringify({
93
- error: "Port is required and must be a number",
94
- }),
95
- {
96
- headers: {
97
- "Content-Type": "application/json",
98
- ...corsHeaders,
99
- },
100
- status: 400,
101
- }
102
- );
103
- }
104
-
105
- // Check if port is exposed
106
- if (!exposedPorts.has(port)) {
107
- return new Response(
108
- JSON.stringify({
109
- error: "Port is not exposed",
110
- }),
111
- {
112
- headers: {
113
- "Content-Type": "application/json",
114
- ...corsHeaders,
115
- },
116
- status: 404,
117
- }
118
- );
119
- }
120
-
121
- // Remove the exposed port
122
- exposedPorts.delete(port);
123
-
124
- console.log(`[Server] Unexposed port: ${port}`);
125
-
126
- return new Response(
127
- JSON.stringify({
128
- port,
129
- success: true,
130
- timestamp: new Date().toISOString(),
131
- }),
132
- {
133
- headers: {
134
- "Content-Type": "application/json",
135
- ...corsHeaders,
136
- },
137
- }
138
- );
139
- } catch (error) {
140
- console.error("[Server] Error in handleUnexposePortRequest:", error);
141
- return new Response(
142
- JSON.stringify({
143
- error: "Failed to unexpose port",
144
- message: error instanceof Error ? error.message : "Unknown error",
145
- }),
146
- {
147
- headers: {
148
- "Content-Type": "application/json",
149
- ...corsHeaders,
150
- },
151
- status: 500,
152
- }
153
- );
154
- }
155
- }
156
-
157
- export async function handleGetExposedPortsRequest(
158
- exposedPorts: Map<number, { name?: string; exposedAt: Date }>,
159
- req: Request,
160
- corsHeaders: Record<string, string>
161
- ): Promise<Response> {
162
- try {
163
- const ports = Array.from(exposedPorts.entries()).map(([port, info]) => ({
164
- port,
165
- name: info.name,
166
- exposedAt: info.exposedAt.toISOString(),
167
- }));
168
-
169
- return new Response(
170
- JSON.stringify({
171
- ports,
172
- count: ports.length,
173
- timestamp: new Date().toISOString(),
174
- }),
175
- {
176
- headers: {
177
- "Content-Type": "application/json",
178
- ...corsHeaders,
179
- },
180
- }
181
- );
182
- } catch (error) {
183
- console.error("[Server] Error in handleGetExposedPortsRequest:", error);
184
- return new Response(
185
- JSON.stringify({
186
- error: "Failed to get exposed ports",
187
- message: error instanceof Error ? error.message : "Unknown error",
188
- }),
189
- {
190
- headers: {
191
- "Content-Type": "application/json",
192
- ...corsHeaders,
193
- },
194
- status: 500,
195
- }
196
- );
197
- }
198
- }
199
-
200
- export async function handleProxyRequest(
201
- exposedPorts: Map<number, { name?: string; exposedAt: Date }>,
202
- req: Request,
203
- corsHeaders: Record<string, string>
204
- ): Promise<Response> {
205
- try {
206
- const url = new URL(req.url);
207
- const pathParts = url.pathname.split("/");
208
-
209
- // Extract port from path like /proxy/3000/...
210
- if (pathParts.length < 3) {
211
- return new Response(
212
- JSON.stringify({
213
- error: "Invalid proxy path",
214
- }),
215
- {
216
- headers: {
217
- "Content-Type": "application/json",
218
- ...corsHeaders,
219
- },
220
- status: 400,
221
- }
222
- );
223
- }
224
-
225
- const port = parseInt(pathParts[2]);
226
- if (!port || Number.isNaN(port)) {
227
- return new Response(
228
- JSON.stringify({
229
- error: "Invalid port in proxy path",
230
- }),
231
- {
232
- headers: {
233
- "Content-Type": "application/json",
234
- ...corsHeaders,
235
- },
236
- status: 400,
237
- }
238
- );
239
- }
240
-
241
- // Check if port is exposed
242
- if (!exposedPorts.has(port)) {
243
- return new Response(
244
- JSON.stringify({
245
- error: `Port ${port} is not exposed`,
246
- }),
247
- {
248
- headers: {
249
- "Content-Type": "application/json",
250
- ...corsHeaders,
251
- },
252
- status: 404,
253
- }
254
- );
255
- }
256
-
257
- // Construct the target URL
258
- const targetPath = `/${pathParts.slice(3).join("/")}`;
259
- // Use 127.0.0.1 instead of localhost for more reliable container networking
260
- const targetUrl = `http://127.0.0.1:${port}${targetPath}${url.search}`;
261
-
262
- console.log(`[Server] Proxying request to: ${targetUrl}`);
263
- console.log(`[Server] Method: ${req.method}, Port: ${port}, Path: ${targetPath}`);
264
-
265
- try {
266
- // Forward the request to the target port
267
- const targetResponse = await fetch(targetUrl, {
268
- method: req.method,
269
- headers: req.headers,
270
- body: req.body,
271
- });
272
-
273
- // Return the response from the target
274
- return new Response(targetResponse.body, {
275
- status: targetResponse.status,
276
- statusText: targetResponse.statusText,
277
- headers: {
278
- ...Object.fromEntries(targetResponse.headers.entries()),
279
- ...corsHeaders,
280
- },
281
- });
282
- } catch (fetchError) {
283
- console.error(`[Server] Error proxying to port ${port}:`, fetchError);
284
- return new Response(
285
- JSON.stringify({
286
- error: `Service on port ${port} is not responding`,
287
- message: fetchError instanceof Error ? fetchError.message : "Unknown error",
288
- }),
289
- {
290
- headers: {
291
- "Content-Type": "application/json",
292
- ...corsHeaders,
293
- },
294
- status: 502,
295
- }
296
- );
297
- }
298
- } catch (error) {
299
- console.error("[Server] Error in handleProxyRequest:", error);
300
- return new Response(
301
- JSON.stringify({
302
- error: "Failed to proxy request",
303
- message: error instanceof Error ? error.message : "Unknown error",
304
- }),
305
- {
306
- headers: {
307
- "Content-Type": "application/json",
308
- ...corsHeaders,
309
- },
310
- status: 500,
311
- }
312
- );
313
- }
314
- }