Skip to content

Commit d2e78d1

Browse files
committed
feat(secure-exec): add python runtime and unify integration suites
1 parent 5069d18 commit d2e78d1

37 files changed

Lines changed: 1656 additions & 252 deletions

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- always add or update tests that cover plausible exploit/abuse paths introduced by each feature or behavior change
99
- treat host memory buildup and CPU amplification as critical risks; avoid unbounded buffering/work (for example, default in-memory log buffering)
1010
- check GitHub Actions test/typecheck status per commit to identify when a failure first appeared
11+
- do not use `contract` in test filenames; use names like `suite`, `behavior`, `parity`, `integration`, or `policy` instead
1112

1213
## Terminology
1314

docs-internal/arch/overview.md

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
# Architecture Overview
22

33
```
4-
NodeRuntime
5-
→ SystemDriver + RuntimeDriverFactory
6-
→ NodeExecutionDriver (node target) | BrowserRuntimeDriver + Worker runtime (browser target)
4+
NodeRuntime | PythonRuntime
5+
→ SystemDriver + NodeRuntimeDriverFactory | PythonRuntimeDriverFactory
6+
→ NodeExecutionDriver (node target) | BrowserRuntimeDriver + Worker runtime (browser target) | PyodideRuntimeDriver + Worker runtime (python target)
77
```
88

9-
## NodeRuntime
9+
## NodeRuntime / PythonRuntime
1010

11-
`src/index.ts`
11+
`src/runtime.ts`, `src/python-runtime.ts`
1212

13-
Public API. Thin facade that delegates orchestration to a runtime driver.
13+
Public APIs. Thin facades that delegate orchestration to runtime drivers.
1414

15-
- `run(code)` — execute as module, get exports back
15+
- `NodeRuntime.run(code)` — execute JS module, get exports back
16+
- `PythonRuntime.run(code)` — execute Python and return structured value/global wrapper
1617
- `exec(code)` — execute as script, get exit code/error contract
1718
- `dispose()` / `terminate()`
1819
- Requires both:
1920
- `systemDriver` for runtime capabilities/config
20-
- `runtimeDriverFactory` for runtime-driver construction
21+
- runtime-driver factory for runtime-driver construction
2122

2223
## SystemDriver
2324

@@ -30,7 +31,7 @@ Config object that bundles what the sandbox can access. Deny-by-default.
3031
- `commandExecutor` — child processes
3132
- `permissions` — per-adapter allow/deny checks
3233

33-
## RuntimeDriverFactory
34+
## NodeRuntimeDriverFactory / PythonRuntimeDriverFactory
3435

3536
Factory abstraction for constructing runtime drivers from normalized runtime options.
3637

@@ -74,6 +75,15 @@ Factory that builds a browser-backed `RuntimeDriverFactory`.
7475
- Constructs `BrowserRuntimeDriver` instances
7576
- Owns worker URL/runtime-driver creation options
7677

78+
### createPyodideRuntimeDriverFactory()
79+
80+
`src/python/driver.ts`
81+
82+
Factory that builds a Python-backed `PythonRuntimeDriverFactory`.
83+
84+
- Constructs `PyodideRuntimeDriver` instances
85+
- Owns Pyodide worker bootstrap and runtime-driver creation options
86+
7787
## NodeExecutionDriver
7888

7989
`src/node/execution-driver.ts`
@@ -107,6 +117,18 @@ Worker-side runtime implementation used by the browser runtime driver.
107117
- Uses permission-aware filesystem/network adapters in the worker context
108118
- Preserves deterministic unsupported-operation contracts (for example DNS gaps)
109119

120+
## PyodideRuntimeDriver
121+
122+
`src/python/driver.ts`
123+
124+
Python execution driver that owns a Node worker running Pyodide.
125+
126+
- Loads Pyodide once per runtime instance and keeps interpreter state warm across runs
127+
- Dispatches `run`/`exec` requests and correlates responses by request ID
128+
- Streams stdio events to host hooks without runtime-managed output buffering
129+
- Uses worker-to-host RPC for permission-wrapped filesystem/network access through `SystemDriver`
130+
- Restarts worker state on execution timeout to preserve deterministic recovery behavior
131+
110132
## ModuleAccessFileSystem
111133

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

docs-internal/friction.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# Sandboxed Node Friction Log
22

3+
## 2026-03-03
4+
5+
1. **[resolved]** Python runtime contract split needed explicit cross-runtime `exec()` parity and warm-state guardrails.
6+
- Symptom: introducing Python execution risked mixing Node-only runtime-driver contracts into Python APIs and drifting `exec()` timeout/error semantics across runtimes.
7+
- Fix: split runtime-driver interfaces into Node/Python contracts, added `PythonRuntime` + `PyodideRuntimeDriver`, and enforced a shared host-facing `exec()` result contract.
8+
- Compatibility trade-off: `PythonRuntime` instances are intentionally warm/shared per instance; callers needing fresh interpreter state must create/terminate runtime instances explicitly.
9+
2. **[resolved]** Python package installation/loading pathways were intentionally out-of-scope for the first Python runtime increment.
10+
- Symptom: enabling package installation in the same change as the core runtime-driver split would widen attack surface and blur permission policy scope.
11+
- Fix: Python package install/load pathways now fail deterministically with `ERR_PYTHON_PACKAGE_INSTALL_UNSUPPORTED`.
12+
- Follow-up: define explicit package governance and permission policy before enabling runtime package install/load capabilities.
13+
314
## 2026-03-02
415

516
1. **[resolved]** Browser runtime execution was intentionally disabled during runtime-driver boundary refactor.
@@ -41,7 +52,7 @@
4152

4253
2. **[resolved]** Bridge/global type contracts drifted across host wiring, bridge modules, and isolate-runtime declarations.
4354
- Symptom: host injection keys, bridge global declarations, and isolate-runtime global types were defined ad hoc in multiple files, leaving boundary typing inconsistent and prone to regressions.
44-
- Fix: added canonical shared bridge contract definitions in `packages/secure-exec/src/shared/bridge-contract.ts`, migrated bridge modules and `src/index.ts` wiring to shared keys/types, coupled isolate-runtime declarations to shared types via type-only imports, and added `packages/secure-exec/tests/bridge-contract.test.ts` to enforce key/type registry consistency.
55+
- Fix: added canonical shared bridge contract definitions in `packages/secure-exec/src/shared/bridge-contract.ts`, migrated bridge modules and `src/index.ts` wiring to shared keys/types, coupled isolate-runtime declarations to shared types via type-only imports, and added `packages/secure-exec/tests/bridge-registry-policy.test.ts` to enforce key/type registry consistency.
4556

4657
## 2026-02-27
4758

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: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
## Context
2+
3+
`secure-exec` runtime architecture is currently optimized for Node-oriented execution drivers and contracts. The current runtime driver naming and shape make Python support ambiguous, while host-facing orchestration needs a stable `exec()` contract across runtimes.
4+
5+
This change introduces an explicit Python runtime path using a Pyodide-backed driver while keeping `SystemDriver` generic and preserving existing permission enforcement behavior. The design must keep warm runtime lifecycle semantics and avoid memory/CPU amplification regressions.
6+
7+
## Goals / Non-Goals
8+
9+
**Goals:**
10+
- Split runtime-driver interfaces into runtime-specific contracts (`NodeRuntimeDriver`, `PythonRuntimeDriver`) while preserving a shared execution result contract for `exec()`.
11+
- Add `PythonRuntime` as a user-facing API and implement a concrete Pyodide-backed Python driver.
12+
- Keep `SystemDriver` runtime-agnostic and reuse existing permissions (`fs`, `network`, `childProcess`, `env`) for Python execution.
13+
- Define a generic Python `run()` result wrapper that does not expose raw Pyodide-specific objects.
14+
- Keep warm-runtime behavior for Python execution on a single runtime instance.
15+
- Add explicit abuse-path coverage (high-volume output and timeout paths).
16+
17+
**Non-Goals:**
18+
- Dynamic runtime package installation/loading (`micropip`, import-triggered package install) behavior.
19+
- Non-Pyodide Python backends (for example host subprocess CPython).
20+
- Full parity between Node `run()` and Python `run()` result semantics.
21+
22+
## Decisions
23+
24+
1. Introduce runtime-specific driver interfaces with a shared execution base contract.
25+
- Decision: Define separate `NodeRuntimeDriver` and `PythonRuntimeDriver` interfaces, plus a shared `exec` lifecycle contract (`exec`, `dispose`, `terminate`).
26+
- Rationale: Prevent Node-only and Python-only concerns from leaking into each other while keeping host orchestration uniform.
27+
- Alternative considered: Keep one generic runtime-driver interface with optional runtime-specific methods. Rejected because it grows ambiguous and weakens type-level guarantees.
28+
29+
2. Keep `SystemDriver` capability-owned and runtime-neutral.
30+
- Decision: `SystemDriver` remains the single capability/permission boundary for both runtimes.
31+
- Rationale: Preserves deny-by-default behavior and avoids runtime-specific capability duplication.
32+
- Alternative considered: Add Python-only capability adapters to `SystemDriver`. Rejected for now to avoid premature surface expansion.
33+
34+
3. Standardize `exec()` parity across Node and Python runtimes.
35+
- Decision: Node and Python `exec()` return the same base result semantics (`code`, `errorMessage`, timeout/cancel expectations).
36+
- Rationale: Enables runtime-agnostic host orchestration and simpler control planes.
37+
- Alternative considered: Runtime-specific `exec()` contracts. Rejected due to host branching and governance complexity.
38+
39+
4. Define Python `run()` as a generic structured wrapper.
40+
- Decision: `PythonRuntime.run()` returns a structured Python run result wrapper (for example value plus optional selected bindings), not raw Pyodide proxies.
41+
- Rationale: Keeps API portable and avoids runtime-specific memory/lifecycle leaks through public contracts.
42+
- Alternative considered: Return raw Pyodide values/proxies. Rejected because it couples API consumers to one backend and increases leak risk.
43+
44+
5. Keep Python runtime warm by default per instance.
45+
- Decision: Consecutive Python executions on the same `PythonRuntime` share runtime state unless explicitly disposed/terminated.
46+
- Rationale: Matches current runtime lifecycle expectations and improves performance.
47+
- Alternative considered: Fresh interpreter per execution. Rejected for now due to startup overhead and changed semantics.
48+
49+
6. Keep package installation/loading out of scope for this change.
50+
- Decision: Python runtime package installation/loading APIs are not enabled in this change and MUST fail deterministically when invoked.
51+
- Rationale: Reduces initial attack surface and keeps scope focused on core runtime contracts.
52+
- Alternative considered: Enable package loading behind permissions in v1. Rejected to avoid mixing supply-chain policy with foundational runtime split.
53+
54+
## Risks / Trade-offs
55+
56+
- [Risk] Warm Python state can leak data across executions on the same instance. → Mitigation: document lifecycle clearly and provide deterministic `dispose`/`terminate` behavior.
57+
- [Risk] High-volume Python stdout/stderr can amplify host memory/CPU if buffered. → Mitigation: enforce streaming-only output behavior and add exploit-oriented high-volume tests.
58+
- [Risk] Timeout/cancellation semantics can drift between Node and Python drivers. → Mitigation: codify shared `exec()` parity requirements and cross-runtime contract suites.
59+
- [Risk] API overfitting to Pyodide backend details. → Mitigation: keep Python runtime public contracts backend-neutral and block direct Pyodide proxy exposure.
60+
61+
## Migration Plan
62+
63+
1. Add new runtime-driver interfaces and type aliases with backwards-compatible export strategy where possible.
64+
2. Introduce `PythonRuntime` and `PyodideRuntimeDriver` behind additive API exports.
65+
3. Update Node runtime wiring to use renamed Node-specific driver interface without behavior regressions.
66+
4. Add shared/runtime-specific contract tests and abuse-path regressions.
67+
5. Update architecture/docs to include Python runtime components and contracts.
68+
69+
## Open Questions
70+
71+
- Do we need a first-class explicit reset API for warm Python runtime instances in this change or follow-up?
72+
- Should timeout error text be strictly identical across runtimes or only semantically equivalent under shared `ExecResult` fields?
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
## Why
2+
3+
The runtime stack is currently Node-centric and cannot host Python execution without leaking Node assumptions into cross-runtime APIs. We need first-class Python runtime support now so hosts can run Python code with the same safety and permission model used by existing system drivers.
4+
5+
## What Changes
6+
7+
- Add a new Python runtime surface (`PythonRuntime`) with a dedicated driver interface and a concrete Pyodide-based driver implementation.
8+
- Split runtime driver contracts so Node and Python use separate interfaces (`NodeRuntimeDriver` and `PythonRuntimeDriver`) while preserving shared execution-result semantics for `exec()`.
9+
- Keep `SystemDriver` runtime-agnostic and require Python execution to use the same filesystem/network/child-process/env permission gates already enforced by system drivers.
10+
- Define Python `run()` contract as a runtime-neutral structured wrapper (not raw Pyodide-specific objects).
11+
- Keep warm-runtime behavior for Python execution, matching current Node runtime lifecycle expectations.
12+
- Explicitly keep runtime package installation/loading (`micropip`, import-triggered package install) out of scope for this change.
13+
14+
## Capabilities
15+
16+
### New Capabilities
17+
- `python-runtime`: Python runtime API and driver contracts, including Pyodide-backed execution behavior and Python-specific run semantics.
18+
19+
### Modified Capabilities
20+
- `node-runtime`: Runtime-driver architecture and naming/contracts updated to split Node and Python runtime drivers while preserving Node behavior.
21+
- `compatibility-governance`: Runtime-driver test governance expanded so shared `exec()` parity and Python abuse/safety regressions are covered.
22+
23+
## Impact
24+
25+
- Affected code: runtime contracts/types, runtime API exports, driver factories, and new Python runtime/driver modules.
26+
- Affected tests: new Python runtime-driver contract tests and cross-runtime `exec()` parity tests, plus abuse-path regressions for output/memory/CPU amplification.
27+
- Affected docs/specs: architecture overview and runtime specs will add Python runtime components and clarify shared-vs-runtime-specific contracts.
28+
- Dependencies: introduces Pyodide runtime integration for the Python driver implementation.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
## ADDED Requirements
2+
3+
### Requirement: Python Runtime Driver Changes MUST Validate Shared Runtime Contracts
4+
Any change that introduces or modifies Python runtime-driver behavior MUST validate shared execution contracts and Python-specific runtime-driver suites.
5+
6+
#### Scenario: Python runtime contract change triggers shared and Python runtime-driver suites
7+
- **WHEN** a change modifies Python runtime contracts or Python runtime-driver behavior under `packages/secure-exec/src/python/**`, `src/runtime.ts`, or shared runtime driver types
8+
- **THEN** the change MUST run `pnpm --filter secure-exec vitest --run tests/test-suite.test.ts tests/test-suite-python.test.ts` and `pnpm --filter secure-exec vitest --run tests/runtime-driver/python.test.ts`
9+
10+
#### Scenario: Python execution behavior change triggers Python exec behavior suites
11+
- **WHEN** a change modifies Python execution behavior or Python execution suites under `packages/secure-exec/tests/test-suite/`
12+
- **THEN** the change MUST run `pnpm --filter secure-exec vitest --run tests/test-suite-python.test.ts`
13+
14+
### Requirement: Cross-Runtime Exec Parity Must Be Regression-Tested
15+
Changes that affect shared execution result semantics SHALL preserve Node/Python `exec()` parity for host-facing result contracts.
16+
17+
#### Scenario: Shared exec result semantics change
18+
- **WHEN** a change modifies shared execution result fields or timeout/error contract behavior
19+
- **THEN** the change MUST include or update tests that verify Node and Python runtimes return the same base `exec()` contract semantics
20+
21+
### Requirement: Python Runtime Changes MUST Include Abuse-Path Coverage
22+
Python runtime changes SHALL include exploit-oriented regression coverage for memory and CPU amplification paths.
23+
24+
#### Scenario: High-volume stdout/stderr path is modified
25+
- **WHEN** Python runtime logging/stdio handling changes
26+
- **THEN** tests MUST verify high-volume output does not create unbounded host-memory accumulation
27+
28+
#### Scenario: Timeout enforcement path is modified
29+
- **WHEN** Python runtime CPU limit enforcement behavior changes
30+
- **THEN** tests MUST verify deterministic timeout contract behavior and runtime recovery expectations
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
## MODIFIED Requirements
2+
3+
### Requirement: Driver-Based Capability Composition
4+
Runtime capabilities SHALL be composed through host-provided system drivers so filesystem, network, and child-process behavior are controlled by configured adapters rather than hardcoded runtime behavior. `NodeRuntime` construction SHALL require both a capability-side `SystemDriver` and an execution-side `NodeRuntimeDriver` contract.
5+
6+
#### Scenario: Node runtime uses configured adapters with explicit Node runtime driver
7+
- **WHEN** `NodeRuntime` is created with a `SystemDriver` that defines filesystem, network, and command-execution adapters and with a `NodeRuntimeDriver`
8+
- **THEN** sandboxed operations MUST route through those adapters for capability access and execution MUST be created through the provided Node runtime driver contract
9+
10+
#### Scenario: Missing permissions deny capability access by default
11+
- **WHEN** a system driver is configured without explicit permission allowance for a capability domain
12+
- **THEN** operations in that capability domain MUST be denied by default
13+
14+
#### Scenario: Omitted capability remains unavailable
15+
- **WHEN** a capability adapter is omitted from system-driver configuration
16+
- **THEN** corresponding sandbox operations MUST be unavailable or denied by the runtime contract
17+
18+
#### Scenario: Runtime process/os config remains system-driver-owned
19+
- **WHEN** a caller provides runtime `process` and `os` configuration on the system driver
20+
- **THEN** `NodeRuntime` MUST source and inject that configuration into runtime-driver creation

0 commit comments

Comments
 (0)