Skip to content

Commit fa60c9c

Browse files
authored
Merge pull request #7 from rivet-dev/se-website
feat: docs site and marketing website
2 parents 7659aba + 7439fa6 commit fa60c9c

61 files changed

Lines changed: 6781 additions & 364 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs-internal/todo.md

Lines changed: 96 additions & 128 deletions
Large diffs are not rendered by default.

docs/api-reference.mdx

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
title: API Reference
33
description: Complete reference for all secure-exec exports.
4+
icon: "rectangle-code"
45
---
56

67
## Runtimes
@@ -390,33 +391,33 @@ type StdioHook = (event: { channel: "stdout" | "stderr"; message: string }) => v
390391

391392
| Field | Type | Default |
392393
|---|---|---|
393-
| `platform` | `string` | |
394-
| `arch` | `string` | |
395-
| `version` | `string` | |
394+
| `platform` | `string` | |
395+
| `arch` | `string` | |
396+
| `version` | `string` | |
396397
| `cwd` | `string` | `"/root"` |
397-
| `env` | `Record<string, string>` | |
398-
| `argv` | `string[]` | |
399-
| `execPath` | `string` | |
400-
| `pid` | `number` | |
401-
| `ppid` | `number` | |
402-
| `uid` | `number` | |
403-
| `gid` | `number` | |
404-
| `stdin` | `string` | |
405-
| `timingMitigation` | `"off" \| "freeze"` | |
406-
| `frozenTimeMs` | `number` | |
398+
| `env` | `Record<string, string>` | |
399+
| `argv` | `string[]` | |
400+
| `execPath` | `string` | |
401+
| `pid` | `number` | |
402+
| `ppid` | `number` | |
403+
| `uid` | `number` | |
404+
| `gid` | `number` | |
405+
| `stdin` | `string` | |
406+
| `timingMitigation` | `"off" \| "freeze"` | |
407+
| `frozenTimeMs` | `number` | |
407408

408409
### `OSConfig`
409410

410411
| Field | Type | Default |
411412
|---|---|---|
412-
| `platform` | `string` | |
413-
| `arch` | `string` | |
414-
| `type` | `string` | |
415-
| `release` | `string` | |
416-
| `version` | `string` | |
413+
| `platform` | `string` | |
414+
| `arch` | `string` | |
415+
| `type` | `string` | |
416+
| `release` | `string` | |
417+
| `version` | `string` | |
417418
| `homedir` | `string` | `"/root"` |
418419
| `tmpdir` | `string` | `"/tmp"` |
419-
| `hostname` | `string` | |
420+
| `hostname` | `string` | |
420421

421422
### `SystemDriver`
422423

docs/architecture.mdx

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
---
2+
title: Architecture
3+
description: How secure-exec components fit together across runtimes and environments.
4+
icon: "sitemap"
5+
---
6+
7+
## Overview
8+
9+
Every secure-exec sandbox has three layers: a **runtime** (public API), a **bridge** (isolation boundary), and a **system driver** (host capabilities).
10+
11+
```mermaid
12+
flowchart TB
13+
subgraph Host["Host Process"]
14+
RT["Runtime<br/>(NodeRuntime / PythonRuntime)"]
15+
SD["System Driver<br/>(filesystem, network, processes, permissions)"]
16+
end
17+
subgraph Isolate["Sandbox"]
18+
UC["User Code"]
19+
BR["Bridge"]
20+
end
21+
RT --> SD
22+
RT --> Isolate
23+
UC -->|"capability request"| BR
24+
BR -->|"permission-gated call"| SD
25+
```
26+
27+
User code runs inside the sandbox and can only reach host capabilities through the bridge. The bridge enforces payload size limits on every transfer. The system driver wraps each capability in a permission check before executing it on the host.
28+
29+
## Components
30+
31+
### Runtime
32+
33+
The public API. `NodeRuntime` and `PythonRuntime` are thin facades that accept a system driver and a runtime driver factory, then delegate all execution to the runtime driver.
34+
35+
```ts
36+
const runtime = new NodeRuntime({
37+
systemDriver: createNodeDriver(),
38+
runtimeDriverFactory: createNodeRuntimeDriverFactory(),
39+
});
40+
41+
await runtime.exec("console.log('hello')");
42+
await runtime.run("export default 42");
43+
runtime.dispose();
44+
```
45+
46+
### System Driver
47+
48+
A config object that bundles host capabilities. Deny-by-default.
49+
50+
| Capability | What it provides |
51+
|---|---|
52+
| `filesystem` | Read/write/stat/mkdir operations |
53+
| `network` | fetch, DNS, HTTP |
54+
| `commandExecutor` | Child process spawning |
55+
| `permissions` | Per-capability allow/deny checks |
56+
57+
Each capability is wrapped in a permission layer before the bridge can call it. Missing capabilities get deny-all stubs.
58+
59+
### Bridge
60+
61+
The narrow interface between the sandbox and the host. All privileged operations pass through the bridge. It serializes requests, enforces payload size limits, and routes calls to the appropriate system driver capability.
62+
63+
### Runtime Driver
64+
65+
Manages the actual execution environment. This is where the runtime-specific isolation mechanism lives.
66+
67+
## Node Runtime
68+
69+
On Node, the sandbox is a V8 isolate managed by `isolated-vm`.
70+
71+
```mermaid
72+
flowchart TB
73+
subgraph Host["Host (Node.js process)"]
74+
NR["NodeRuntime"]
75+
NED["NodeExecutionDriver"]
76+
SD["System Driver"]
77+
MAFS["ModuleAccessFileSystem"]
78+
end
79+
subgraph ISO["V8 Isolate"]
80+
UC["User Code (CJS / ESM)"]
81+
BR["Bridge (ivm.Reference callbacks)"]
82+
MOD["Module Cache"]
83+
end
84+
NR --> NED
85+
NED --> ISO
86+
UC --> BR
87+
BR -->|"fs / net / process / crypto"| SD
88+
SD --> MAFS
89+
```
90+
91+
**Inside the isolate:**
92+
- User code runs as CJS or ESM (auto-detected from `package.json` `type` field)
93+
- Bridge globals are injected as `ivm.Reference` callbacks for fs, network, child_process, crypto, and timers
94+
- Compiled modules are cached per isolate
95+
- `Date.now()` and `performance.now()` return frozen values by default (timing mitigation)
96+
- `SharedArrayBuffer` is unavailable in freeze mode
97+
98+
**Outside the isolate (host):**
99+
- `NodeExecutionDriver` creates contexts, compiles modules, and manages the isolate lifecycle
100+
- `ModuleAccessFileSystem` overlays host `node_modules` at `/app/node_modules` (read-only, blocks `.node` native addons, prevents symlink escapes)
101+
- System driver applies permission checks before every host operation
102+
- Bridge enforces payload size limits on all transfers (`ERR_SANDBOX_PAYLOAD_TOO_LARGE`)
103+
104+
**Resource controls:**
105+
- `memoryLimit`: V8 isolate memory cap (default 128 MB)
106+
- `cpuTimeLimitMs`: CPU time budget (exit code 124 on exceeded)
107+
- `timingMitigation`: `"freeze"` (default) or `"off"`
108+
109+
## Browser Runtime
110+
111+
In the browser, the sandbox is a Web Worker.
112+
113+
```mermaid
114+
flowchart TB
115+
subgraph Host["Host (browser main thread)"]
116+
NR["NodeRuntime"]
117+
BRD["BrowserRuntimeDriver"]
118+
SD["System Driver"]
119+
end
120+
subgraph WK["Web Worker"]
121+
UC["User Code (CJS / ESM)"]
122+
BR["Bridge (postMessage RPC)"]
123+
end
124+
NR --> BRD
125+
BRD -->|"postMessage"| WK
126+
UC --> BR
127+
BR -->|"postMessage"| SD
128+
```
129+
130+
**Inside the worker:**
131+
- User code runs as transformed CJS/ESM
132+
- Bridge globals are initialized from the worker init payload
133+
- Filesystem and network use permission-aware adapters
134+
- DNS operations return deterministic `ENOSYS` errors
135+
136+
**Outside the worker (host):**
137+
- `BrowserRuntimeDriver` spawns workers, dispatches requests by ID, and correlates responses
138+
- `createBrowserDriver()` configures OPFS or in-memory filesystem and fetch-based networking
139+
- Node-only runtime options (like `memoryLimit`) are validated and rejected at creation time
140+
141+
## Python Runtime
142+
143+
The Python sandbox runs Pyodide in a Node Worker thread.
144+
145+
```mermaid
146+
flowchart TB
147+
subgraph Host["Host (Node.js process)"]
148+
PR["PythonRuntime"]
149+
PRD["PyodideRuntimeDriver"]
150+
SD["System Driver"]
151+
end
152+
subgraph WK["Worker Thread"]
153+
PY["Pyodide (CPython via Emscripten)"]
154+
UC["User Code (Python)"]
155+
BR["Bridge (worker RPC)"]
156+
end
157+
PR --> PRD
158+
PRD -->|"worker messages"| WK
159+
UC --> BR
160+
BR -->|"worker RPC"| SD
161+
```
162+
163+
**Inside the worker:**
164+
- Pyodide loads once and keeps interpreter state warm across runs
165+
- Python code executes with access to bridged filesystem and network
166+
- stdio streams to the host via message events
167+
168+
**Outside the worker (host):**
169+
- `PyodideRuntimeDriver` manages worker lifecycle and request correlation
170+
- Filesystem and network access goes through the same `SystemDriver` permission layer
171+
- On execution timeout, the worker state restarts for deterministic recovery
172+
- No `memoryLimit` or `timingMitigation` (Pyodide runs in a Worker, not a V8 isolate)
173+
174+
## Permission Flow
175+
176+
Every capability request follows the same path regardless of runtime:
177+
178+
```
179+
User Code -> Bridge -> Permission Check -> System Driver -> Host OS
180+
```
181+
182+
If the permission check denies the request, the bridge returns an error before the system driver is called. If no adapter is configured for a capability, a deny-all stub handles it.

docs/cloudflare-workers-comparison.mdx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
title: Cloudflare Workers Comparison
33
description: Node.js API compatibility comparison between secure-exec and Cloudflare Workers (standard, Workers for Platforms, dynamic dispatch).
4+
icon: "scale-balanced"
45
---
56

67
*Last updated: 2026-03-10*
@@ -23,12 +24,12 @@ All three CF deployment models share the same `nodejs_compat` API surface. WfP a
2324

2425
| Icon | Meaning |
2526
| --- | --- |
26-
| 🟢 | Supported native or full implementation. |
27-
| 🔵 | Planned not yet implemented; on the roadmap. |
28-
| 🟡 | Partial functional with behavioral gaps or wrapper limitations. |
29-
|| TBD under consideration; not yet committed. |
30-
| 🔴 | Stub requireable but most APIs throw on call. |
31-
|| Unsupported not available; `require()` throws immediately. |
27+
| 🟢 | Supported: native or full implementation. |
28+
| 🔵 | Planned: not yet implemented; on the roadmap. |
29+
| 🟡 | Partial: functional with behavioral gaps or wrapper limitations. |
30+
|| TBD: under consideration; not yet committed. |
31+
| 🔴 | Stub: requireable but most APIs throw on call. |
32+
|| Unsupported: not available; `require()` throws immediately. |
3233

3334
---
3435

@@ -57,8 +58,8 @@ All three CF deployment models share the same `nodejs_compat` API surface. WfP a
5758
| **`worker_threads`** | ⛔ Stubs that throw on API call. | 🔴 Non-functional stub. | Neither platform supports worker threads. |
5859
| **`cluster`** |`require()` throws. | 🔴 Non-functional stub. | Neither platform supports clustering. |
5960
| **`timers`** | 🟢 `setTimeout`, `clearTimeout`, `setInterval`, `clearInterval`, `setImmediate`, `clearImmediate`. | 🟢 Same surface; returns `Timeout` objects. | Equivalent support. |
60-
| **`vm`** | 🔴 Browser polyfill via `Function()`/`eval()`. No real context isolation; shares global scope. | 🔴 Non-functional stub. | Neither offers real `vm` sandboxing. secure-exec polyfill silently runs code in shared scope not safe for isolation. |
61-
| **`v8`** | 🔴 Mock heap stats; `serialize`/`deserialize` use JSON instead of V8 binary format (bug). | 🔴 Non-functional stub. | Neither exposes real V8 internals. secure-exec `v8.serialize` silently produces JSON needs fix to use V8 structured serialization. |
61+
| **`vm`** | 🔴 Browser polyfill via `Function()`/`eval()`. No real context isolation; shares global scope. | 🔴 Non-functional stub. | Neither offers real `vm` sandboxing. secure-exec polyfill silently runs code in shared scope, not safe for isolation. |
62+
| **`v8`** | 🔴 Mock heap stats; `serialize`/`deserialize` use JSON instead of V8 binary format (bug). | 🔴 Non-functional stub. | Neither exposes real V8 internals. secure-exec `v8.serialize` silently produces JSON, needs fix to use V8 structured serialization. |
6263

6364
### Crypto and Security
6465

@@ -117,7 +118,7 @@ All three CF deployment models share the same `nodejs_compat` API surface. WfP a
117118
| **Permission model** | Deny-by-default for `fs`, `network`, `childProcess`, `env`. Fine-grained per-domain policies. | No granular permission model. WfP adds `request.cf` restriction and cache isolation. |
118119
| **Memory limits** | Configurable `memoryLimit` (MB). | 128 MB per Worker (platform-managed). |
119120
| **CPU time limits** | Configurable `cpuTimeLimitMs` with exit code 124. | 10ms (free) / 30s (paid) CPU time; WfP operators can set custom limits. |
120-
| **Timing mitigation** | `freeze` mode (deterministic clocks) or `off` (real-time). | I/O-gated coarsening `Date.now()` and `performance.now()` only advance after I/O to mitigate Spectre-class side channels. |
121+
| **Timing mitigation** | `freeze` mode (deterministic clocks) or `off` (real-time). | I/O-gated coarsening: `Date.now()` and `performance.now()` only advance after I/O to mitigate Spectre-class side channels. |
121122
| **Module loading** | CJS + ESM with package.json `type` field semantics; `node_modules` overlay. | ES modules primary; CJS via `nodejs_compat`; no `node_modules` overlay (bundled at deploy). |
122123
| **Subprocess execution** | Bound to the system driver; subprocess behavior determined by driver implementation. | Not available. |
123124
| **Filesystem** | System-driver-determined: host filesystem (permission-gated) or virtual filesystem, depending on driver implementation. Read-only `/app/node_modules` overlay. | Ephemeral VFS only; Durable Objects for persistence. |

docs/docs.json

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,30 @@
11
{
22
"$schema": "https://mintlify.com/docs.json",
33
"theme": "mint",
4-
"name": "secure-exec",
4+
"appearance": {
5+
"default": "dark",
6+
"strict": true
7+
},
8+
"background": {
9+
"color": {
10+
"dark": "#09090b"
11+
}
12+
},
13+
"name": "Secure Exec",
14+
"logo": {
15+
"light": "/logo.svg",
16+
"dark": "/logo.svg"
17+
},
518
"colors": {
6-
"primary": "#0EA5A4",
7-
"light": "#5EEAD4",
8-
"dark": "#0F766E"
19+
"primary": "#CC0000",
20+
"light": "#FF3333",
21+
"dark": "#CC0000"
922
},
1023
"navigation": {
1124
"groups": [
1225
{
1326
"group": "Getting Started",
14-
"pages": ["quickstart", "api-reference"]
27+
"pages": ["quickstart", "sdk-overview"]
1528
},
1629
{
1730
"group": "Runtimes",
@@ -30,16 +43,28 @@
3043
]
3144
},
3245
{
33-
"group": "Security",
34-
"pages": ["security-model"]
35-
},
36-
{
37-
"group": "Compatibility",
38-
"pages": ["node-compatability"]
46+
"group": "Features",
47+
"pages": [
48+
"features/permissions",
49+
"features/filesystem",
50+
"features/networking",
51+
"features/module-loading",
52+
"features/resource-limits",
53+
"features/typescript",
54+
"features/child-processes",
55+
"features/output-capture"
56+
]
3957
},
4058
{
41-
"group": "Comparisons",
42-
"pages": ["cloudflare-workers-comparison"]
59+
"group": "Reference",
60+
"pages": [
61+
"api-reference",
62+
"architecture",
63+
"security-model",
64+
"nodejs-compatibility",
65+
"python-compatibility",
66+
"cloudflare-workers-comparison"
67+
]
4368
}
4469
]
4570
}

0 commit comments

Comments
 (0)