@cloudflare/sandbox 0.3.7 → 0.4.2

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 +8 -10
  3. package/Dockerfile +82 -18
  4. package/README.md +89 -824
  5. package/dist/chunk-53JFOF7F.js +2352 -0
  6. package/dist/chunk-53JFOF7F.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-JXZMAU2C.js +559 -0
  12. package/dist/chunk-JXZMAU2C.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 +45 -38
  19. package/dist/interpreter.d.ts +3 -3
  20. package/dist/interpreter.js +2 -2
  21. package/dist/request-handler.d.ts +4 -3
  22. package/dist/request-handler.js +4 -7
  23. package/dist/sandbox-D9K2ypln.d.ts +583 -0
  24. package/dist/sandbox.d.ts +3 -3
  25. package/dist/sandbox.js +4 -7
  26. package/dist/security.d.ts +4 -3
  27. package/dist/security.js +3 -3
  28. package/dist/sse-parser.js +1 -1
  29. package/package.json +12 -4
  30. package/src/clients/base-client.ts +280 -0
  31. package/src/clients/command-client.ts +115 -0
  32. package/src/clients/file-client.ts +269 -0
  33. package/src/clients/git-client.ts +92 -0
  34. package/src/clients/index.ts +63 -0
  35. package/src/{interpreter-client.ts → clients/interpreter-client.ts} +148 -171
  36. package/src/clients/port-client.ts +105 -0
  37. package/src/clients/process-client.ts +177 -0
  38. package/src/clients/sandbox-client.ts +41 -0
  39. package/src/clients/types.ts +84 -0
  40. package/src/clients/utility-client.ts +94 -0
  41. package/src/errors/adapter.ts +180 -0
  42. package/src/errors/classes.ts +469 -0
  43. package/src/errors/index.ts +105 -0
  44. package/src/file-stream.ts +119 -117
  45. package/src/index.ts +81 -69
  46. package/src/interpreter.ts +17 -8
  47. package/src/request-handler.ts +69 -43
  48. package/src/sandbox.ts +694 -533
  49. package/src/security.ts +14 -23
  50. package/src/sse-parser.ts +4 -8
  51. package/startup.sh +3 -0
  52. package/tests/base-client.test.ts +328 -0
  53. package/tests/command-client.test.ts +407 -0
  54. package/tests/file-client.test.ts +643 -0
  55. package/tests/file-stream.test.ts +306 -0
  56. package/tests/git-client.test.ts +328 -0
  57. package/tests/port-client.test.ts +301 -0
  58. package/tests/process-client.test.ts +658 -0
  59. package/tests/sandbox.test.ts +465 -0
  60. package/tests/sse-parser.test.ts +290 -0
  61. package/tests/utility-client.test.ts +266 -0
  62. package/tests/wrangler.jsonc +35 -0
  63. package/tsconfig.json +9 -1
  64. package/vitest.config.ts +31 -0
  65. package/container_src/bun.lock +0 -76
  66. package/container_src/circuit-breaker.ts +0 -121
  67. package/container_src/control-process.ts +0 -784
  68. package/container_src/handler/exec.ts +0 -185
  69. package/container_src/handler/file.ts +0 -457
  70. package/container_src/handler/git.ts +0 -130
  71. package/container_src/handler/ports.ts +0 -314
  72. package/container_src/handler/process.ts +0 -568
  73. package/container_src/handler/session.ts +0 -92
  74. package/container_src/index.ts +0 -601
  75. package/container_src/interpreter-service.ts +0 -276
  76. package/container_src/isolation.ts +0 -1213
  77. package/container_src/mime-processor.ts +0 -255
  78. package/container_src/package.json +0 -18
  79. package/container_src/runtime/executors/javascript/node_executor.ts +0 -123
  80. package/container_src/runtime/executors/python/ipython_executor.py +0 -338
  81. package/container_src/runtime/executors/typescript/ts_executor.ts +0 -138
  82. package/container_src/runtime/process-pool.ts +0 -464
  83. package/container_src/shell-escape.ts +0 -42
  84. package/container_src/startup.sh +0 -11
  85. package/container_src/types.ts +0 -131
  86. package/dist/chunk-32UDXUPC.js +0 -671
  87. package/dist/chunk-32UDXUPC.js.map +0 -1
  88. package/dist/chunk-5DILEXGY.js +0 -85
  89. package/dist/chunk-5DILEXGY.js.map +0 -1
  90. package/dist/chunk-D3U63BZP.js +0 -240
  91. package/dist/chunk-D3U63BZP.js.map +0 -1
  92. package/dist/chunk-FXYPFGOZ.js +0 -129
  93. package/dist/chunk-FXYPFGOZ.js.map +0 -1
  94. package/dist/chunk-JTKON2SH.js +0 -113
  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
  }