Files
skills/plugins/library-navigator/skills/sp-sandbox/SKILL.md
2026-05-29 08:20:56 +01:00

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.