@cloudflare/sandbox 0.7.0 → 0.7.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.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { _ as getEnvString, a as isExecResult, c as shellEscape, d as TraceContext, f as Execution, g as filterEnvVars, h as extractRepoName, i as isWSStreamChunk, l as createLogger, m as GitLogger, n as isWSError, o as isProcess, p as ResultImpl, r as isWSResponse, s as isProcessStatus, t as generateRequestId, u as createNoOpLogger, v as partitionEnvVars } from "./dist-BpbdH8ry.js";
2
- import { t as ErrorCode } from "./errors-BCXUmJUn.js";
1
+ import { _ as getEnvString, a as isExecResult, c as shellEscape, d as TraceContext, f as Execution, g as filterEnvVars, h as extractRepoName, i as isWSStreamChunk, l as createLogger, m as GitLogger, n as isWSError, o as isProcess, p as ResultImpl, r as isWSResponse, s as isProcessStatus, t as generateRequestId, u as createNoOpLogger, v as partitionEnvVars } from "./dist-D9B_6gn_.js";
2
+ import { t as ErrorCode } from "./errors-Bzl0ZNia.js";
3
3
  import { Container, getContainer, switchPort } from "@cloudflare/containers";
4
4
 
5
5
  //#region src/errors/classes.ts
@@ -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
  /**
@@ -2216,6 +2219,19 @@ var CodeInterpreter = class {
2216
2219
  }
2217
2220
  };
2218
2221
 
2222
+ //#endregion
2223
+ //#region src/pty/proxy.ts
2224
+ async function proxyTerminal(stub, sessionId, request, options) {
2225
+ if (!sessionId || typeof sessionId !== "string") throw new Error("sessionId is required for terminal access");
2226
+ if (request.headers.get("Upgrade")?.toLowerCase() !== "websocket") throw new Error("terminal() requires a WebSocket upgrade request");
2227
+ const params = new URLSearchParams({ sessionId });
2228
+ if (options?.cols) params.set("cols", String(options.cols));
2229
+ if (options?.rows) params.set("rows", String(options.rows));
2230
+ const ptyUrl = `http://localhost/ws/pty?${params}`;
2231
+ const ptyRequest = new Request(ptyUrl, request);
2232
+ return stub.fetch(switchPort(ptyRequest, 3e3));
2233
+ }
2234
+
2219
2235
  //#endregion
2220
2236
  //#region src/request-handler.ts
2221
2237
  async function proxyToSandbox(request, env) {
@@ -2254,15 +2270,18 @@ async function proxyToSandbox(request, env) {
2254
2270
  let proxyUrl;
2255
2271
  if (port !== 3e3) proxyUrl = `http://localhost:${port}${path}${url.search}`;
2256
2272
  else proxyUrl = `http://localhost:3000${path}${url.search}`;
2273
+ const headers = {
2274
+ "X-Original-URL": request.url,
2275
+ "X-Forwarded-Host": url.hostname,
2276
+ "X-Forwarded-Proto": url.protocol.replace(":", ""),
2277
+ "X-Sandbox-Name": sandboxId
2278
+ };
2279
+ request.headers.forEach((value, key) => {
2280
+ headers[key] = value;
2281
+ });
2257
2282
  const proxyRequest = new Request(proxyUrl, {
2258
2283
  method: request.method,
2259
- headers: {
2260
- ...Object.fromEntries(request.headers),
2261
- "X-Original-URL": request.url,
2262
- "X-Forwarded-Host": url.hostname,
2263
- "X-Forwarded-Proto": url.protocol.replace(":", ""),
2264
- "X-Sandbox-Name": sandboxId
2265
- },
2284
+ headers,
2266
2285
  body: request.body,
2267
2286
  duplex: "half"
2268
2287
  });
@@ -2548,7 +2567,7 @@ function buildS3fsSource(bucket, prefix) {
2548
2567
  * This file is auto-updated by .github/changeset-version.ts during releases
2549
2568
  * DO NOT EDIT MANUALLY - Changes will be overwritten on the next version bump
2550
2569
  */
2551
- const SDK_VERSION = "0.7.0";
2570
+ const SDK_VERSION = "0.7.2";
2552
2571
 
2553
2572
  //#endregion
2554
2573
  //#region src/sandbox.ts
@@ -2563,11 +2582,31 @@ function getSandbox(ns, id, options) {
2563
2582
  if (options?.sleepAfter !== void 0) stub.setSleepAfter(options.sleepAfter);
2564
2583
  if (options?.keepAlive !== void 0) stub.setKeepAlive(options.keepAlive);
2565
2584
  if (options?.containerTimeouts) stub.setContainerTimeouts(options.containerTimeouts);
2566
- return Object.assign(stub, { wsConnect: connect(stub) });
2585
+ const defaultSessionId = `sandbox-${effectiveId}`;
2586
+ const enhancedMethods = {
2587
+ createSession: async (opts) => {
2588
+ return enhanceSession(stub, await stub.createSession(opts));
2589
+ },
2590
+ getSession: async (sessionId) => {
2591
+ return enhanceSession(stub, await stub.getSession(sessionId));
2592
+ },
2593
+ terminal: (request, opts) => proxyTerminal(stub, defaultSessionId, request, opts),
2594
+ wsConnect: connect(stub)
2595
+ };
2596
+ return new Proxy(stub, { get(target, prop) {
2597
+ if (typeof prop === "string" && prop in enhancedMethods) return enhancedMethods[prop];
2598
+ return target[prop];
2599
+ } });
2600
+ }
2601
+ function enhanceSession(stub, rpcSession) {
2602
+ return {
2603
+ ...rpcSession,
2604
+ terminal: (request, opts) => proxyTerminal(stub, rpcSession.id, request, opts)
2605
+ };
2567
2606
  }
2568
2607
  function connect(stub) {
2569
2608
  return async (request, port) => {
2570
- if (!validatePort(port)) throw new SecurityError(`Invalid or restricted port: ${port}. Ports must be in range 1024-65535 and not reserved.`);
2609
+ if (!validatePort(port)) throw new SecurityError(`Invalid port number: ${port}. Must be 1024-65535, excluding 3000 (sandbox control plane).`);
2571
2610
  const portSwitchedRequest = switchPort(request, port);
2572
2611
  return await stub.fetch(portSwitchedRequest);
2573
2612
  };
@@ -3632,6 +3671,7 @@ var Sandbox = class extends Container {
3632
3671
  * // url: https://8080-sandbox-id-my-token-v1.example.com
3633
3672
  */
3634
3673
  async exposePort(port, options) {
3674
+ if (!validatePort(port)) throw new SecurityError(`Invalid port number: ${port}. Must be 1024-65535, excluding 3000 (sandbox control plane).`);
3635
3675
  if (options.hostname.endsWith(".workers.dev")) throw new CustomDomainRequiredError({
3636
3676
  code: ErrorCode.CUSTOM_DOMAIN_REQUIRED,
3637
3677
  message: `Port exposure requires a custom domain. .workers.dev domains do not support wildcard subdomains required for port proxying.`,
@@ -3659,7 +3699,7 @@ var Sandbox = class extends Container {
3659
3699
  };
3660
3700
  }
3661
3701
  async unexposePort(port) {
3662
- if (!validatePort(port)) throw new SecurityError(`Invalid port number: ${port}. Must be between 1024-65535 and not reserved.`);
3702
+ if (!validatePort(port)) throw new SecurityError(`Invalid port number: ${port}. Must be 1024-65535, excluding 3000 (sandbox control plane).`);
3663
3703
  const sessionId = await this.ensureDefaultSession();
3664
3704
  await this.client.ports.unexposePort(port, sessionId);
3665
3705
  const tokens = await this.ctx.storage.get("portTokens") || {};
@@ -3699,11 +3739,14 @@ var Sandbox = class extends Container {
3699
3739
  this.logger.error("Port is exposed but has no token - bug detected", void 0, { port });
3700
3740
  return false;
3701
3741
  }
3702
- if (storedToken.length !== token.length) return false;
3703
3742
  const encoder = new TextEncoder();
3704
3743
  const a = encoder.encode(storedToken);
3705
3744
  const b = encoder.encode(token);
3706
- return crypto.subtle.timingSafeEqual(a, b);
3745
+ try {
3746
+ return crypto.subtle.timingSafeEqual(a, b);
3747
+ } catch {
3748
+ return false;
3749
+ }
3707
3750
  }
3708
3751
  validateCustomToken(token) {
3709
3752
  if (token.length === 0) throw new SecurityError(`Custom token cannot be empty.`);
@@ -3716,7 +3759,7 @@ var Sandbox = class extends Container {
3716
3759
  return btoa(String.fromCharCode(...array)).replace(/\+/g, "_").replace(/\//g, "_").replace(/=/g, "").toLowerCase();
3717
3760
  }
3718
3761
  constructPreviewUrl(port, sandboxId, hostname, token) {
3719
- if (!validatePort(port)) throw new SecurityError(`Invalid port number: ${port}. Must be between 1024-65535 and not reserved.`);
3762
+ if (!validatePort(port)) throw new SecurityError(`Invalid port number: ${port}. Must be 1024-65535, excluding 3000 (sandbox control plane).`);
3720
3763
  const effectiveId = this.sandboxName || sandboxId;
3721
3764
  const hasUppercase = /[A-Z]/.test(effectiveId);
3722
3765
  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.`);
@@ -3790,13 +3833,10 @@ var Sandbox = class extends Container {
3790
3833
  timestamp: response.timestamp
3791
3834
  };
3792
3835
  }
3793
- /**
3794
- * Internal helper to create ExecutionSession wrapper for a given sessionId
3795
- * Used by both createSession and getSession
3796
- */
3797
3836
  getSessionWrapper(sessionId) {
3798
3837
  return {
3799
3838
  id: sessionId,
3839
+ terminal: null,
3800
3840
  exec: (command, options) => this.execWithSession(command, sessionId, options),
3801
3841
  execStream: (command, options) => this.execStreamWithSession(command, sessionId, options),
3802
3842
  startProcess: (command, options) => this.startProcess(command, options, sessionId),
@@ -3995,5 +4035,5 @@ async function collectFile(stream) {
3995
4035
  }
3996
4036
 
3997
4037
  //#endregion
3998
- export { BucketMountError, CodeInterpreter, CommandClient, FileClient, GitClient, InvalidMountConfigError, MissingCredentialsError, PortClient, ProcessClient, ProcessExitedBeforeReadyError, ProcessReadyTimeoutError, S3FSMountError, Sandbox, SandboxClient, UtilityClient, asyncIterableToSSEStream, collectFile, getSandbox, isExecResult, isProcess, isProcessStatus, parseSSEStream, proxyToSandbox, responseToAsyncIterable, streamFile };
4038
+ export { BucketMountError, CodeInterpreter, CommandClient, FileClient, GitClient, InvalidMountConfigError, MissingCredentialsError, PortClient, ProcessClient, ProcessExitedBeforeReadyError, ProcessReadyTimeoutError, S3FSMountError, Sandbox, SandboxClient, UtilityClient, asyncIterableToSSEStream, collectFile, getSandbox, isExecResult, isProcess, isProcessStatus, parseSSEStream, proxyTerminal, proxyToSandbox, responseToAsyncIterable, streamFile };
3999
4039
  //# sourceMappingURL=index.js.map