Skip to content

Commit 5069d18

Browse files
committed
refactor: simplify runtime-driver test suites and archive openspec changes
1 parent 30e92db commit 5069d18

39 files changed

Lines changed: 3034 additions & 202 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Run sandboxed Node.js code using a driver-based runtime.
1111
TODO:
1212

1313
- **Node runtime**: isolated-vm backed sandbox execution with driver-owned capability wiring.
14-
- **Browser runtime**: temporarily disabled during the driver-owned runtime refactor.
14+
- **Browser runtime**: Worker-backed execution through `NodeRuntime` + browser driver factories.
1515
- **Driver-based**: Provide a driver to map filesystem, network, and child_process.
1616
- **Permissions**: Gate syscalls with custom allow/deny functions.
1717
- **Opt-in system features**: Disable network/child_process/FS by omission.

docs-internal/arch/overview.md

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
# Architecture Overview
22

33
```
4-
NodeRuntime → SystemDriver + RuntimeDriverFactory → NodeExecutionDriver
5-
(API) (capability/config + execution wiring) (isolated-vm engine)
4+
NodeRuntime
5+
→ SystemDriver + RuntimeDriverFactory
6+
→ NodeExecutionDriver (node target) | BrowserRuntimeDriver + Worker runtime (browser target)
67
```
78

89
## NodeRuntime
910

1011
`src/index.ts`
1112

12-
Public API. Thin facade delegates everything to the execution driver.
13+
Public API. Thin facade that delegates orchestration to a runtime driver.
1314

1415
- `run(code)` — execute as module, get exports back
15-
- `exec(code)` — execute as script, get exit code + stdout/stderr
16+
- `exec(code)` — execute as script, get exit code/error contract
1617
- `dispose()` / `terminate()`
1718
- Requires both:
1819
- `systemDriver` for runtime capabilities/config
@@ -53,6 +54,26 @@ Factory that builds a Node-backed `RuntimeDriverFactory`.
5354
- Constructs `NodeExecutionDriver` instances
5455
- Owns optional Node-specific isolate creation hook
5556

57+
### createBrowserDriver()
58+
59+
`src/browser/driver.ts`
60+
61+
Factory that builds a browser `SystemDriver` with browser-native adapters.
62+
63+
- Uses OPFS or in-memory filesystem adapters
64+
- Uses fetch-backed network adapter with deterministic `ENOSYS` for unsupported DNS/server paths
65+
- Applies permission wrappers before returning the driver
66+
67+
### createBrowserRuntimeDriverFactory()
68+
69+
`src/browser/runtime-driver.ts`
70+
71+
Factory that builds a browser-backed `RuntimeDriverFactory`.
72+
73+
- Validates and rejects Node-only runtime options
74+
- Constructs `BrowserRuntimeDriver` instances
75+
- Owns worker URL/runtime-driver creation options
76+
5677
## NodeExecutionDriver
5778

5879
`src/node/execution-driver.ts`
@@ -64,6 +85,28 @@ The engine. Owns the `isolated-vm` isolate and bridges host capabilities in.
6485
- Caches compiled modules and resolved formats per isolate
6586
- Enforces payload size limits on bridge transfers
6687

88+
## BrowserRuntimeDriver
89+
90+
`src/browser/runtime-driver.ts`
91+
92+
Browser execution driver that owns worker lifecycle and message marshalling.
93+
94+
- Spawns and manages the browser runtime worker
95+
- Dispatches `run`/`exec` requests and correlates responses by request ID
96+
- Streams optional stdio events to host hooks without runtime-managed output buffering
97+
- Exposes the configured browser network adapter through `NodeRuntime.network`
98+
99+
## Browser Worker Runtime
100+
101+
`src/browser/worker.ts`
102+
103+
Worker-side runtime implementation used by the browser runtime driver.
104+
105+
- Initializes browser bridge globals and runtime config from worker init payload
106+
- Executes transformed CJS/ESM user code and returns runtime-contract results
107+
- Uses permission-aware filesystem/network adapters in the worker context
108+
- Preserves deterministic unsupported-operation contracts (for example DNS gaps)
109+
67110
## ModuleAccessFileSystem
68111

69112
`src/node/module-access.ts`

docs-internal/friction.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Sandboxed Node Friction Log
22

3+
## 2026-03-02
4+
5+
1. **[resolved]** Browser runtime execution was intentionally disabled during runtime-driver boundary refactor.
6+
- Symptom: browser entrypoints threw deterministic unsupported errors, so runtime-driver integration coverage could only exercise Node execution paths.
7+
- Fix: restored browser runtime through `NodeRuntime` driver composition (`createBrowserDriver` + `createBrowserRuntimeDriverFactory`), moved worker lifecycle/marshalling into browser runtime-driver implementation, and added shared node/browser runtime-contract integration suites plus browser runner wiring.
8+
39
## 2026-03-01
410

511
1. **[resolved]** `NodeRuntime` constructor ownership drifted between driver and direct adapter options.

docs-internal/research/comparison/isolation-mechanisms.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ A comparison of sandboxing and isolation approaches relevant to running untruste
177177
- `eval()` in Workers is not isolated from the Worker's own globals
178178
- No memory limits — a Worker can consume arbitrary memory
179179

180-
**libsandbox relevance:** This is libsandbox's browser isolation mechanism. The BrowserSandbox class creates a Web Worker and communicates via a request/response protocol over `postMessage`. Note that Worker isolation is weaker than isolated-vm — code in the Worker has full access to the Worker global scope.
180+
**libsandbox relevance:** This is libsandbox's browser isolation mechanism. `NodeRuntime` uses a browser runtime driver that creates a Web Worker and communicates via a request/response protocol over `postMessage`. Note that Worker isolation is weaker than isolated-vm — code in the Worker has full access to the Worker global scope.
181181

182182
---
183183

docs/quickstart.mdx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,26 @@ description: Get secure-exec running from TypeScript in a few minutes.
3030
await proc.dispose();
3131
```
3232
</Step>
33+
<Step title="Execute code in browser">
34+
```ts
35+
import {
36+
NodeRuntime,
37+
createBrowserDriver,
38+
createBrowserRuntimeDriverFactory,
39+
} from "secure-exec/browser";
40+
41+
const runtime = new NodeRuntime({
42+
systemDriver: await createBrowserDriver({
43+
filesystem: "memory",
44+
}),
45+
runtimeDriverFactory: createBrowserRuntimeDriverFactory(),
46+
});
47+
48+
const result = await runtime.exec("console.log('hello from browser runtime')");
49+
if (result.errorMessage) {
50+
throw new Error(result.errorMessage);
51+
}
52+
await runtime.terminate();
53+
```
54+
</Step>
3355
</Steps>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
schema: spec-driven
2+
created: 2026-03-02
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
## Context
2+
3+
The runtime architecture has converged on a universal `NodeRuntime` orchestrator that composes a capability-side `SystemDriver` with an execution-side `RuntimeDriverFactory`. During the boundary refactor, browser execution was intentionally disabled and `BrowserSandbox` was left as a throwing compatibility surface.
4+
5+
The next step is to restore browser execution without reintroducing split APIs or duplicated orchestration logic. The design target is one runtime API (`NodeRuntime`) with driver-only variation, plus reusable integration tests that assert runtime-contract behavior across `node` and `browser` execution targets.
6+
7+
## Goals / Non-Goals
8+
9+
**Goals:**
10+
- Restore browser execution using the existing `NodeRuntime` + runtime-driver architecture.
11+
- Remove `BrowserSandbox` and keep one universal runtime API.
12+
- Keep capability ownership in `SystemDriver` and move browser execution heavy lifting into a browser runtime driver implementation.
13+
- Enforce deterministic validation for Node-only options when browser runtime is selected.
14+
- Introduce reusable integration suites (`run*` functions) that can be executed with a shared `TestContext` class for both node and browser targets.
15+
16+
**Non-Goals:**
17+
- Backward compatibility for `BrowserSandbox` or legacy constructor aliases.
18+
- Adding new capability domains beyond existing fs/network/child-process/env/runtime behavior.
19+
- Full cross-browser matrix expansion in this change (single supported browser engine is sufficient initially).
20+
21+
## Decisions
22+
23+
### 1. Keep `NodeRuntime` as the only execution API
24+
25+
- **Choice:** Browser execution is restored by adding a browser `RuntimeDriverFactory`, not by reintroducing a separate browser runtime class.
26+
- **Rationale:** This preserves the architecture goal that only the driver changes across targets.
27+
- **Alternative considered:** Keep `BrowserSandbox` as a wrapper around `NodeRuntime`; rejected because it keeps parallel public runtime surfaces and API drift risk.
28+
29+
### 2. Move browser runtime heavy lifting into browser runtime driver
30+
31+
- **Choice:** Implement browser execution lifecycle in browser runtime-driver code (worker spin-up/teardown, message protocol, runtime marshalling, and browser bridge shims).
32+
- **Rationale:** Mirrors Node driver ownership and keeps `NodeRuntime` focused on orchestration.
33+
- **Alternative considered:** Put browser worker orchestration directly in `NodeRuntime`; rejected because it breaks driver boundary consistency.
34+
35+
### 3. Keep `createBrowserDriver` as capability-side `SystemDriver` factory
36+
37+
- **Choice:** `createBrowserDriver` returns a `SystemDriver` (OPFS/in-memory filesystem, browser fetch-backed network adapter, permission wrappers) and pairs with a separate browser runtime-driver factory.
38+
- **Rationale:** Capability composition remains symmetric across node and browser targets.
39+
- **Alternative considered:** Make browser runtime-driver implicitly create capability adapters; rejected because it conflates capability and execution layers.
40+
41+
### 4. Reject Node-only execution options for browser target
42+
43+
- **Choice:** Browser runtime-driver factory performs explicit validation and rejects `memoryLimit`, `cpuTimeLimitMs`, `timingMitigation`, and payload-limit overrides.
44+
- **Rationale:** These options are Node/isolated-vm-specific and silently ignoring them would create unsafe ambiguity.
45+
- **Alternative considered:** Ignore unsupported fields for browser; rejected because misconfiguration would pass silently.
46+
47+
### 5. Shared integration harness with class-based context
48+
49+
- **Choice:** Add `tests/integration/` suites exporting `runX(ctx: TestContext)` functions, and one orchestrator test that loops targets (`node`, `browser`) and executes all shared suites under `describe` blocks.
50+
- **Rationale:** Reuses existing runtime-contract tests while preventing node/browser drift.
51+
- **Alternative considered:** Duplicate separate node and browser spec files; rejected due to maintenance overhead and parity gaps.
52+
53+
### 6. Run browser integration through a browser test runner profile
54+
55+
- **Choice:** Add a dedicated browser integration runner configuration and script that executes the same suite in a real browser engine.
56+
- **Rationale:** Browser runtime behavior (Worker/OPFS/fetch semantics) needs real-browser validation.
57+
- **Alternative considered:** Node-only emulation for browser tests; rejected because it cannot validate critical browser runtime primitives.
58+
59+
## Risks / Trade-offs
60+
61+
- [Browser runtime contract mismatch with Node semantics] -> Mitigation: keep shared `run*` integration suites as parity gates and document intentional deviations in friction docs.
62+
- [Browser tests are slower/flakier than node tests] -> Mitigation: keep browser suite targeted and under one minute, run full matrix only in dedicated CI lanes.
63+
- [Removal of `BrowserSandbox` breaks existing consumers] -> Mitigation: treat as explicit breaking change with updated docs/examples in the same change.
64+
- [Capability gaps in browser adapters (for example DNS/HTTP2)] -> Mitigation: keep deterministic `ENOSYS` contracts and spec coverage for unsupported operations.
65+
66+
## Migration Plan
67+
68+
1. Implement browser runtime driver factory and runtime-driver class that satisfy `RuntimeDriver` contract.
69+
2. Re-enable `createBrowserDriver` capability factory and remove/retire `BrowserSandbox` exports.
70+
3. Add browser target wiring in test utilities via a class-based `TestContext` abstraction.
71+
4. Migrate selected existing runtime-contract tests into reusable `tests/integration/run*.ts` suites.
72+
5. Add node and browser integration entrypoints that execute the same `run*` suites by target.
73+
6. Update docs/specs/friction notes and run targeted typecheck/test commands for node and browser profiles.
74+
75+
## Open Questions
76+
77+
- Which single browser engine should be the default CI gate in phase one (Chromium recommended)?
78+
- Should unsupported browser options fail at `NodeRuntime` construction time or runtime-driver factory creation time (factory-time preferred for clearer ownership)?
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
## Why
2+
3+
Browser runtime execution is currently disabled while the runtime-driver architecture has already converged on a universal `NodeRuntime` API. We need to restore browser support now by implementing a browser runtime driver and validating behavior parity with reusable integration tests.
4+
5+
## What Changes
6+
7+
- **BREAKING**: Remove `BrowserSandbox` as a public execution API and standardize on `NodeRuntime` with runtime-driver selection (`node` vs `browser`).
8+
- Add a browser `RuntimeDriverFactory` implementation that performs the browser-specific execution heavy lifting (worker lifecycle, host bridge wiring, and browser capability shims) while reusing the same `NodeRuntime` orchestration contract.
9+
- Restore browser capability driver construction (`createBrowserDriver`) and wire it to the same `SystemDriver` + `RuntimeDriverFactory` composition model as Node.
10+
- Enforce deterministic option validation for browser runtime-driver creation: reject Node-only runtime options (`memoryLimit`, `cpuTimeLimitMs`, `timingMitigation`, and payload-limit overrides) when targeting browser execution.
11+
- Add a shared integration harness built around a `TestContext` class and reusable `run*` suites that execute the same runtime-contract tests for both driver targets.
12+
- Keep existing deny-by-default permissions behavior and runtime capability contracts consistent across both targets.
13+
14+
## Capabilities
15+
16+
### New Capabilities
17+
- `runtime-driver-integration-testing`: Shared integration test harness that runs reusable runtime suites against multiple runtime-driver targets with one contract surface.
18+
19+
### Modified Capabilities
20+
- `node-runtime`: Runtime execution support is expanded from Node-only phase behavior to include browser runtime-driver execution via `NodeRuntime`; browser-disabled requirements are removed and replaced with parity/validation requirements.
21+
- `compatibility-governance`: Validation policy requires targeted integration coverage that runs shared runtime-contract suites across both node and browser runtime-driver targets.
22+
23+
## Impact
24+
25+
- Affected runtime API and implementation:
26+
- `packages/secure-exec/src/index.ts`
27+
- `packages/secure-exec/src/runtime-driver.ts`
28+
- `packages/secure-exec/src/browser/driver.ts`
29+
- `packages/secure-exec/src/browser/worker.ts`
30+
- `packages/secure-exec/src/browser/index.ts` (removal or deprecation path)
31+
- `packages/secure-exec/src/node/driver.ts`
32+
- Affected tests:
33+
- New integration suite directory under `packages/secure-exec/tests/integration/`
34+
- Existing `packages/secure-exec/tests/test-utils.ts` and selected runtime tests migrated to shared `run*` suites
35+
- Affected docs/specs:
36+
- `openspec/specs/node-runtime/spec.md`
37+
- `openspec/specs/compatibility-governance/spec.md`
38+
- compatibility/friction docs and runtime API docs referencing browser runtime availability
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
## ADDED Requirements
2+
3+
### Requirement: Runtime Driver Contract Changes MUST Run Shared Cross-Target Integration Suites
4+
Any change that modifies runtime-driver behavior or runtime orchestration contracts MUST run shared integration suites against both node and browser runtime-driver targets.
5+
6+
#### Scenario: Runtime/driver implementation changes trigger cross-target validation
7+
- **WHEN** a change modifies runtime contracts or driver behavior under `packages/secure-exec/src/index.ts`, `src/runtime-driver.ts`, `src/node/**`, or `src/browser/**`
8+
- **THEN** the change MUST execute shared integration suites for both node and browser targets before completion
9+
10+
#### Scenario: Shared suites are reused between targets
11+
- **WHEN** runtime integration coverage is executed for node and browser
12+
- **THEN** both targets MUST run the same reusable `run*` contract suites rather than target-specific duplicated logic
13+
14+
### Requirement: Browser Runtime Validation Workflow MUST Remain Available To Contributors
15+
Repository scripts and test wiring MUST provide a documented way to run browser runtime integration tests locally using the shared runtime-contract suites.
16+
17+
#### Scenario: Contributor runs targeted browser integration validation
18+
- **WHEN** a contributor runs the documented browser integration command
19+
- **THEN** the runtime integration suite MUST execute in a real browser environment and report pass/fail for the shared contract suites
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
## MODIFIED Requirements
2+
3+
### Requirement: Unified Sandbox Execution Interface
4+
The project SHALL provide a stable sandbox execution interface through `NodeRuntime`, with `exec` for running untrusted code and returning structured execution results and `run` for module-export evaluation. Runtime-driver target selection (node or browser) MUST not require separate runtime classes.
5+
6+
#### Scenario: Execute code in Node runtime target
7+
- **WHEN** a caller creates `NodeRuntime` with a Node-target runtime driver and invokes `exec`
8+
- **THEN** the sandbox MUST run the provided code in an isolated execution context and return structured output for the caller
9+
10+
#### Scenario: Execute code in browser runtime target
11+
- **WHEN** a caller creates `NodeRuntime` with a browser-target runtime driver and invokes `exec`
12+
- **THEN** execution MUST run through browser runtime primitives and return the same structured runtime result contract
13+
14+
#### Scenario: Run CJS module and retrieve exports
15+
- **WHEN** a caller invokes `run()` with CommonJS code that assigns to `module.exports`
16+
- **THEN** the result's `exports` field MUST contain the value of `module.exports`
17+
18+
#### Scenario: Run ESM module and retrieve namespace exports
19+
- **WHEN** a caller invokes `run()` with ESM code that uses `export` declarations
20+
- **THEN** the result's `exports` field MUST contain the module namespace object with all named exports and the `default` export (if declared)
21+
22+
#### Scenario: Run ESM module with only a default export
23+
- **WHEN** a caller invokes `run()` with ESM code containing `export default <value>`
24+
- **THEN** the result's `exports` field MUST be an object with a `default` property holding that value
25+
26+
#### Scenario: Run ESM module with named and default exports
27+
- **WHEN** a caller invokes `run()` with ESM code containing both `export default` and named `export` declarations
28+
- **THEN** the result's `exports` field MUST be an object containing both the `default` property and all named export properties
29+
30+
## ADDED Requirements
31+
32+
### Requirement: Browser Runtime Driver Rejects Node-Only Execution Options
33+
Browser-target runtime-driver construction MUST reject Node-specific runtime options that are unsupported in browser execution.
34+
35+
#### Scenario: Browser runtime rejects Node-only execution controls
36+
- **WHEN** a caller creates `NodeRuntime` with a browser runtime-driver factory and passes any of `memoryLimit`, `cpuTimeLimitMs`, `timingMitigation`, or payload-limit overrides
37+
- **THEN** construction MUST fail with a deterministic validation error indicating unsupported browser runtime options
38+
39+
#### Scenario: Browser runtime accepts baseline cross-target options
40+
- **WHEN** a caller creates `NodeRuntime` with a browser runtime-driver factory using only cross-target options (for example `systemDriver`, `runtimeDriverFactory`, and optional `onStdio`)
41+
- **THEN** runtime construction MUST succeed and preserve runtime execution behavior
42+
43+
### Requirement: BrowserSandbox API Surface Is Removed
44+
The runtime contract MUST expose browser execution through `NodeRuntime` driver composition and MUST NOT require a separate `BrowserSandbox` execution class.
45+
46+
#### Scenario: Runtime entrypoint is unified by driver target
47+
- **WHEN** a caller needs browser execution behavior
48+
- **THEN** they MUST construct `NodeRuntime` with browser system/runtime drivers rather than a separate browser sandbox runtime class

0 commit comments

Comments
 (0)