sandboxedjs 0.1.11 → 0.1.13

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/README.md CHANGED
@@ -620,6 +620,20 @@ Two ways in, both byte-exact:
620
620
  await box.fs.writeFile("/workspace/uploads/clip.mp4", bytes); // creates missing directories
621
621
  ```
622
622
 
623
+ The `File` returned by a browser picker can be passed directly too. It is read
624
+ as bytes and written at the exact container path, including an application-
625
+ generated temporary path:
626
+
627
+ ```js
628
+ const picked = input.files[0];
629
+ const sourcePath = "/tmp/transcribe-quran-52fjch/source-1786307713702-baleela.f32le";
630
+ await box.fs.writeFile(sourcePath, picked);
631
+ ```
632
+
633
+ Do not pass `picked.path` (or any other host path) to a command in the
634
+ container. Browser file pickers provide file data, not a path the sandbox can
635
+ see.
636
+
623
637
  ```js
624
638
  // 2. Through a server running inside the container — a picker in the previewed
625
639
  // app posting to its own backend. Publish the port and upload normally.
package/dist/index.cjs CHANGED
@@ -23480,6 +23480,14 @@ function installUserland(kernel) {
23480
23480
 
23481
23481
  // src/container/fs.ts
23482
23482
  init_path();
23483
+ async function bytesFor(data) {
23484
+ if (data instanceof Uint8Array) return data;
23485
+ if (data instanceof ArrayBuffer) return new Uint8Array(data);
23486
+ if (ArrayBuffer.isView(data)) {
23487
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
23488
+ }
23489
+ return new Uint8Array(await data.arrayBuffer());
23490
+ }
23483
23491
  var ContainerFs = class {
23484
23492
  constructor(kernel) {
23485
23493
  this.kernel = kernel;
@@ -23496,10 +23504,12 @@ var ContainerFs = class {
23496
23504
  const abs = clean(path);
23497
23505
  const parent = dirname(abs);
23498
23506
  if (!this.vfs.lexists(parent)) this.vfs.mkdir(parent, { recursive: true, mode: 493 });
23499
- this.vfs.writeFile(abs, data, { privileged: true, mode: opts.mode ?? 420 });
23507
+ const content = typeof data === "string" ? data : await bytesFor(data);
23508
+ this.vfs.writeFile(abs, content, { privileged: true, mode: opts.mode ?? 420 });
23500
23509
  }
23501
23510
  async appendFile(path, data) {
23502
- this.vfs.appendFile(clean(path), data, { privileged: true });
23511
+ const content = typeof data === "string" ? data : await bytesFor(data);
23512
+ this.vfs.appendFile(clean(path), content, { privileged: true });
23503
23513
  }
23504
23514
  async readdir(path, opts) {
23505
23515
  if (opts?.withFileTypes) return this.vfs.readdirWithTypes(clean(path));
@@ -23665,6 +23675,146 @@ var Session = class {
23665
23675
  }
23666
23676
  };
23667
23677
 
23678
+ // src/runtime/node-child-process-bridge.ts
23679
+ var KernelChildProcess = class {
23680
+ pid;
23681
+ command;
23682
+ args;
23683
+ parentPid;
23684
+ state = "running";
23685
+ exitCode;
23686
+ stdout = "";
23687
+ stderr = "";
23688
+ listeners = /* @__PURE__ */ new Map();
23689
+ kernel;
23690
+ cred;
23691
+ cwd;
23692
+ env;
23693
+ stdin = new Pipe();
23694
+ process;
23695
+ started = false;
23696
+ cancelled = false;
23697
+ constructor(kernel, cred, config, pid) {
23698
+ this.kernel = kernel;
23699
+ this.cred = cred;
23700
+ this.pid = pid;
23701
+ this.command = config.command;
23702
+ this.args = config.args?.slice() ?? [];
23703
+ this.parentPid = config.parentPid;
23704
+ this.cwd = config.cwd ?? "/";
23705
+ this.env = { ...config.env };
23706
+ }
23707
+ on(event, listener) {
23708
+ let listeners = this.listeners.get(event);
23709
+ if (!listeners) {
23710
+ listeners = /* @__PURE__ */ new Set();
23711
+ this.listeners.set(event, listeners);
23712
+ }
23713
+ listeners.add(listener);
23714
+ return this;
23715
+ }
23716
+ emit(event, ...args) {
23717
+ for (const listener of this.listeners.get(event) ?? []) listener(...args);
23718
+ }
23719
+ exec() {
23720
+ if (this.started) return;
23721
+ this.started = true;
23722
+ queueMicrotask(() => {
23723
+ if (this.cancelled) {
23724
+ this.finish(143);
23725
+ return;
23726
+ }
23727
+ const stdout = new ChildOutput(this, "stdout");
23728
+ const stderr = new ChildOutput(this, "stderr");
23729
+ try {
23730
+ this.process = this.kernel.spawn([this.command, ...this.args], {
23731
+ cwd: this.cwd,
23732
+ env: this.env,
23733
+ cred: this.cred,
23734
+ stdin: this.stdin,
23735
+ stdout,
23736
+ stderr,
23737
+ ppid: 1
23738
+ });
23739
+ void this.process.wait().then((code) => {
23740
+ stdout.end();
23741
+ stderr.end();
23742
+ this.finish(code);
23743
+ });
23744
+ } catch (error) {
23745
+ stderr.write(error instanceof Error ? error.message : String(error));
23746
+ stderr.end();
23747
+ stdout.end();
23748
+ this.finish(1);
23749
+ }
23750
+ });
23751
+ }
23752
+ sendStdin(data) {
23753
+ try {
23754
+ this.stdin.write(data);
23755
+ } catch {
23756
+ }
23757
+ }
23758
+ kill(signal = "SIGTERM") {
23759
+ if (this.process) {
23760
+ this.process.deliver(signal);
23761
+ return;
23762
+ }
23763
+ this.cancelled = true;
23764
+ }
23765
+ finish(code) {
23766
+ if (this.state === "exited") return;
23767
+ this.state = "exited";
23768
+ this.exitCode = code;
23769
+ this.emit("exit", code);
23770
+ }
23771
+ append(stream, text) {
23772
+ this[stream] += text;
23773
+ this.emit(stream, text);
23774
+ }
23775
+ };
23776
+ var ChildOutput = class {
23777
+ constructor(child, stream) {
23778
+ this.child = child;
23779
+ this.stream = stream;
23780
+ }
23781
+ child;
23782
+ stream;
23783
+ isTTY = false;
23784
+ closedState = false;
23785
+ decoder = new TextDecoder();
23786
+ get closed() {
23787
+ return this.closedState;
23788
+ }
23789
+ write(data) {
23790
+ if (this.closedState) return;
23791
+ const text = typeof data === "string" ? data : this.decoder.decode(data, { stream: true });
23792
+ if (text) this.child.append(this.stream, text);
23793
+ }
23794
+ end() {
23795
+ if (this.closedState) return;
23796
+ this.closedState = true;
23797
+ const tail2 = this.decoder.decode();
23798
+ if (tail2) this.child.append(this.stream, tail2);
23799
+ }
23800
+ };
23801
+ var nextBridgePid = 1073741824;
23802
+ function installNodeChildProcessBridge(pod, kernel, cred) {
23803
+ const manager = pod.processManager;
23804
+ const originalSpawn = manager.spawn;
23805
+ manager.spawn = (config) => {
23806
+ const resolved = kernel.resolveExecutable(config.command, config.cwd ?? "/", config.env ?? {}, cred);
23807
+ if (resolved?.kind === "builtin" && resolved.command?.name !== "node" && resolved.command?.name !== "nodejs") {
23808
+ return new KernelChildProcess(kernel, cred, config, nextBridgePid++);
23809
+ }
23810
+ return originalSpawn.call(manager, config);
23811
+ };
23812
+ return () => {
23813
+ if (manager.spawn === originalSpawn) return;
23814
+ manager.spawn = originalSpawn;
23815
+ };
23816
+ }
23817
+
23668
23818
  // src/container/container.ts
23669
23819
  var Container = class _Container {
23670
23820
  kernel;
@@ -23675,6 +23825,7 @@ var Container = class _Container {
23675
23825
  hooks;
23676
23826
  disposed = false;
23677
23827
  defaultSession = null;
23828
+ restoreNodeChildProcessBridge;
23678
23829
  constructor(kernel, pod, opts) {
23679
23830
  this.kernel = kernel;
23680
23831
  this.pod = pod;
@@ -23703,6 +23854,7 @@ var Container = class _Container {
23703
23854
  };
23704
23855
  if (opts.timeoutMs !== void 0) this.defaults.timeoutMs = opts.timeoutMs;
23705
23856
  this.hooks = { onStdout: opts.onStdout, onStderr: opts.onStderr };
23857
+ this.restoreNodeChildProcessBridge = installNodeChildProcessBridge(pod, kernel, cred);
23706
23858
  }
23707
23859
  // ── boot ──────────────────────────────────────────────────────────────────
23708
23860
  static async create(opts = {}) {
@@ -24113,6 +24265,7 @@ var Container = class _Container {
24113
24265
  dispose() {
24114
24266
  if (this.disposed) return;
24115
24267
  this.disposed = true;
24268
+ this.restoreNodeChildProcessBridge();
24116
24269
  this.kernel.dispose();
24117
24270
  try {
24118
24271
  this.pod.teardown();