@directive-run/sandbox 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +13 -13
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -4,7 +4,7 @@ Execute Directive snippets server-side and return a structured transcript (logs,
4
4
 
5
5
  ## What it does
6
6
 
7
- Takes a TypeScript snippet (single source string OR a paired library + runner `files: [{path, source}]` array same shape `playground_link` accepts), validates it against an AST allowlist, bundles the multi-file payload via esbuild, and executes the result in a bounded `worker_threads` sandbox. Returns:
7
+ Takes a TypeScript snippet (single source string OR a paired library + runner `files: [{path, source}]` array same shape `playground_link` accepts), validates it against an AST allowlist, bundles the multi-file payload via esbuild, and executes the result in a bounded `worker_threads` sandbox. Returns:
8
8
 
9
9
  ```ts
10
10
  interface SandboxResult {
@@ -16,22 +16,22 @@ interface SandboxResult {
16
16
  }
17
17
  ```
18
18
 
19
- The bundler injects an early-capture immediately after `createSystem(...)`, so the post-mortem snapshot survives mid-runner errors a validation throw inside `await system.settle()` still hands you the init-state facts.
19
+ The bundler injects an early-capture immediately after `createSystem(...)`, so the post-mortem snapshot survives mid-runner errors a validation throw inside `await system.settle()` still hands you the init-state facts.
20
20
 
21
21
  ## Sandbox boundary
22
22
 
23
23
  Three layers:
24
24
 
25
25
  1. **AST allowlist validator** (`ts-morph`). Imports are restricted to a curated `@directive-run/*` set + relative `./*.js`:
26
- - **Allowed:** `core`, `ai`, `query`, `react`, `vue`, `svelte`, `solid`, `lit`, `el`, `optimistic`, `timeline`, `mutator`, `knowledge`, `scaffold`, `claude-plugin`, `lint`, `sources` (17 packages anything an end-user demo realistically composes from; the canonical, drift-proof list is `ALLOWED_DIRECTIVE_PACKAGES` in `src/validator.ts`).
27
- - **Denied:** `cli`, `mcp`, `sandbox`, `vite-plugin-api-proxy` (build / CLI / sandbox-meta tooling no legitimate use inside a sandboxed demo).
26
+ - **Allowed:** `core`, `ai`, `query`, `react`, `vue`, `svelte`, `solid`, `lit`, `el`, `optimistic`, `timeline`, `mutator`, `knowledge`, `scaffold`, `claude-plugin`, `lint`, `sources` (17 packages anything an end-user demo realistically composes from; the canonical, drift-proof list is `ALLOWED_DIRECTIVE_PACKAGES` in `src/validator.ts`).
27
+ - **Denied:** `cli`, `mcp`, `sandbox`, `vite-plugin-api-proxy` (build / CLI / sandbox-meta tooling no legitimate use inside a sandboxed demo).
28
28
  - Everything else (`node:fs`, `express`, `@sizls/*`, etc.) is rejected.
29
29
  - **Identifier references** to FS / network / eval surfaces (`process`, `require`, `fetch`, `fs`, `child_process`, `eval`, `new Function`, `setTimeout`, `Buffer`, etc.) are rejected as free identifiers.
30
30
  - **Property-access bypass chains** are rejected (v0.3.0): `globalThis.process`, `globalThis.fetch`, `globalThis["X"]` bracket access with string literal, `.constructor` access on any value, `Function(...)` call without `new`, `Reflect.get/has/getOwnPropertyDescriptor(globalThis, "X")` smuggle chains. The [June 2026 security audit](../../docs/security/sandbox-audit-2026-06.md) traces these PoCs and how v0.3.0 closes them.
31
31
  2. **esbuild bundler**. Virtualizes the multi-file payload into a single ESM string with `@directive-run/*` externalized. Throws on imports that can't be resolved against the in-memory file map.
32
- 3. **`worker_threads.Worker`** with `resourceLimits` (32 MB heap, 16 MB code) and a clamped wall-clock budget (`[100ms, 10s]`, default 5s). The worker is hard-terminated on timeout no cooperative cancellation needed.
32
+ 3. **`worker_threads.Worker`** with `resourceLimits` (32 MB heap, 16 MB code) and a clamped wall-clock budget (`[100ms, 10s]`, default 5s). The worker is hard-terminated on timeout no cooperative cancellation needed.
33
33
 
34
- Resource limits are heap-only workers share the parent process's FS / network access. The allowlist validator is what actually prevents sandbox escape; the worker layer adds OOM / runaway-CPU bounding on top.
34
+ Resource limits are heap-only workers share the parent process's FS / network access. The allowlist validator is what actually prevents sandbox escape; the worker layer adds OOM / runaway-CPU bounding on top.
35
35
 
36
36
  ## API
37
37
 
@@ -50,14 +50,14 @@ console.log(result.logs); // ["[start] count= 0", "[settled] count= 2"]
50
50
  console.log(result.facts); // { count: 2 }
51
51
  ```
52
52
 
53
- The single-source shortcut `runInSandbox({ source: "..." })` maps onto `src/main.ts` internally convenient for already-runnable snippets from `get_example` / `fix_code`.
53
+ The single-source shortcut `runInSandbox({ source: "..." })` maps onto `src/main.ts` internally convenient for already-runnable snippets from `get_example` / `fix_code`.
54
54
 
55
55
  ### Cancellation
56
56
 
57
- `runInSandbox` accepts an optional `AbortSignal`. Wire it to your HTTP request's signal (Next.js, Express, and Hono all expose one) so a client that disconnects mid-flight releases its worker slot immediately. Without this, an abandoned caller still occupies the per-process worker queue until the worker times out (up to 10s) under sustained load that drives the pool to deadlock.
57
+ `runInSandbox` accepts an optional `AbortSignal`. Wire it to your HTTP request's signal (Next.js, Express, and Hono all expose one) so a client that disconnects mid-flight releases its worker slot immediately. Without this, an abandoned caller still occupies the per-process worker queue until the worker times out (up to 10s) under sustained load that drives the pool to deadlock.
58
58
 
59
59
  ```ts
60
- // Next.js App Router `request.signal` fires on client disconnect.
60
+ // Next.js App Router `request.signal` fires on client disconnect.
61
61
  export async function POST(request: Request) {
62
62
  const result = await runInSandbox({
63
63
  source,
@@ -67,7 +67,7 @@ export async function POST(request: Request) {
67
67
  }
68
68
  ```
69
69
 
70
- The signal aborts the queue-wait phase AND the running worker the slot frees the moment the signal trips, not when the wall-clock budget expires.
70
+ The signal aborts the queue-wait phase AND the running worker the slot frees the moment the signal trips, not when the wall-clock budget expires.
71
71
 
72
72
  ### Operator knobs
73
73
 
@@ -75,7 +75,7 @@ The signal aborts the queue-wait phase AND the running worker — the slot frees
75
75
 
76
76
  ```ts
77
77
  import { setMaxConcurrentWorkers } from "@directive-run/sandbox";
78
- // At module init runs once per cold start.
78
+ // At module init runs once per cold start.
79
79
  setMaxConcurrentWorkers(8);
80
80
  ```
81
81
 
@@ -88,5 +88,5 @@ logger.error({ msg: "sandbox failed", err: sanitizeStack(err.stack) });
88
88
 
89
89
  ## See also
90
90
 
91
- - [`@directive-run/mcp`](../mcp) wraps `runInSandbox` as the `run_in_sandbox` MCP tool, returns the transcript to the AI client alongside a `playgroundUrl`.
92
- - [`@directive-run/scaffold`](../scaffold) `generateRunner` produces the canonical runner shape this sandbox expects to execute (`createSystem` → `start()` → dispatch → `settle()` → log).
91
+ - [`@directive-run/mcp`](../mcp) wraps `runInSandbox` as the `run_in_sandbox` MCP tool, returns the transcript to the AI client alongside a `playgroundUrl`.
92
+ - [`@directive-run/scaffold`](../scaffold) `generateRunner` produces the canonical runner shape this sandbox expects to execute (`createSystem` → `start()` → dispatch → `settle()` → log).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@directive-run/sandbox",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Execute Directive snippets server-side and return a structured transcript (logs + facts + errors). Consumed by @directive-run/mcp's run_in_sandbox tool and directive.run/playground's live DevTools panel. Uses worker_threads + esbuild bundling + an AST allowlist validator so user-supplied TypeScript runs with a bounded surface (allowlisted imports, allowlisted API calls, 5s wall clock, 32 MB heap).",
5
5
  "license": "(MIT OR Apache-2.0)",
6
6
  "author": "Jason Comes",
@@ -47,7 +47,7 @@
47
47
  "dist"
48
48
  ],
49
49
  "dependencies": {
50
- "@directive-run/core": "1.21.0"
50
+ "@directive-run/core": "1.22.0"
51
51
  },
52
52
  "optionalDependencies": {
53
53
  "esbuild": "^0.24.0",