@cloudflare/sandbox 0.3.7 → 0.4.1

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 (120) hide show
  1. package/.turbo/turbo-build.log +44 -0
  2. package/CHANGELOG.md +6 -14
  3. package/Dockerfile +82 -18
  4. package/README.md +89 -824
  5. package/dist/{chunk-JTKON2SH.js → chunk-BCJ7SF3Q.js} +9 -5
  6. package/dist/chunk-BCJ7SF3Q.js.map +1 -0
  7. package/dist/chunk-BFVUNTP4.js +104 -0
  8. package/dist/chunk-BFVUNTP4.js.map +1 -0
  9. package/dist/{chunk-NNGBXDMY.js → chunk-EKSWCBCA.js} +3 -6
  10. package/dist/chunk-EKSWCBCA.js.map +1 -0
  11. package/dist/chunk-HGF554LH.js +2236 -0
  12. package/dist/chunk-HGF554LH.js.map +1 -0
  13. package/dist/{chunk-6UAWTJ5S.js → chunk-Z532A7QC.js} +13 -20
  14. package/dist/{chunk-6UAWTJ5S.js.map → chunk-Z532A7QC.js.map} +1 -1
  15. package/dist/file-stream.d.ts +16 -38
  16. package/dist/file-stream.js +1 -2
  17. package/dist/index.d.ts +6 -5
  18. package/dist/index.js +35 -39
  19. package/dist/index.js.map +1 -1
  20. package/dist/interpreter.d.ts +3 -3
  21. package/dist/interpreter.js +2 -2
  22. package/dist/request-handler.d.ts +4 -3
  23. package/dist/request-handler.js +4 -7
  24. package/dist/sandbox-D9K2ypln.d.ts +583 -0
  25. package/dist/sandbox.d.ts +3 -3
  26. package/dist/sandbox.js +4 -7
  27. package/dist/security.d.ts +4 -3
  28. package/dist/security.js +3 -3
  29. package/dist/sse-parser.js +1 -1
  30. package/package.json +11 -5
  31. package/src/clients/base-client.ts +280 -0
  32. package/src/clients/command-client.ts +115 -0
  33. package/src/clients/file-client.ts +269 -0
  34. package/src/clients/git-client.ts +92 -0
  35. package/src/clients/index.ts +63 -0
  36. package/src/{interpreter-client.ts → clients/interpreter-client.ts} +148 -171
  37. package/src/clients/port-client.ts +105 -0
  38. package/src/clients/process-client.ts +177 -0
  39. package/src/clients/sandbox-client.ts +41 -0
  40. package/src/clients/types.ts +84 -0
  41. package/src/clients/utility-client.ts +94 -0
  42. package/src/errors/adapter.ts +180 -0
  43. package/src/errors/classes.ts +469 -0
  44. package/src/errors/index.ts +105 -0
  45. package/src/file-stream.ts +119 -117
  46. package/src/index.ts +81 -69
  47. package/src/interpreter.ts +17 -8
  48. package/src/request-handler.ts +69 -43
  49. package/src/sandbox.ts +694 -533
  50. package/src/security.ts +14 -23
  51. package/src/sse-parser.ts +4 -8
  52. package/startup.sh +3 -0
  53. package/tests/base-client.test.ts +328 -0
  54. package/tests/command-client.test.ts +407 -0
  55. package/tests/file-client.test.ts +643 -0
  56. package/tests/file-stream.test.ts +306 -0
  57. package/tests/git-client.test.ts +328 -0
  58. package/tests/port-client.test.ts +301 -0
  59. package/tests/process-client.test.ts +658 -0
  60. package/tests/sandbox.test.ts +465 -0
  61. package/tests/sse-parser.test.ts +290 -0
  62. package/tests/utility-client.test.ts +266 -0
  63. package/tests/wrangler.jsonc +35 -0
  64. package/tsconfig.json +9 -1
  65. package/vitest.config.ts +31 -0
  66. package/container_src/bun.lock +0 -76
  67. package/container_src/circuit-breaker.ts +0 -121
  68. package/container_src/control-process.ts +0 -784
  69. package/container_src/handler/exec.ts +0 -185
  70. package/container_src/handler/file.ts +0 -457
  71. package/container_src/handler/git.ts +0 -130
  72. package/container_src/handler/ports.ts +0 -314
  73. package/container_src/handler/process.ts +0 -568
  74. package/container_src/handler/session.ts +0 -92
  75. package/container_src/index.ts +0 -601
  76. package/container_src/interpreter-service.ts +0 -276
  77. package/container_src/isolation.ts +0 -1213
  78. package/container_src/mime-processor.ts +0 -255
  79. package/container_src/package.json +0 -18
  80. package/container_src/runtime/executors/javascript/node_executor.ts +0 -123
  81. package/container_src/runtime/executors/python/ipython_executor.py +0 -338
  82. package/container_src/runtime/executors/typescript/ts_executor.ts +0 -138
  83. package/container_src/runtime/process-pool.ts +0 -464
  84. package/container_src/shell-escape.ts +0 -42
  85. package/container_src/startup.sh +0 -11
  86. package/container_src/types.ts +0 -131
  87. package/dist/chunk-32UDXUPC.js +0 -671
  88. package/dist/chunk-32UDXUPC.js.map +0 -1
  89. package/dist/chunk-5DILEXGY.js +0 -85
  90. package/dist/chunk-5DILEXGY.js.map +0 -1
  91. package/dist/chunk-D3U63BZP.js +0 -240
  92. package/dist/chunk-D3U63BZP.js.map +0 -1
  93. package/dist/chunk-FXYPFGOZ.js +0 -129
  94. package/dist/chunk-FXYPFGOZ.js.map +0 -1
  95. package/dist/chunk-JTKON2SH.js.map +0 -1
  96. package/dist/chunk-NNGBXDMY.js.map +0 -1
  97. package/dist/chunk-SQLJNZ3K.js +0 -674
  98. package/dist/chunk-SQLJNZ3K.js.map +0 -1
  99. package/dist/chunk-W7TVRPBG.js +0 -108
  100. package/dist/chunk-W7TVRPBG.js.map +0 -1
  101. package/dist/client-B3RUab0s.d.ts +0 -225
  102. package/dist/client.d.ts +0 -4
  103. package/dist/client.js +0 -7
  104. package/dist/client.js.map +0 -1
  105. package/dist/errors.d.ts +0 -95
  106. package/dist/errors.js +0 -27
  107. package/dist/errors.js.map +0 -1
  108. package/dist/interpreter-client.d.ts +0 -4
  109. package/dist/interpreter-client.js +0 -9
  110. package/dist/interpreter-client.js.map +0 -1
  111. package/dist/interpreter-types.d.ts +0 -259
  112. package/dist/interpreter-types.js +0 -9
  113. package/dist/interpreter-types.js.map +0 -1
  114. package/dist/types.d.ts +0 -453
  115. package/dist/types.js +0 -45
  116. package/dist/types.js.map +0 -1
  117. package/src/client.ts +0 -1048
  118. package/src/errors.ts +0 -219
  119. package/src/interpreter-types.ts +0 -390
  120. package/src/types.ts +0 -571
@@ -1,6 +1,6 @@
1
+ import { createLogger, type LogContext, TraceContext } from "@repo/shared";
1
2
  import { getSandbox, type Sandbox } from "./sandbox";
2
3
  import {
3
- logSecurityEvent,
4
4
  sanitizeSandboxId,
5
5
  validatePort
6
6
  } from "./security";
@@ -13,12 +13,21 @@ export interface RouteInfo {
13
13
  port: number;
14
14
  sandboxId: string;
15
15
  path: string;
16
+ token: string;
16
17
  }
17
18
 
18
19
  export async function proxyToSandbox<E extends SandboxEnv>(
19
20
  request: Request,
20
21
  env: E
21
22
  ): Promise<Response | null> {
23
+ // Create logger context for this request
24
+ const traceId = TraceContext.fromHeaders(request.headers) || TraceContext.generate();
25
+ const logger = createLogger({
26
+ component: 'sandbox-do',
27
+ traceId,
28
+ operation: 'proxy'
29
+ });
30
+
22
31
  try {
23
32
  const url = new URL(request.url);
24
33
  const routeInfo = extractSandboxRoute(url);
@@ -27,9 +36,40 @@ export async function proxyToSandbox<E extends SandboxEnv>(
27
36
  return null; // Not a request to an exposed container port
28
37
  }
29
38
 
30
- const { sandboxId, port, path } = routeInfo;
39
+ const { sandboxId, port, path, token } = routeInfo;
31
40
  const sandbox = getSandbox(env.Sandbox, sandboxId);
32
41
 
42
+ // Critical security check: Validate token (mandatory for all user ports)
43
+ // Skip check for control plane port 3000
44
+ if (port !== 3000) {
45
+ // Validate the token matches the port
46
+ const isValidToken = await sandbox.validatePortToken(port, token);
47
+ if (!isValidToken) {
48
+ logger.warn('Invalid token access blocked', {
49
+ port,
50
+ sandboxId,
51
+ path,
52
+ hostname: url.hostname,
53
+ url: request.url,
54
+ method: request.method,
55
+ userAgent: request.headers.get('User-Agent') || 'unknown'
56
+ });
57
+
58
+ return new Response(
59
+ JSON.stringify({
60
+ error: `Access denied: Invalid token or port not exposed`,
61
+ code: 'INVALID_TOKEN'
62
+ }),
63
+ {
64
+ status: 404,
65
+ headers: {
66
+ 'Content-Type': 'application/json'
67
+ }
68
+ }
69
+ );
70
+ }
71
+ }
72
+
33
73
  // Build proxy request with proper headers
34
74
  let proxyUrl: string;
35
75
 
@@ -52,43 +92,32 @@ export async function proxyToSandbox<E extends SandboxEnv>(
52
92
  'X-Sandbox-Name': sandboxId, // Pass the friendly name
53
93
  },
54
94
  body: request.body,
95
+ // @ts-expect-error - duplex required for body streaming in modern runtimes
96
+ duplex: 'half',
55
97
  });
56
98
 
57
99
  return sandbox.containerFetch(proxyRequest, port);
58
100
  } catch (error) {
59
- console.error('[Sandbox] Proxy routing error:', error);
101
+ logger.error('Proxy routing error', error instanceof Error ? error : new Error(String(error)));
60
102
  return new Response('Proxy routing error', { status: 500 });
61
103
  }
62
104
  }
63
105
 
64
106
  function extractSandboxRoute(url: URL): RouteInfo | null {
65
- // Parse subdomain pattern: port-sandboxId.domain
66
- const subdomainMatch = url.hostname.match(/^(\d{4,5})-([^.-][^.]*[^.-]|[^.-])\.(.+)$/);
107
+ // Parse subdomain pattern: port-sandboxId-token.domain (tokens mandatory)
108
+ const subdomainMatch = url.hostname.match(/^(\d{4,5})-([^.-][^.]*[^.-]|[^.-])-([a-zA-Z0-9_-]{12,20})\.(.+)$/);
67
109
 
68
110
  if (!subdomainMatch) {
69
- // Log malformed subdomain attempts
70
- if (url.hostname.includes('-') && url.hostname.includes('.')) {
71
- logSecurityEvent('MALFORMED_SUBDOMAIN_ATTEMPT', {
72
- hostname: url.hostname,
73
- url: url.toString()
74
- }, 'medium');
75
- }
76
111
  return null;
77
112
  }
78
113
 
79
114
  const portStr = subdomainMatch[1];
80
115
  const sandboxId = subdomainMatch[2];
81
- const domain = subdomainMatch[3];
116
+ const token = subdomainMatch[3]; // Mandatory token
117
+ const domain = subdomainMatch[4];
82
118
 
83
119
  const port = parseInt(portStr, 10);
84
120
  if (!validatePort(port)) {
85
- logSecurityEvent('INVALID_PORT_IN_SUBDOMAIN', {
86
- port,
87
- portStr,
88
- sandboxId,
89
- hostname: url.hostname,
90
- url: url.toString()
91
- }, 'high');
92
121
  return null;
93
122
  }
94
123
 
@@ -96,49 +125,46 @@ function extractSandboxRoute(url: URL): RouteInfo | null {
96
125
  try {
97
126
  sanitizedSandboxId = sanitizeSandboxId(sandboxId);
98
127
  } catch (error) {
99
- logSecurityEvent('INVALID_SANDBOX_ID_IN_SUBDOMAIN', {
100
- sandboxId,
101
- port,
102
- hostname: url.hostname,
103
- url: url.toString(),
104
- error: error instanceof Error ? error.message : 'Unknown error'
105
- }, 'high');
106
128
  return null;
107
129
  }
108
130
 
109
131
  // DNS subdomain length limit is 63 characters
110
132
  if (sandboxId.length > 63) {
111
- logSecurityEvent('SANDBOX_ID_LENGTH_VIOLATION', {
112
- sandboxId,
113
- length: sandboxId.length,
114
- port,
115
- hostname: url.hostname
116
- }, 'medium');
117
133
  return null;
118
134
  }
119
135
 
120
- logSecurityEvent('SANDBOX_ROUTE_EXTRACTED', {
121
- port,
122
- sandboxId: sanitizedSandboxId,
123
- domain,
124
- path: url.pathname || "/",
125
- hostname: url.hostname
126
- }, 'low');
127
-
128
136
  return {
129
137
  port,
130
138
  sandboxId: sanitizedSandboxId,
131
139
  path: url.pathname || "/",
140
+ token,
132
141
  };
133
142
  }
134
143
 
135
144
  export function isLocalhostPattern(hostname: string): boolean {
145
+ // Handle IPv6 addresses in brackets (with or without port)
146
+ if (hostname.startsWith('[')) {
147
+ if (hostname.includes(']:')) {
148
+ // [::1]:port format
149
+ const ipv6Part = hostname.substring(0, hostname.indexOf(']:') + 1);
150
+ return ipv6Part === '[::1]';
151
+ } else {
152
+ // [::1] format without port
153
+ return hostname === '[::1]';
154
+ }
155
+ }
156
+
157
+ // Handle bare IPv6 without brackets
158
+ if (hostname === '::1') {
159
+ return true;
160
+ }
161
+
162
+ // For IPv4 and regular hostnames, split on colon to remove port
136
163
  const hostPart = hostname.split(":")[0];
164
+
137
165
  return (
138
166
  hostPart === "localhost" ||
139
167
  hostPart === "127.0.0.1" ||
140
- hostPart === "::1" ||
141
- hostPart === "[::1]" ||
142
168
  hostPart === "0.0.0.0"
143
169
  );
144
170
  }