Files
skills/docs/devtools/tooling/sandbox/src/spawn-engine/spawn-engine.md
2026-05-29 08:20:56 +01:00

2.9 KiB

SpawnEngine

(no submodules)

Purpose

SpawnEngine uses node:child_process.spawn for the strongest isolation the package offers: a fresh OS process spawned per execution, pinned to process.execPath, with a sanitised environment that strips identity-revealing keys (HOME/USER/SHELL/LOGNAME) and case-insensitive substring matches against SECRET/KEY/TOKEN/PASSWORD/CREDENTIAL/AUTH. Code is sent to the child over stdin as a JSON envelope; the result is read from stdout the same way. Timeouts are enforced with child.kill("SIGKILL") so the running script cannot intercept termination.

The child process runs runner.ts (shipped via the ./runner subpath export). The engine resolves the runner path relative to its own module URL by default, but callers can pin a custom path via the runnerPath option — useful for tests that want to drive the engine against a controlled runner fixture.

Features

  • Real node:child_process.spawn — true OS-process isolation.
  • Sanitised env via sanitiseEnv (exported for direct testing).
  • Injectable ProcessSpawner seam (defaults to node:child_process.spawn).
  • Configurable runnerPath and execPath.
  • Timeout enforcement via SIGKILL after Promise.race.
  • Failure modes: timeout, no-output, parse-error, all surfaced via the structured SandboxResult error field.

Usage: Basic

import { SpawnEngine } from "@suaveplan/sandbox/spawn-engine";

const engine = new SpawnEngine({
    timeout: 5000,
    maxOutputSize: 1024,
    memoryLimit: 50 * 1024 * 1024,
});
const r = await engine.execute("1 + 1");
engine.destroy();

Usage: Advanced

Inject a custom spawner for tests, plus a stripped-down env for tightest control:

import { SpawnEngine } from "@suaveplan/sandbox/spawn-engine";

const engine = new SpawnEngine({
    timeout: 5000,
    maxOutputSize: 1024,
    memoryLimit: 5e7,
    env: { PATH: process.env.PATH },
    spawner: (cmd, args, opts) => myFakeSpawn(cmd, args, opts),
});

API Reference

  • new SpawnEngine(options: SpawnEngineOptions).
  • execute(code, context?, options?): Promise<SandboxResult>.
  • registerModule(mod): void.
  • unregisterModule(name): boolean.
  • getRunnerPath(): string.
  • getSanitisedEnv(): Record<string, string>.
  • reset(): void.
  • destroy(): void.
  • sanitiseEnv(env) — pure helper exported for direct testing.

Implementation Notes

The raceChild helper consolidates the four ways a child can finish: clean exit with parseable JSON (ok), clean exit with non-JSON or empty output (no-output / parse-error), error event before close (parse-error), and timeout (timeout). Settling is idempotent — only the first outcome counts — so a timeout that races with a clean exit doesn't double-resolve. The kill on timeout is wrapped in try/catch because child.kill raises on already-exited processes on some platforms.