133 lines
5.9 KiB
Markdown
133 lines
5.9 KiB
Markdown
---
|
|
name: sp-sandbox
|
|
description: Server-tier sandboxed code execution with vm/worker/spawn/isolate engines, pre-warmed pools, and audit logging.
|
|
---
|
|
|
|
# Sandbox for Suaveplan
|
|
|
|
Server-tier sandboxed code execution. Ships four `SandboxEngine`
|
|
implementations (`node:vm` default, `node:worker_threads`,
|
|
`node:child_process`, and a structural `isolated-vm` adapter), a pre-warmed
|
|
`SandboxPool`, an async `ResultStreamer`, a pattern-based `CodeTransformer`,
|
|
an allowlist-based `ModuleResolver`, a sliding-window `ResourceLimiter`, and a
|
|
SHA-256 `AuditLogger` — all behind a `createSandbox()` facade with
|
|
Zod-validated config and full OpenTelemetry instrumentation.
|
|
|
|
## Architecture
|
|
|
|
| Component | Responsibility |
|
|
|-----------|---------------|
|
|
| `createSandbox` | Public facade — composes engine, resolver, limiter, logger, and transformer into a `Sandbox` interface. |
|
|
| `VmEngine` | Default engine using `node:vm` directly — fastest, but NOT a security boundary against adversarial code. |
|
|
| `WorkerEngine` | Spawns a fresh `node:worker_threads` Worker per execution — true OS-thread isolation. |
|
|
| `SpawnEngine` | Spawns a fresh `node:child_process` — strongest isolation, highest overhead. |
|
|
| `IsolateEngine` | Structural adapter over `isolated-vm` (optional peer dep) — strongest in-process isolation. |
|
|
| `SandboxPool` | Pre-warmed engine pool with lazy growth, execution-count recycling, and idle reclamation. |
|
|
| `ResourceLimiter` | Sliding-window rate limit + output-size truncation. |
|
|
| `ModuleResolver` | Allowlist/denylist gatekeeper for modules exposed to sandboxed code. |
|
|
| `CodeTransformer` | Regex-based defence-in-depth layer blocking dangerous patterns (`process.exit`, `require`, `eval`). |
|
|
| `AuditLogger` | SHA-256-hashed structured audit log of every execution. |
|
|
| `ResultStreamer` | Incremental async output delivery instead of end-of-execution dumps. |
|
|
| `SandboxError` | Typed error carrying `SANDBOX_CODES.*` via `defineCodes`. |
|
|
|
|
## File Structure
|
|
|
|
```
|
|
devtools/tooling/sandbox/
|
|
src/sandbox/ # createSandbox facade
|
|
src/vm-engine/ # VmEngine — default node:vm engine
|
|
src/worker-engine/ # WorkerEngine — node:worker_threads
|
|
src/spawn-engine/ # SpawnEngine + runner.ts child entry point
|
|
src/isolate-engine/ # IsolateEngine — isolated-vm structural adapter
|
|
src/pool/ # SandboxPool — pre-warmed engine recycling
|
|
src/resource-limiter/ # rate limit + output truncation
|
|
src/module-resolver/ # allowlist/denylist module gatekeeper
|
|
src/code-transformer/ # pattern-based dangerous-code guard
|
|
src/audit-logger/ # structured SHA-256 audit log
|
|
src/result-streamer/ # incremental async output
|
|
src/schemas/ # Zod schemas for every public boundary
|
|
src/error-codes/ # SANDBOX_CODES registry + SandboxError
|
|
src/telemetry/ # OTel instruments for the sandbox itself
|
|
src/types/ # shared SandboxEngine, SandboxResult, etc.
|
|
```
|
|
|
|
Sibling packages:
|
|
- `@suaveplan/linter` — unified markdown + biome linting pipeline
|
|
- `@suaveplan/profiler` — runtime performance profiling toolkit
|
|
- `@suaveplan/readline` — terminal I/O primitive
|
|
- `@suaveplan/test-vite` — Vite + Vitest configuration factories
|
|
|
|
## Patterns
|
|
|
|
### Quick Start — execute code in a vm sandbox
|
|
|
|
```typescript
|
|
import { createSandbox } from "@suaveplan/sandbox";
|
|
|
|
const sandbox = createSandbox({ timeout: 5000, engine: "vm" });
|
|
const result = await sandbox.execute("2 + 3");
|
|
console.log(result.result); // 5
|
|
sandbox.destroy();
|
|
```
|
|
|
|
### Engine selection guide
|
|
|
|
```typescript
|
|
import { createSandbox } from "@suaveplan/sandbox";
|
|
|
|
// Trusted accident-isolation, fastest path
|
|
const vm = createSandbox({ engine: "vm" });
|
|
|
|
// Adversarial code, OS-thread isolation
|
|
const worker = createSandbox({ engine: "worker" });
|
|
|
|
// Adversarial code, full process isolation
|
|
const spawn = createSandbox({ engine: "spawn" });
|
|
|
|
// Strongest in-process isolation (requires isolated-vm peer dep + customEngine)
|
|
const isolate = createSandbox({ engine: "isolate", customEngine: myIsolateFactory });
|
|
```
|
|
|
|
### Using the pool for high-throughput execution
|
|
|
|
```typescript
|
|
import { SandboxPool, VmEngine } from "@suaveplan/sandbox";
|
|
|
|
const pool = new SandboxPool({
|
|
engineConfig: { timeout: 5000, maxOutputSize: 1024 * 1024 },
|
|
factory: (config) => new VmEngine(config),
|
|
minSize: 2,
|
|
maxSize: 10,
|
|
});
|
|
|
|
const result = await pool.execute("1 + 1");
|
|
pool.destroy();
|
|
```
|
|
|
|
### Register custom modules
|
|
|
|
```typescript
|
|
import { createSandbox } from "@suaveplan/sandbox";
|
|
|
|
const sandbox = createSandbox();
|
|
sandbox.registerModule({ name: "math", exports: { add: (a: number, b: number) => a + b } });
|
|
const result = await sandbox.execute("math.add(2, 3)");
|
|
```
|
|
|
|
## Constraints
|
|
|
|
- **Node ≥ 20** — uses `node:vm`, `node:worker_threads`, `node:child_process`, and `node:crypto`.
|
|
- **`vm` engine is NOT a security boundary** — `vm.runInContext` shares the host realm; prototype-chain escapes are possible. Use `worker` or `spawn` for adversarial code.
|
|
- **`engine: "isolate"` requires a `customEngine` factory** — the package does not bundle `isolated-vm`; supply an `IsolateFactory` via `customEngine`.
|
|
- **Zod validates every public boundary** — `createSandbox`, `execute`, `registerModule`, and pool config all run through strict schemas.
|
|
- **CodeTransformer is defence-in-depth only** — it catches obvious accidents (`process.exit`, `require`, `eval`) but is not a substitute for an engine-level boundary.
|
|
- **Error codes go through `defineCodes`** — `SANDBOX_CODES` is produced by `defineCodes("SANDBOX", [...])`; never hand-write the string literal.
|
|
|
|
## Checklist
|
|
|
|
1. Choose the engine by threat model: `vm` for trusted code, `worker`/`spawn` for untrusted.
|
|
2. Use `SandboxPool` when you have many short executions to amortise startup cost.
|
|
3. Register modules before executing code; modules are re-synced to the engine on every `execute` call.
|
|
4. Call `sandbox.destroy()` (or `pool.destroy()`) when done to release OS resources.
|
|
5. Import from subpath exports rather than deep `src/` paths for tree-shakeability.
|