@cloudflare/sandbox 0.7.1 → 0.7.3

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/Dockerfile CHANGED
@@ -201,3 +201,33 @@ ENV TYPESCRIPT_POOL_MIN_SIZE=3
201
201
  EXPOSE 4096
202
202
 
203
203
  ENTRYPOINT ["/container-server/sandbox"]
204
+
205
+ # ============================================================================
206
+ # Stage 5d: Musl image - Alpine-based with musl-linked binary
207
+ # ============================================================================
208
+ FROM alpine:3.21 AS musl
209
+
210
+ ARG SANDBOX_VERSION=unknown
211
+
212
+ ENV SANDBOX_VERSION=${SANDBOX_VERSION}
213
+
214
+ RUN apk add --no-cache bash file git ca-certificates curl libstdc++ libgcc s3fs-fuse fuse
215
+
216
+ RUN sed -i 's/#user_allow_other/user_allow_other/' /etc/fuse.conf
217
+
218
+ COPY --from=builder /app/packages/sandbox-container/dist/sandbox-musl /container-server/sandbox
219
+
220
+ WORKDIR /container-server
221
+
222
+ COPY --from=builder /app/packages/sandbox-container/dist/runtime/executors/javascript/node_executor.js ./dist/runtime/executors/javascript/
223
+ COPY --from=builder /app/packages/sandbox-container/dist/index.js ./dist/
224
+
225
+ RUN mkdir -p /workspace
226
+
227
+ EXPOSE 3000
228
+
229
+ ENV PYTHON_POOL_MIN_SIZE=0
230
+ ENV JAVASCRIPT_POOL_MIN_SIZE=0
231
+ ENV TYPESCRIPT_POOL_MIN_SIZE=0
232
+
233
+ ENTRYPOINT ["/container-server/sandbox"]
package/dist/index.js CHANGED
@@ -2089,13 +2089,16 @@ var SecurityError = class extends Error {
2089
2089
  }
2090
2090
  };
2091
2091
  /**
2092
- * Validates port numbers for sandbox services
2093
- * Only allows non-system ports to prevent conflicts and security issues
2092
+ * Validates port numbers for sandbox services.
2093
+ *
2094
+ * Rules:
2095
+ * - Range: 1024-65535 (privileged ports require root, which containers don't have)
2096
+ * - Reserved: 3000 (sandbox control plane)
2094
2097
  */
2095
2098
  function validatePort(port) {
2096
2099
  if (!Number.isInteger(port)) return false;
2097
2100
  if (port < 1024 || port > 65535) return false;
2098
- if ([3e3, 8787].includes(port)) return false;
2101
+ if ([3e3].includes(port)) return false;
2099
2102
  return true;
2100
2103
  }
2101
2104
  /**
@@ -2564,7 +2567,7 @@ function buildS3fsSource(bucket, prefix) {
2564
2567
  * This file is auto-updated by .github/changeset-version.ts during releases
2565
2568
  * DO NOT EDIT MANUALLY - Changes will be overwritten on the next version bump
2566
2569
  */
2567
- const SDK_VERSION = "0.7.1";
2570
+ const SDK_VERSION = "0.7.3";
2568
2571
 
2569
2572
  //#endregion
2570
2573
  //#region src/sandbox.ts
@@ -2581,6 +2584,7 @@ function getSandbox(ns, id, options) {
2581
2584
  if (options?.containerTimeouts) stub.setContainerTimeouts(options.containerTimeouts);
2582
2585
  const defaultSessionId = `sandbox-${effectiveId}`;
2583
2586
  const enhancedMethods = {
2587
+ fetch: (request) => stub.fetch(request),
2584
2588
  createSession: async (opts) => {
2585
2589
  return enhanceSession(stub, await stub.createSession(opts));
2586
2590
  },
@@ -2603,7 +2607,7 @@ function enhanceSession(stub, rpcSession) {
2603
2607
  }
2604
2608
  function connect(stub) {
2605
2609
  return async (request, port) => {
2606
- if (!validatePort(port)) throw new SecurityError(`Invalid or restricted port: ${port}. Ports must be in range 1024-65535 and not reserved.`);
2610
+ if (!validatePort(port)) throw new SecurityError(`Invalid port number: ${port}. Must be 1024-65535, excluding 3000 (sandbox control plane).`);
2607
2611
  const portSwitchedRequest = switchPort(request, port);
2608
2612
  return await stub.fetch(portSwitchedRequest);
2609
2613
  };
@@ -3668,6 +3672,7 @@ var Sandbox = class extends Container {
3668
3672
  * // url: https://8080-sandbox-id-my-token-v1.example.com
3669
3673
  */
3670
3674
  async exposePort(port, options) {
3675
+ if (!validatePort(port)) throw new SecurityError(`Invalid port number: ${port}. Must be 1024-65535, excluding 3000 (sandbox control plane).`);
3671
3676
  if (options.hostname.endsWith(".workers.dev")) throw new CustomDomainRequiredError({
3672
3677
  code: ErrorCode.CUSTOM_DOMAIN_REQUIRED,
3673
3678
  message: `Port exposure requires a custom domain. .workers.dev domains do not support wildcard subdomains required for port proxying.`,
@@ -3695,7 +3700,7 @@ var Sandbox = class extends Container {
3695
3700
  };
3696
3701
  }
3697
3702
  async unexposePort(port) {
3698
- if (!validatePort(port)) throw new SecurityError(`Invalid port number: ${port}. Must be between 1024-65535 and not reserved.`);
3703
+ if (!validatePort(port)) throw new SecurityError(`Invalid port number: ${port}. Must be 1024-65535, excluding 3000 (sandbox control plane).`);
3699
3704
  const sessionId = await this.ensureDefaultSession();
3700
3705
  await this.client.ports.unexposePort(port, sessionId);
3701
3706
  const tokens = await this.ctx.storage.get("portTokens") || {};
@@ -3735,11 +3740,14 @@ var Sandbox = class extends Container {
3735
3740
  this.logger.error("Port is exposed but has no token - bug detected", void 0, { port });
3736
3741
  return false;
3737
3742
  }
3738
- if (storedToken.length !== token.length) return false;
3739
3743
  const encoder = new TextEncoder();
3740
3744
  const a = encoder.encode(storedToken);
3741
3745
  const b = encoder.encode(token);
3742
- return crypto.subtle.timingSafeEqual(a, b);
3746
+ try {
3747
+ return crypto.subtle.timingSafeEqual(a, b);
3748
+ } catch {
3749
+ return false;
3750
+ }
3743
3751
  }
3744
3752
  validateCustomToken(token) {
3745
3753
  if (token.length === 0) throw new SecurityError(`Custom token cannot be empty.`);
@@ -3752,7 +3760,7 @@ var Sandbox = class extends Container {
3752
3760
  return btoa(String.fromCharCode(...array)).replace(/\+/g, "_").replace(/\//g, "_").replace(/=/g, "").toLowerCase();
3753
3761
  }
3754
3762
  constructPreviewUrl(port, sandboxId, hostname, token) {
3755
- if (!validatePort(port)) throw new SecurityError(`Invalid port number: ${port}. Must be between 1024-65535 and not reserved.`);
3763
+ if (!validatePort(port)) throw new SecurityError(`Invalid port number: ${port}. Must be 1024-65535, excluding 3000 (sandbox control plane).`);
3756
3764
  const effectiveId = this.sandboxName || sandboxId;
3757
3765
  const hasUppercase = /[A-Z]/.test(effectiveId);
3758
3766
  if (!this.normalizeId && hasUppercase) throw new SecurityError(`Preview URLs require lowercase sandbox IDs. Your ID "${effectiveId}" contains uppercase letters.\n\nTo fix this:\n1. Create a new sandbox with: getSandbox(ns, "${effectiveId}", { normalizeId: true })\n2. This will create a sandbox with ID: "${effectiveId.toLowerCase()}"\n\nNote: Due to DNS case-insensitivity, IDs with uppercase letters cannot be used with preview URLs.`);