# 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 ```ts 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: ```ts 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`. - `registerModule(mod): void`. - `unregisterModule(name): boolean`. - `getRunnerPath(): string`. - `getSanitisedEnv(): Record`. - `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.