@cloudflare/sandbox 0.5.4 → 0.6.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.
Files changed (57) hide show
  1. package/Dockerfile +54 -59
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +12 -1
  6. package/dist/index.js.map +1 -1
  7. package/package.json +13 -8
  8. package/.turbo/turbo-build.log +0 -23
  9. package/CHANGELOG.md +0 -441
  10. package/src/clients/base-client.ts +0 -356
  11. package/src/clients/command-client.ts +0 -133
  12. package/src/clients/file-client.ts +0 -300
  13. package/src/clients/git-client.ts +0 -98
  14. package/src/clients/index.ts +0 -64
  15. package/src/clients/interpreter-client.ts +0 -333
  16. package/src/clients/port-client.ts +0 -105
  17. package/src/clients/process-client.ts +0 -198
  18. package/src/clients/sandbox-client.ts +0 -39
  19. package/src/clients/types.ts +0 -88
  20. package/src/clients/utility-client.ts +0 -156
  21. package/src/errors/adapter.ts +0 -238
  22. package/src/errors/classes.ts +0 -594
  23. package/src/errors/index.ts +0 -109
  24. package/src/file-stream.ts +0 -169
  25. package/src/index.ts +0 -121
  26. package/src/interpreter.ts +0 -168
  27. package/src/openai/index.ts +0 -465
  28. package/src/request-handler.ts +0 -184
  29. package/src/sandbox.ts +0 -1937
  30. package/src/security.ts +0 -119
  31. package/src/sse-parser.ts +0 -144
  32. package/src/storage-mount/credential-detection.ts +0 -41
  33. package/src/storage-mount/errors.ts +0 -51
  34. package/src/storage-mount/index.ts +0 -17
  35. package/src/storage-mount/provider-detection.ts +0 -93
  36. package/src/storage-mount/types.ts +0 -17
  37. package/src/version.ts +0 -6
  38. package/tests/base-client.test.ts +0 -582
  39. package/tests/command-client.test.ts +0 -444
  40. package/tests/file-client.test.ts +0 -831
  41. package/tests/file-stream.test.ts +0 -310
  42. package/tests/get-sandbox.test.ts +0 -172
  43. package/tests/git-client.test.ts +0 -455
  44. package/tests/openai-shell-editor.test.ts +0 -434
  45. package/tests/port-client.test.ts +0 -283
  46. package/tests/process-client.test.ts +0 -649
  47. package/tests/request-handler.test.ts +0 -292
  48. package/tests/sandbox.test.ts +0 -890
  49. package/tests/sse-parser.test.ts +0 -291
  50. package/tests/storage-mount/credential-detection.test.ts +0 -119
  51. package/tests/storage-mount/provider-detection.test.ts +0 -77
  52. package/tests/utility-client.test.ts +0 -339
  53. package/tests/version.test.ts +0 -16
  54. package/tests/wrangler.jsonc +0 -35
  55. package/tsconfig.json +0 -11
  56. package/tsdown.config.ts +0 -13
  57. package/vitest.config.ts +0 -31
@@ -1,292 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from 'vitest';
2
- import { proxyToSandbox, type SandboxEnv } from '../src/request-handler';
3
- import type { Sandbox } from '../src/sandbox';
4
-
5
- // Mock getSandbox from sandbox.ts
6
- vi.mock('../src/sandbox', () => {
7
- const mockFn = vi.fn();
8
- return {
9
- getSandbox: mockFn,
10
- Sandbox: vi.fn()
11
- };
12
- });
13
-
14
- // Import the mock after vi.mock is set up
15
- import { getSandbox } from '../src/sandbox';
16
-
17
- describe('proxyToSandbox - WebSocket Support', () => {
18
- let mockSandbox: Partial<Sandbox>;
19
- let mockEnv: SandboxEnv;
20
-
21
- beforeEach(() => {
22
- vi.clearAllMocks();
23
-
24
- // Mock Sandbox with necessary methods
25
- mockSandbox = {
26
- validatePortToken: vi.fn().mockResolvedValue(true),
27
- fetch: vi.fn().mockResolvedValue(new Response('WebSocket response')),
28
- containerFetch: vi.fn().mockResolvedValue(new Response('HTTP response'))
29
- };
30
-
31
- mockEnv = {
32
- Sandbox: {} as any
33
- };
34
-
35
- vi.mocked(getSandbox).mockReturnValue(mockSandbox as Sandbox);
36
- });
37
-
38
- describe('WebSocket detection and routing', () => {
39
- it('should detect WebSocket upgrade header (case-insensitive)', async () => {
40
- const request = new Request(
41
- 'https://8080-sandbox-token12345678901.example.com/ws',
42
- {
43
- headers: {
44
- Upgrade: 'websocket',
45
- Connection: 'Upgrade'
46
- }
47
- }
48
- );
49
-
50
- await proxyToSandbox(request, mockEnv);
51
-
52
- // Should route through fetch() for WebSocket
53
- expect(mockSandbox.fetch).toHaveBeenCalledTimes(1);
54
- expect(mockSandbox.containerFetch).not.toHaveBeenCalled();
55
- });
56
-
57
- it('should set cf-container-target-port header for WebSocket', async () => {
58
- const request = new Request(
59
- 'https://8080-sandbox-token12345678901.example.com/ws',
60
- {
61
- headers: {
62
- Upgrade: 'websocket'
63
- }
64
- }
65
- );
66
-
67
- await proxyToSandbox(request, mockEnv);
68
-
69
- expect(mockSandbox.fetch).toHaveBeenCalledTimes(1);
70
- const fetchCall = vi.mocked(mockSandbox.fetch as any).mock
71
- .calls[0][0] as Request;
72
- expect(fetchCall.headers.get('cf-container-target-port')).toBe('8080');
73
- });
74
-
75
- it('should preserve original headers for WebSocket', async () => {
76
- const request = new Request(
77
- 'https://8080-sandbox-token12345678901.example.com/ws',
78
- {
79
- headers: {
80
- Upgrade: 'websocket',
81
- 'Sec-WebSocket-Key': 'test-key-123',
82
- 'Sec-WebSocket-Version': '13',
83
- 'User-Agent': 'test-client'
84
- }
85
- }
86
- );
87
-
88
- await proxyToSandbox(request, mockEnv);
89
-
90
- const fetchCall = vi.mocked(mockSandbox.fetch as any).mock
91
- .calls[0][0] as Request;
92
- expect(fetchCall.headers.get('Upgrade')).toBe('websocket');
93
- expect(fetchCall.headers.get('Sec-WebSocket-Key')).toBe('test-key-123');
94
- expect(fetchCall.headers.get('Sec-WebSocket-Version')).toBe('13');
95
- expect(fetchCall.headers.get('User-Agent')).toBe('test-client');
96
- });
97
- });
98
-
99
- describe('HTTP routing (existing behavior)', () => {
100
- it('should route HTTP requests through containerFetch', async () => {
101
- const request = new Request(
102
- 'https://8080-sandbox-token12345678901.example.com/api/data',
103
- {
104
- method: 'GET'
105
- }
106
- );
107
-
108
- await proxyToSandbox(request, mockEnv);
109
-
110
- // Should route through containerFetch() for HTTP
111
- expect(mockSandbox.containerFetch).toHaveBeenCalledTimes(1);
112
- expect(mockSandbox.fetch).not.toHaveBeenCalled();
113
- });
114
-
115
- it('should route POST requests through containerFetch', async () => {
116
- const request = new Request(
117
- 'https://8080-sandbox-token12345678901.example.com/api/data',
118
- {
119
- method: 'POST',
120
- body: JSON.stringify({ data: 'test' }),
121
- headers: {
122
- 'Content-Type': 'application/json'
123
- }
124
- }
125
- );
126
-
127
- await proxyToSandbox(request, mockEnv);
128
-
129
- expect(mockSandbox.containerFetch).toHaveBeenCalledTimes(1);
130
- expect(mockSandbox.fetch).not.toHaveBeenCalled();
131
- });
132
-
133
- it('should not detect SSE as WebSocket', async () => {
134
- const request = new Request(
135
- 'https://8080-sandbox-token12345678901.example.com/events',
136
- {
137
- headers: {
138
- Accept: 'text/event-stream'
139
- }
140
- }
141
- );
142
-
143
- await proxyToSandbox(request, mockEnv);
144
-
145
- // SSE should use HTTP path, not WebSocket path
146
- expect(mockSandbox.containerFetch).toHaveBeenCalledTimes(1);
147
- expect(mockSandbox.fetch).not.toHaveBeenCalled();
148
- });
149
- });
150
-
151
- describe('Token validation', () => {
152
- it('should validate token for both WebSocket and HTTP requests', async () => {
153
- const wsRequest = new Request(
154
- 'https://8080-sandbox-token12345678901.example.com/ws',
155
- {
156
- headers: { Upgrade: 'websocket' }
157
- }
158
- );
159
-
160
- await proxyToSandbox(wsRequest, mockEnv);
161
- expect(mockSandbox.validatePortToken).toHaveBeenCalledWith(
162
- 8080,
163
- 'token12345678901'
164
- );
165
-
166
- vi.clearAllMocks();
167
-
168
- const httpRequest = new Request(
169
- 'https://8080-sandbox-token12345678901.example.com/api'
170
- );
171
- await proxyToSandbox(httpRequest, mockEnv);
172
- expect(mockSandbox.validatePortToken).toHaveBeenCalledWith(
173
- 8080,
174
- 'token12345678901'
175
- );
176
- });
177
-
178
- it('should reject requests with invalid token', async () => {
179
- vi.mocked(mockSandbox.validatePortToken as any).mockResolvedValue(false);
180
-
181
- const request = new Request(
182
- 'https://8080-sandbox-invalidtoken1234.example.com/ws',
183
- {
184
- headers: { Upgrade: 'websocket' }
185
- }
186
- );
187
-
188
- const response = await proxyToSandbox(request, mockEnv);
189
-
190
- expect(response?.status).toBe(404);
191
- expect(mockSandbox.fetch).not.toHaveBeenCalled();
192
-
193
- const body = await response?.json();
194
- expect(body).toMatchObject({
195
- error: 'Access denied: Invalid token or port not exposed',
196
- code: 'INVALID_TOKEN'
197
- });
198
- });
199
-
200
- it('should reject reserved port 3000', async () => {
201
- // Port 3000 is reserved as control plane port and rejected by validatePort()
202
- const request = new Request(
203
- 'https://3000-sandbox-anytoken12345678.example.com/status',
204
- {
205
- method: 'GET'
206
- }
207
- );
208
-
209
- const response = await proxyToSandbox(request, mockEnv);
210
-
211
- // Port 3000 is reserved and should be rejected (extractSandboxRoute returns null)
212
- expect(response).toBeNull();
213
- expect(mockSandbox.validatePortToken).not.toHaveBeenCalled();
214
- expect(mockSandbox.containerFetch).not.toHaveBeenCalled();
215
- });
216
- });
217
-
218
- describe('Port routing', () => {
219
- it('should route to correct port from subdomain', async () => {
220
- const request = new Request(
221
- 'https://9000-sandbox-token12345678901.example.com/api',
222
- {
223
- method: 'GET'
224
- }
225
- );
226
-
227
- await proxyToSandbox(request, mockEnv);
228
-
229
- expect(mockSandbox.validatePortToken).toHaveBeenCalledWith(
230
- 9000,
231
- 'token12345678901'
232
- );
233
- });
234
- });
235
-
236
- describe('Non-sandbox requests', () => {
237
- it('should return null for non-sandbox URLs', async () => {
238
- const request = new Request('https://example.com/some-path');
239
-
240
- const response = await proxyToSandbox(request, mockEnv);
241
-
242
- expect(response).toBeNull();
243
- expect(mockSandbox.fetch).not.toHaveBeenCalled();
244
- expect(mockSandbox.containerFetch).not.toHaveBeenCalled();
245
- });
246
-
247
- it('should return null for invalid subdomain patterns', async () => {
248
- const request = new Request('https://invalid-pattern.example.com');
249
-
250
- const response = await proxyToSandbox(request, mockEnv);
251
-
252
- expect(response).toBeNull();
253
- });
254
- });
255
-
256
- describe('Error handling', () => {
257
- it('should handle errors during WebSocket routing', async () => {
258
- (mockSandbox.fetch as any).mockImplementation(() =>
259
- Promise.reject(new Error('Connection failed'))
260
- );
261
-
262
- const request = new Request(
263
- 'https://8080-sandbox-token12345678901.example.com/ws',
264
- {
265
- headers: {
266
- Upgrade: 'websocket'
267
- }
268
- }
269
- );
270
-
271
- const response = await proxyToSandbox(request, mockEnv);
272
-
273
- expect(response?.status).toBe(500);
274
- const text = await response?.text();
275
- expect(text).toBe('Proxy routing error');
276
- });
277
-
278
- it('should handle errors during HTTP routing', async () => {
279
- (mockSandbox.containerFetch as any).mockImplementation(() =>
280
- Promise.reject(new Error('Service error'))
281
- );
282
-
283
- const request = new Request(
284
- 'https://8080-sandbox-token12345678901.example.com/api'
285
- );
286
-
287
- const response = await proxyToSandbox(request, mockEnv);
288
-
289
- expect(response?.status).toBe(500);
290
- });
291
- });
292
- });