Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,187 @@
# Design — @suaveplan/sandbox
## File layout
```text
packages/devtools/tooling/sandbox/
package.json # 0.1.0, server-tier, sideEffects: false
vite.config.ts # multi-entry lib build, preserveModules
vitest.config.ts # forks pool, istanbul, 100/100/100/100
tsconfig.lib.json # rootDir=./src, lib=ES2022, types=[node]
README.md
src/
index.ts # explicit named re-exports
types/
sandbox.types.ts # SandboxConfig, SandboxResult, ...
index.ts
error-codes/
error-codes.ts # defineCodes("SANDBOX", [...])
error-codes.md
error-codes.test.ts
index.ts
schemas/
schemas.ts # SandboxConfigSchema, ...
schemas.md
schemas.test.ts
index.ts
telemetry/
telemetry.ts # counters + histogram + record fns
telemetry.md
telemetry.test.ts
index.ts
audit-logger/
audit-logger.ts # AuditLogger class
audit-logger.md
audit-logger.test.ts
index.ts
resource-limiter/
resource-limiter.ts # ResourceLimiter with injectable Clock
resource-limiter.md
resource-limiter.test.ts
index.ts
module-resolver/
module-resolver.ts # ModuleResolver + BLOCKED + BUILTINS
module-resolver.md
module-resolver.test.ts
index.ts
code-transformer/
code-transformer.ts # CodeTransformer + BUILTIN_RULES
code-transformer.md
code-transformer.test.ts
index.ts
vm-engine/
vm-engine.ts # VmEngine (default; node:vm)
vm-engine.md
vm-engine.test.ts
index.ts
worker-engine/
worker-engine.ts # WorkerEngine (node:worker_threads)
worker-engine.md
worker-engine.test.ts
index.ts
spawn-engine/
spawn-engine.ts # SpawnEngine (node:child_process)
spawn-engine.md
spawn-engine.test.ts
runner.ts # child entry point
runner.md
index.ts
isolate-engine/
isolate-engine.ts # IsolateEngine (structural ivm)
isolate-engine.md
isolate-engine.test.ts
index.ts
pool/
pool.ts # SandboxPool with injectable Clock
pool.md
pool.test.ts
index.ts
result-streamer/
result-streamer.ts # ResultStreamer
result-streamer.md
result-streamer.test.ts
index.ts
sandbox/
sandbox.ts # createSandbox facade
sandbox.md
sandbox.test.ts
index.ts
```
## Engine selection
`createSandbox({ engine })` accepts `"vm" | "worker" | "spawn" | "isolate"` plus
a `customEngine` factory escape hatch. Default is `"vm"`. Engines all conform to
the `SandboxEngine` contract:
```ts
interface SandboxEngine {
readonly name: string;
execute<T>(code, context, options): Promise<SandboxResult<T>> | SandboxResult<T>;
registerModule(mod): void;
unregisterModule(name): boolean;
reset(): void;
destroy(): void;
}
```
`vm-engine` and `isolate-engine` execute synchronously and wrap the result in
`Promise.resolve` at the facade level. `worker-engine` and `spawn-engine`
execute asynchronously. The facade always returns `Promise<SandboxResult<T>>`.
## Frozen-Realm pattern (vm-engine)
`node:vm` shares the host realm but `createContext` lets us hand the script a
context object whose intrinsics (`Object`, `Array`, etc.) are the host's. We
mitigate prototype-chain escapes by:
1. `vm.createContext({})` with `codeGeneration: { strings: false, wasm: false }`
to prevent `eval`/`Function` constructor inside the script.
2. `Object.freeze` on the context's `Object.prototype`, `Function.prototype`,
`Array.prototype`, and the context's `Object` constructor itself.
3. Injecting `console` as a frozen proxy that delegates only to the captured
log array.
4. `runInContext(wrappedCode, { timeout, breakOnSigint: true })` — synchronous
timeout enforcement.
The package README documents that `vm-engine` is NOT a security boundary against
adversarial code; for that, use `worker-engine` (true OS-thread isolation) or
`spawn-engine` (true process isolation).
## Test seams
- `ResourceLimiter` and `SandboxPool` accept a `Clock` (`() => number`)
defaulting to `Date.now`. Tests inject a `FakeClock` to assert sliding-window
+ idle-timeout behaviour without `setTimeout` flake.
- `SpawnEngine` accepts a `ProcessSpawner` defaulting to `child_process.spawn`.
Tests use the real spawner against a temp-file `runner.ts` fixture so the
whole pipeline (stdin write, stdout read, timeout kill, JSON parse) is
exercised end-to-end.
- `WorkerEngine` uses real `node:worker_threads`. No stubbing — workers run
inline strings through real V8.
- `IsolateEngine` consumes a structural `IsolateFactory` so tests can wire a
hand-written fake that implements the same contract a real `isolated-vm`
instance would. The structural type means the optional peer dep stays
optional and the tests stay deterministic.
## Error codes (SANDBOX.*)
`defineCodes("SANDBOX", [...])` yields fifteen codes:
- `INVALID_CONFIG` — Zod validation failed on config.
- `INVALID_OPTIONS` — Zod validation failed on per-execution options.
- `INVALID_MODULE` — Zod validation failed on a `SandboxModule`.
- `BLOCKED_MODULE` — caller tried to register a denylisted module name.
- `RATE_LIMITED` — sliding-window rate limit tripped.
- `TIMEOUT` — execution exceeded the configured timeout.
- `MEMORY_EXCEEDED` — V8 heap statistics reported usage above the limit.
- `CODE_BLOCKED` — `CodeTransformer` blocked execution.
- `CODE_TOO_LONG` — code exceeded `maxCodeLength`.
- `ENGINE_DISPOSED` — caller invoked execute after `destroy()`.
- `ENGINE_FAILED` — engine raised before producing a result envelope.
- `WORKER_FAILED` — worker thread exited or errored before responding.
- `SPAWN_FAILED` — child process exited non-zero without producing JSON.
- `POOL_EXHAUSTED` — pool is at `maxSize` with no available engines.
- `STREAM_CLOSED` — `pushChunk` after `complete()`.
Every code is reachable from a call site exercised by a test.
## OTel instruments
- Counter `sandbox_executions_total` — `{ engine, outcome }`.
- Counter `sandbox_rule_blocks_total` — `{ rule }`.
- Counter `sandbox_pool_acquisitions_total` — `{ result }` (`acquired` /
`created` / `exhausted`).
- Histogram `sandbox_execution_duration_ms` — `{ engine, outcome }`.
## Zod boundaries
`createSandbox(config?)` parses `SandboxConfigPartialSchema` then merges with
defaults. `execute(code, context?, options?)` parses the options object via
`SandboxOptionsSchema`. `registerModule(mod)` parses `SandboxModuleSchema`.
Failures raise `SandboxError` with `INVALID_CONFIG` / `INVALID_OPTIONS` /
`INVALID_MODULE`.
## No dependents
Net-new package on genesis — pure addition.