sandboxedjs 0.1.67 → 0.1.69

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.cjs CHANGED
@@ -506,6 +506,9 @@ function normalizeSignal(spec) {
506
506
  const withPrefix = upper.startsWith("SIG") ? upper : "SIG" + upper;
507
507
  return withPrefix in exports.SIGNALS ? withPrefix : null;
508
508
  }
509
+ function signalNumber(name) {
510
+ return exports.SIGNALS[name] ?? 0;
511
+ }
509
512
  function exitCodeForSignal(sig) {
510
513
  return 128 + (exports.SIGNALS[sig] ?? 0);
511
514
  }
@@ -17817,7 +17820,10 @@ var Op = {
17817
17820
  getpeername: 1801,
17818
17821
  setsockopt: 1802,
17819
17822
  getsockopt: 1803,
17820
- socket_close: 1804
17823
+ socket_close: 1804,
17824
+ spawn: 2048,
17825
+ waitpid: 2049,
17826
+ kill: 2050
17821
17827
  };
17822
17828
  var Errno = {
17823
17829
  EPERM: 1,
@@ -17828,6 +17834,7 @@ var Errno = {
17828
17834
  ENXIO: 6,
17829
17835
  E2BIG: 7,
17830
17836
  EBADF: 9,
17837
+ ECHILD: 10,
17831
17838
  EAGAIN: 11,
17832
17839
  ENOMEM: 12,
17833
17840
  EACCES: 13,
@@ -17963,7 +17970,7 @@ var wheels_default = {
17963
17970
 
17964
17971
  // package.json
17965
17972
  var package_default = {
17966
- version: "0.1.67"};
17973
+ version: "0.1.69"};
17967
17974
 
17968
17975
  // src/runtime/python/config.ts
17969
17976
  var bundledManifest = {
@@ -18508,6 +18515,9 @@ function toPosixError(error) {
18508
18515
  };
18509
18516
  return new PosixError(code && mapped[code] ? mapped[code] : Errno.EIO, message);
18510
18517
  }
18518
+
18519
+ // src/runtime/python/backend.ts
18520
+ init_signals();
18511
18521
  var DescriptorTable = class _DescriptorTable {
18512
18522
  slots = /* @__PURE__ */ new Map();
18513
18523
  /** Descriptions this table shares with others, so close counts correctly. */
@@ -19401,6 +19411,46 @@ function createHostAbiServer(proc) {
19401
19411
  const joined = Object.entries(proc.env).map(([key, value]) => `${key}=${value}`).join("\0");
19402
19412
  return { status: 0, payload: new Writer().string(joined).finish() };
19403
19413
  }
19414
+ /* --------------------------------------------------------- processes */
19415
+ case Op.spawn: {
19416
+ const argv = [];
19417
+ for (let i = 0, n = r.u32(); i < n; i++) argv.push(r.string());
19418
+ const env2 = {};
19419
+ for (let i = 0, n = r.u32(); i < n; i++) {
19420
+ const entry = r.string();
19421
+ const split2 = entry.indexOf("=");
19422
+ if (split2 > 0) env2[entry.slice(0, split2)] = entry.slice(split2 + 1);
19423
+ }
19424
+ const cwd = r.string() || proc.cwd;
19425
+ const files2 = /* @__PURE__ */ new Map();
19426
+ for (let i = 0, n = r.u32(); i < n; i++) {
19427
+ const childFd = r.u32() | 0;
19428
+ const parentFd = r.u32() | 0;
19429
+ files2.set(childFd, proc.table.get(parentFd));
19430
+ }
19431
+ if (!proc.processes) throw new PosixError(Errno.ENOSYS);
19432
+ if (argv.length === 0) throw new PosixError(Errno.EINVAL);
19433
+ return { status: await proc.processes.spawn({ argv, env: env2, cwd: resolve3(cwd), files: files2 }) };
19434
+ }
19435
+ case Op.waitpid: {
19436
+ const pid = r.u32() | 0;
19437
+ const options = r.u32() | 0;
19438
+ if (!proc.processes) throw new PosixError(Errno.ENOSYS);
19439
+ const result = await Promise.race([
19440
+ proc.processes.wait(pid, options),
19441
+ aborted().then(() => {
19442
+ throw new PosixError(Errno.EINTR);
19443
+ })
19444
+ ]);
19445
+ return { status: 0, payload: new Writer().i32(result.pid).i32(result.status).finish() };
19446
+ }
19447
+ case Op.kill: {
19448
+ const pid = r.u32() | 0;
19449
+ const signal = r.u32() | 0;
19450
+ if (!proc.processes) throw new PosixError(Errno.ENOSYS);
19451
+ proc.processes.kill(pid, signal);
19452
+ return { status: 0 };
19453
+ }
19404
19454
  /* ---------------------------------------------------------- entropy */
19405
19455
  case Op.getrandom: {
19406
19456
  const length = Math.min(r.u32(), 65536);
@@ -19754,6 +19804,7 @@ async function startPythonProcess(options) {
19754
19804
  table.set(0, stdin);
19755
19805
  table.set(1, stdout);
19756
19806
  table.set(2, stderr);
19807
+ for (const [fd, description] of options.inherit ?? []) table.set(fd, description);
19757
19808
  const controller = new AbortController();
19758
19809
  const abort = () => controller.abort();
19759
19810
  options.signal.addEventListener("abort", abort, { once: true });
@@ -19767,7 +19818,8 @@ async function startPythonProcess(options) {
19767
19818
  env: options.env,
19768
19819
  cwd: options.cwd,
19769
19820
  signal: controller.signal,
19770
- sockets: options.sockets
19821
+ sockets: options.sockets,
19822
+ processes: options.processes
19771
19823
  });
19772
19824
  const buffers = createHostTransportBuffers();
19773
19825
  let fault = null;
@@ -19832,7 +19884,11 @@ async function startPythonProcess(options) {
19832
19884
  pid: options.pid,
19833
19885
  ppid: options.ppid,
19834
19886
  mounts: options.mounts,
19835
- isTty: options.isTty
19887
+ isTty: options.isTty,
19888
+ /* Only beyond the standard three: 0, 1 and 2 are bound as the process's
19889
+ * terminal-or-pipe streams by the worker itself, and installing a second
19890
+ * stream over one of them would leave two objects claiming the number. */
19891
+ inheritFds: [...options.inherit?.keys() ?? []].filter((fd) => fd > 2)
19836
19892
  });
19837
19893
  return {
19838
19894
  write: (data) => stdin.push(data),
@@ -20320,6 +20376,129 @@ raise SystemExit(entry())
20320
20376
  }
20321
20377
 
20322
20378
  // src/runtime/python/backend.ts
20379
+ var PENDING_INHERITANCE = /* @__PURE__ */ new Map();
20380
+ var INHERIT_TOKEN = "SBX_PYTHON_INHERIT";
20381
+ var WNOHANG = 1;
20382
+ function claimInheritance(env2) {
20383
+ const token = env2[INHERIT_TOKEN];
20384
+ if (!token) return void 0;
20385
+ const files = PENDING_INHERITANCE.get(token);
20386
+ PENDING_INHERITANCE.delete(token);
20387
+ delete env2[INHERIT_TOKEN];
20388
+ return files;
20389
+ }
20390
+ function waitStatus(proc) {
20391
+ if (proc.termSignal) return signalNumber(proc.termSignal) & 127;
20392
+ return ((proc.exitCode ?? 0) & 255) << 8;
20393
+ }
20394
+ function outputFor(description, fallback) {
20395
+ if (!description) return fallback;
20396
+ return {
20397
+ write(data) {
20398
+ description.write(typeof data === "string" ? new TextEncoder().encode(data) : data);
20399
+ },
20400
+ end() {
20401
+ },
20402
+ get closed() {
20403
+ return false;
20404
+ },
20405
+ isTTY: fallback.isTTY
20406
+ };
20407
+ }
20408
+ function createProcessService(ctx) {
20409
+ const children = /* @__PURE__ */ new Map();
20410
+ const exited = (proc) => proc.exitCode !== null || proc.termSignal !== null;
20411
+ return {
20412
+ async spawn(request) {
20413
+ const token = `${ctx.proc.pid}:${Math.random().toString(36).slice(2)}`;
20414
+ const env2 = { ...request.env };
20415
+ if (request.files.size > 0) {
20416
+ PENDING_INHERITANCE.set(token, new Map(request.files));
20417
+ env2[INHERIT_TOKEN] = token;
20418
+ }
20419
+ const stdin = request.files.get(0);
20420
+ const argv = [containerProgram(ctx, request.argv[0] ?? ""), ...request.argv.slice(1)];
20421
+ const proc = ctx.kernel.spawn(argv, {
20422
+ cwd: request.cwd,
20423
+ env: env2,
20424
+ cred: ctx.cred,
20425
+ ppid: ctx.proc.pid,
20426
+ stdin: stdin ? descriptionInput(stdin) : void 0,
20427
+ stdout: outputFor(request.files.get(1), ctx.stdout),
20428
+ stderr: outputFor(request.files.get(2), ctx.stderr)
20429
+ });
20430
+ children.set(proc.pid, proc);
20431
+ void proc.wait().finally(() => PENDING_INHERITANCE.delete(token));
20432
+ return proc.pid;
20433
+ },
20434
+ async wait(pid, options) {
20435
+ const candidates = pid > 0 ? [children.get(pid)].filter((p) => p !== void 0) : [...children.values()];
20436
+ if (candidates.length === 0) throw new PosixError(Errno.ECHILD);
20437
+ const reap = (proc) => {
20438
+ children.delete(proc.pid);
20439
+ return { pid: proc.pid, status: waitStatus(proc) };
20440
+ };
20441
+ const done = candidates.find(exited);
20442
+ if (done) return reap(done);
20443
+ if (options & WNOHANG) return { pid: 0, status: 0 };
20444
+ await Promise.race(candidates.map((proc) => proc.wait()));
20445
+ const finished = candidates.find(exited);
20446
+ if (!finished) throw new PosixError(Errno.ECHILD);
20447
+ return reap(finished);
20448
+ },
20449
+ kill(pid, signal) {
20450
+ const proc = children.get(pid) ?? ctx.kernel.procs.get(pid);
20451
+ if (!proc) throw new PosixError(Errno.ESRCH);
20452
+ if (signal === 0) return;
20453
+ const name = exports.SIGNAL_NAMES[signal];
20454
+ if (!name) throw new PosixError(Errno.EINVAL);
20455
+ proc.deliver(name);
20456
+ }
20457
+ };
20458
+ }
20459
+ function containerProgram(ctx, program) {
20460
+ const resolvable = (name) => name !== "" && ctx.kernel.resolveExecutable(name, ctx.cwd, ctx.env, ctx.cred) !== null;
20461
+ if (resolvable(program)) return program;
20462
+ const base2 = program.slice(program.lastIndexOf("/") + 1);
20463
+ if (resolvable(base2)) return base2;
20464
+ if (base2.startsWith("python")) return "python3";
20465
+ return program;
20466
+ }
20467
+ function descriptionInput(description) {
20468
+ const read = async (size) => {
20469
+ for (; ; ) {
20470
+ try {
20471
+ const chunk = description.read(size);
20472
+ if (chunk.length === 0) return null;
20473
+ return chunk;
20474
+ } catch {
20475
+ await description.whenReady();
20476
+ }
20477
+ }
20478
+ };
20479
+ return {
20480
+ isTTY: false,
20481
+ interactive: false,
20482
+ read,
20483
+ async readAll() {
20484
+ const parts = [];
20485
+ for (; ; ) {
20486
+ const chunk = await read(65536);
20487
+ if (chunk === null) break;
20488
+ parts.push(chunk);
20489
+ }
20490
+ let total = 0;
20491
+ for (const part of parts) total += part.length;
20492
+ const out = new Uint8Array(total);
20493
+ let at = 0;
20494
+ for (const part of parts) {
20495
+ out.set(part, at);
20496
+ at += part.length;
20497
+ }
20498
+ return out;
20499
+ }
20500
+ };
20501
+ }
20323
20502
  function withSitePackages(env2) {
20324
20503
  const existing = env2.PYTHONPATH;
20325
20504
  return {
@@ -20360,6 +20539,8 @@ async function runOwnedPython(ctx, argv) {
20360
20539
  const { manifest, workerUrl } = pythonBackend();
20361
20540
  if (!manifest) throw new Error("no Python runtime manifest is configured");
20362
20541
  const mounts = containerMounts(ctx);
20542
+ const env2 = withSitePackages(ctx.env);
20543
+ const inherit = claimInheritance(env2);
20363
20544
  const process2 = await startPythonProcess({
20364
20545
  manifest,
20365
20546
  workerUrl: await resolveWorkerUrl(workerUrl),
@@ -20368,14 +20549,16 @@ async function runOwnedPython(ctx, argv) {
20368
20549
  pid: ctx.proc.pid,
20369
20550
  ppid: ctx.proc.ppid ?? 1,
20370
20551
  argv,
20371
- env: withSitePackages(ctx.env),
20552
+ env: env2,
20372
20553
  cwd: ctx.cwd,
20373
20554
  isTty: isTerminal(ctx),
20374
20555
  mounts,
20375
20556
  onStdout: (data) => ctx.stdout.write(data),
20376
20557
  onStderr: (data) => ctx.stderr.write(data),
20377
20558
  signal: ctx.signal,
20378
- sockets: ctx.kernel.pod.sockets
20559
+ sockets: ctx.kernel.pod.sockets,
20560
+ processes: createProcessService(ctx),
20561
+ inherit
20379
20562
  });
20380
20563
  void pumpStdin(ctx, process2);
20381
20564
  return process2.wait();