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
ProcessSpawnerseam (defaults tonode:child_process.spawn). - Configurable
runnerPathandexecPath. - Timeout enforcement via
SIGKILLafterPromise.race. - Failure modes: timeout, no-output, parse-error, all surfaced via the
structured
SandboxResulterrorfield.
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.