Skip to content

Commit 3e4dfec

Browse files
authored
Merge pull request #5 from rivet-dev/node-ts-typecheck
Extract TypeScript compiler to companion @secure-exec/typescript package
2 parents fbf126c + 2093232 commit 3e4dfec

File tree

30 files changed

+1364
-38
lines changed

30 files changed

+1364
-38
lines changed

docs-internal/arch/overview.md

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

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

99
## NodeRuntime / PythonRuntime
@@ -20,6 +20,16 @@ Public APIs. Thin facades that delegate orchestration to runtime drivers.
2020
- `systemDriver` for runtime capabilities/config
2121
- runtime-driver factory for runtime-driver construction
2222

23+
## TypeScript Tools
24+
25+
`packages/secure-exec-typescript/src/index.ts`
26+
27+
Optional companion package for sandboxed TypeScript compiler work (`@secure-exec/typescript`).
28+
29+
- `createTypeScriptTools(...)` — build project/source compile and typecheck helpers
30+
- Uses a dedicated `NodeRuntime` compiler sandbox per request
31+
- Keeps TypeScript compiler execution out of the core runtime path
32+
2333
## SystemDriver
2434

2535
`src/runtime-driver.ts` (re-exported from `src/types.ts`)

docs-internal/friction.md

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

3+
## 2026-03-10
4+
5+
1. **[resolved]** TypeScript compilation needed sandboxing without baking compiler behavior into the core runtime.
6+
- Symptom: keeping TypeScript handling inside `NodeRuntime` blurred the runtime contract, risked host-memory pressure during compilation, and forced browser/runtime surfaces to inherit TypeScript-specific policy.
7+
- Fix: core `secure-exec` now stays JavaScript-only, while sandboxed TypeScript typecheck/compile flows move to the separate `@secure-exec/typescript` package.
8+
- Compatibility trade-off: callers that want TypeScript must perform an explicit compile/typecheck step before executing emitted JavaScript in the runtime.
9+
310
## 2026-03-09
411

512
1. **[resolved]** Python `exec()` env overrides bypassed `permissions.env`.

docs/api-reference.mdx

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,44 @@ new NodeRuntime(options: NodeRuntimeOptions)
3030
| Method | Returns | Description |
3131
|---|---|---|
3232
| `exec(code, options?)` | `Promise<ExecResult>` | Execute code without a return value. |
33-
| `run<T>(code, filePath?)` | `Promise<RunResult<T>>` | Execute code and return the default export. |
33+
| `run<T>(code, filePath?)` | `Promise<RunResult<T>>` | Execute code and return the module namespace/default export object. |
3434
| `network` | `{ fetch, dnsLookup, httpRequest }` | Network access from the host side. |
3535
| `dispose()` | `void` | Release resources synchronously. |
3636
| `terminate()` | `Promise<void>` | Terminate the runtime. |
3737

3838
---
3939

40+
## TypeScript Tools (`@secure-exec/typescript`)
41+
42+
### `createTypeScriptTools(options)`
43+
44+
Creates sandboxed TypeScript project/source helpers backed by a dedicated compiler runtime.
45+
46+
```ts
47+
createTypeScriptTools(options: TypeScriptToolsOptions)
48+
```
49+
50+
**`TypeScriptToolsOptions`**
51+
52+
| Option | Type | Description |
53+
|---|---|---|
54+
| `systemDriver` | `SystemDriver` | Compiler runtime capabilities and filesystem view. |
55+
| `runtimeDriverFactory` | `NodeRuntimeDriverFactory` | Creates the compiler sandbox runtime. |
56+
| `memoryLimit` | `number` | Compiler sandbox isolate memory cap in MB. Default `512`. |
57+
| `cpuTimeLimitMs` | `number` | Compiler sandbox CPU time budget in ms. |
58+
| `compilerSpecifier` | `string` | Module specifier used to load the TypeScript compiler. Default `"typescript"`. |
59+
60+
**Methods**
61+
62+
| Method | Returns | Description |
63+
|---|---|---|
64+
| `typecheckProject(options?)` | `Promise<TypeCheckResult>` | Type-check a filesystem-backed TypeScript project using `tsconfig` discovery or `configFilePath`. |
65+
| `compileProject(options?)` | `Promise<ProjectCompileResult>` | Compile a filesystem-backed project and write emitted files through the configured filesystem like `tsc`. |
66+
| `typecheckSource(options)` | `Promise<TypeCheckResult>` | Type-check one TypeScript source string without mutating the filesystem. |
67+
| `compileSource(options)` | `Promise<SourceCompileResult>` | Compile one TypeScript source string and return JavaScript text. |
68+
69+
---
70+
4071
### `PythonRuntime`
4172

4273
Sandboxed Python runtime using Pyodide in a Worker thread.
@@ -286,6 +317,71 @@ Extends `ExecOptions` with:
286317
type StdioHook = (event: { channel: "stdout" | "stderr"; message: string }) => void;
287318
```
288319

320+
### `ProjectCompilerOptions`
321+
322+
```ts
323+
{
324+
cwd?: string;
325+
configFilePath?: string;
326+
}
327+
```
328+
329+
### `SourceCompilerOptions`
330+
331+
```ts
332+
{
333+
sourceText: string;
334+
filePath?: string;
335+
cwd?: string;
336+
configFilePath?: string;
337+
compilerOptions?: Record<string, unknown>;
338+
}
339+
```
340+
341+
### `TypeCheckResult`
342+
343+
```ts
344+
{
345+
success: boolean;
346+
diagnostics: TypeScriptDiagnostic[];
347+
}
348+
```
349+
350+
### `ProjectCompileResult`
351+
352+
```ts
353+
{
354+
success: boolean;
355+
diagnostics: TypeScriptDiagnostic[];
356+
emitSkipped: boolean;
357+
emittedFiles: string[];
358+
}
359+
```
360+
361+
### `SourceCompileResult`
362+
363+
```ts
364+
{
365+
success: boolean;
366+
diagnostics: TypeScriptDiagnostic[];
367+
outputText?: string;
368+
sourceMapText?: string;
369+
}
370+
```
371+
372+
### `TypeScriptDiagnostic`
373+
374+
```ts
375+
{
376+
code: number;
377+
category: "error" | "warning" | "suggestion" | "message";
378+
message: string;
379+
filePath?: string;
380+
line?: number;
381+
column?: number;
382+
}
383+
```
384+
289385
---
290386

291387
## Configuration Types

docs/node-compatability.mdx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ Unsupported modules use: `"<module> is not supported in sandbox"`.
6060
- By default, secure-exec drops console emissions instead of buffering runtime-managed output.
6161
- Consumers that need logs should use the explicit `onStdio` hook to stream `stdout`/`stderr` events in emission order.
6262

63+
## TypeScript Workflows
64+
65+
- Core `secure-exec` runtimes execute JavaScript only.
66+
- Sandboxed TypeScript type checking and compilation belong in the separate `@secure-exec/typescript` package.
67+
6368
## Node-Modules Overlay Behavior
6469

6570
- Node runtime composes a read-only `/app/node_modules` overlay from `<cwd>/node_modules` (default `cwd` is host `process.cwd()`, configurable via `moduleAccess.cwd`).

docs/quickstart.mdx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ description: Get secure-exec running in a few minutes.
99
pnpm add secure-exec
1010
```
1111

12+
Optional TypeScript tooling:
13+
14+
```bash
15+
pnpm add @secure-exec/typescript
16+
```
17+
1218
## Create a runtime
1319

1420
A runtime needs two things: a **system driver** that provides host capabilities (filesystem, network, permissions) and a **runtime driver** that manages the sandboxed execution environment.
@@ -51,7 +57,7 @@ Use `exec()` to run code in the sandbox. Console output is streamed through the
5157
```ts
5258
const logs: string[] = [];
5359

54-
const result = await runtime.exec("console.log('hello from secure-exec')", {
60+
const result = await runtime.exec("const value = 'hello from secure-exec'; console.log(value)", {
5561
onStdio: (event) => logs.push(`[${event.channel}] ${event.message}`),
5662
});
5763

@@ -64,10 +70,12 @@ console.log(logs.join("\n"));
6470
Use `run()` to execute code and get an exported value back.
6571

6672
```ts
67-
const result = await runtime.run<number>("export default 2 + 2");
68-
console.log(result.exports); // 4
73+
const result = await runtime.run<{ default: number }>("export default 2 + 2");
74+
console.log(result.exports?.default); // 4
6975
```
7076

77+
If you want sandboxed TypeScript type checking or compilation before execution, use the separate `@secure-exec/typescript` package and pass its emitted JavaScript into `secure-exec`.
78+
7179
## Clean up
7280

7381
Dispose the runtime when you're done to free resources.

docs/runtimes/node.mdx

Lines changed: 146 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ title: Node Runtime
33
description: Sandboxed JavaScript execution in a V8 isolate.
44
---
55

6-
`NodeRuntime` runs JavaScript in an isolated V8 isolate. It supports memory limits, CPU time budgets, and timing side-channel mitigation. The sandbox provides Node.js-compatible APIs through a bridge layer.
6+
`NodeRuntime` runs JavaScript code in an isolated V8 isolate. The sandbox supports memory limits, CPU time budgets, and timing side-channel mitigation.
77

88
## Creating a runtime
99

@@ -27,15 +27,15 @@ const runtime = new NodeRuntime({
2727
Use `exec()` when you care about side effects (logging, file writes) but don't need a return value.
2828

2929
```ts
30-
const result = await runtime.exec("console.log('done')");
30+
const result = await runtime.exec("const label = 'done'; console.log(label)");
3131
console.log(result.code); // 0
3232
```
3333

3434
Use `run()` when you need a value back. The sandboxed code should use `export default`.
3535

3636
```ts
37-
const result = await runtime.run<number>("export default 40 + 2");
38-
console.log(result.exports); // 42
37+
const result = await runtime.run<{ default: number }>("export default 40 + 2");
38+
console.log(result.exports?.default); // 42
3939
```
4040

4141
## Capturing output
@@ -50,6 +50,10 @@ await runtime.exec("console.log('hello'); console.error('oops')", {
5050
// logs: ["[stdout] hello", "[stderr] oops"]
5151
```
5252

53+
## TypeScript workflows
54+
55+
`NodeRuntime` executes JavaScript only. For sandboxed TypeScript type checking or compilation, use the separate `@secure-exec/typescript` package. See [TypeScript support](#typescript-support).
56+
5357
## Resource limits
5458

5559
You can cap memory and CPU time to prevent runaway code from exhausting host resources.
@@ -67,3 +71,141 @@ const runtime = new NodeRuntime({
6771
## Module loading
6872

6973
The sandbox provides a read-only overlay of the host's `node_modules` at `/app/node_modules`. Sandboxed code can `import` or `require()` packages installed on the host without any additional configuration.
74+
75+
## TypeScript support
76+
77+
TypeScript support lives in the companion `@secure-exec/typescript` package. It runs the TypeScript compiler inside a dedicated sandbox so untrusted inputs cannot consume host CPU or memory during type checking or compilation.
78+
79+
Shared setup:
80+
81+
```ts
82+
import {
83+
createInMemoryFileSystem,
84+
createNodeDriver,
85+
createNodeRuntimeDriverFactory,
86+
} from "secure-exec";
87+
import { createTypeScriptTools } from "@secure-exec/typescript";
88+
89+
const filesystem = createInMemoryFileSystem();
90+
const systemDriver = createNodeDriver({ filesystem });
91+
const runtimeDriverFactory = createNodeRuntimeDriverFactory();
92+
const ts = createTypeScriptTools({
93+
systemDriver,
94+
runtimeDriverFactory,
95+
});
96+
```
97+
98+
### Type check source
99+
100+
Use `typecheckSource(...)` when you want to quickly validate AI-generated code before running it:
101+
102+
```ts
103+
const result = await ts.typecheckSource({
104+
filePath: "/root/snippet.ts",
105+
sourceText: `
106+
export function greet(name: string) {
107+
return "hello " + missingValue;
108+
}
109+
`,
110+
});
111+
112+
console.log(result.success); // false
113+
console.log(result.diagnostics[0]?.message);
114+
```
115+
116+
### Type check project
117+
118+
Use `typecheckProject(...)` when you want to validate a full TypeScript project from the filesystem exposed by the system driver. That means `tsconfig.json`, source files, and any other project inputs are read from that sandbox filesystem view.
119+
120+
```ts
121+
await filesystem.mkdir("/root/src");
122+
await filesystem.writeFile(
123+
"/root/tsconfig.json",
124+
JSON.stringify({
125+
compilerOptions: {
126+
module: "nodenext",
127+
moduleResolution: "nodenext",
128+
target: "es2022",
129+
},
130+
include: ["src/**/*.ts"],
131+
}),
132+
);
133+
await filesystem.writeFile(
134+
"/root/src/index.ts",
135+
"export const value: string = 123;\n",
136+
);
137+
138+
const result = await ts.typecheckProject({ cwd: "/root" });
139+
140+
console.log(result.success); // false
141+
console.log(result.diagnostics[0]?.message);
142+
```
143+
144+
### Compile source
145+
146+
Use `compileSource(...)` when you want to transpile one TypeScript source string and then hand the emitted JavaScript to `NodeRuntime`:
147+
148+
```ts
149+
import { NodeRuntime } from "secure-exec";
150+
151+
const compiled = await ts.compileSource({
152+
filePath: "/root/example.ts",
153+
sourceText: "export default 40 + 2;",
154+
compilerOptions: {
155+
module: "esnext",
156+
target: "es2022",
157+
},
158+
});
159+
160+
const runtime = new NodeRuntime({
161+
systemDriver,
162+
runtimeDriverFactory,
163+
});
164+
165+
const execution = await runtime.run<{ default: number }>(
166+
compiled.outputText ?? "",
167+
"/root/example.js",
168+
);
169+
170+
console.log(execution.exports?.default); // 42
171+
```
172+
173+
### Compile project
174+
175+
Use `compileProject(...)` when you want TypeScript to read a real project from the filesystem exposed by the system driver and write emitted files back into that same sandbox filesystem view.
176+
177+
```ts
178+
import {
179+
NodeRuntime,
180+
} from "secure-exec";
181+
await filesystem.mkdir("/root/src");
182+
await filesystem.writeFile(
183+
"/root/tsconfig.json",
184+
JSON.stringify({
185+
compilerOptions: {
186+
module: "commonjs",
187+
target: "es2022",
188+
outDir: "/root/dist",
189+
},
190+
include: ["src/**/*.ts"],
191+
}),
192+
);
193+
await filesystem.writeFile(
194+
"/root/src/index.ts",
195+
"export const answer: number = 42;\n",
196+
);
197+
198+
await ts.compileProject({ cwd: "/root" });
199+
200+
const runtime = new NodeRuntime({
201+
systemDriver,
202+
runtimeDriverFactory,
203+
});
204+
205+
const execution = await runtime.run<{ answer: number }>(
206+
"module.exports = require('./dist/index.js');",
207+
"/root/entry.js",
208+
);
209+
210+
console.log(execution.exports); // { answer: 42 }
211+
```
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-10

0 commit comments

Comments
 (0)