3.2 KiB
VmEngine
(no submodules)
Purpose
VmEngine is the default sandbox engine. It uses node:vm directly: caller code is compiled with new vm.Script(...) and executed via script.runInContext(ctx, { timeout }). The context is created with codeGeneration: { strings: false, wasm: false } so eval and the Function constructor are syntactically unavailable inside the sandbox even if the code-transformer's regex filter fails. Context variables and registered modules are injected as own properties of the context object; the host's intrinsics (Object, Array, etc.) are reachable through the shared realm.
VmEngine is NOT a security boundary against adversarial code. node:vm's
context-sharing model means a determined attacker with code-injection can
escape via this.constructor.constructor("...") or prototype-chain tricks.
For adversarial isolation use worker-engine (OS-thread isolation) or
spawn-engine (process isolation).
Features
- Real
node:vmexecution — synchronous, deterministic. - Frozen intrinsics flag (
codeGeneration: { strings: false, wasm: false }). - Captured console (
log/warn/error) with output-size cap. - Module + context-variable injection per execution.
- V8 heap statistics via
v8.getHeapStatistics(). - OTel
recordExecution+recordDurationemission per call. executeSynccompanion for pool callers that prefer synchronous APIs.
Usage: Basic
import { VmEngine } from "@suaveplan/sandbox/vm-engine";
const engine = new VmEngine({
timeout: 5000,
maxOutputSize: 1024,
memoryLimit: 50 * 1024 * 1024,
});
const r = await engine.execute("1 + 1");
console.log(r.result); // 2
engine.destroy();
Usage: Advanced
Inject a host bridge as a module so user code can interact with the controlled-vocabulary API rather than the host's intrinsics:
import { VmEngine } from "@suaveplan/sandbox/vm-engine";
const engine = new VmEngine({
timeout: 5000,
maxOutputSize: 1024,
memoryLimit: 50 * 1024 * 1024,
modules: [
{
name: "host",
exports: {
fetchProduct: (id: string) => loadProduct(id),
},
},
],
});
const r = await engine.execute('host.fetchProduct("sku-42")');
API Reference
new VmEngine(config: SandboxEngineConfig).execute(code, context?, options?): Promise<SandboxResult>.executeSync(code, context?, options?): SandboxResult.registerModule(mod): void.unregisterModule(name): boolean.getHeapStatistics(): { usedHeapSize, heapSizeLimit } | null.reset(): void.destroy(): void.
Implementation Notes
The compiled script wraps the user code in (${code}) so a bare expression
(e.g. 1 + 1) returns its value rather than throwing a syntax error from a
missing semicolon. An IIFE (function(){...}()) compiles to
(function(){...}()) which is also valid. The wrapper is intentionally
lightweight; callers who want statement-level execution should use
executeFunction on the facade or wrap their code in a function themselves.
The breakOnSigint: true flag is set so a SIGINT delivered to the host
terminates the running script rather than killing the entire process.