@secure-exec/typescript 0.0.0-agentos-dylib-base.edaa4a4

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.
package/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # @secure-exec/typescript
2
+
3
+ Run the TypeScript compiler **inside the secure-exec sandbox**. The compiler is
4
+ projected into the VM and every compile and type-check happens in the guest, so
5
+ untrusted TypeScript never executes (or compiles) on the host.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm install @secure-exec/typescript secure-exec
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ import { createTypeScriptTools } from "@secure-exec/typescript";
17
+
18
+ const tools = createTypeScriptTools();
19
+
20
+ // Compile TypeScript to JavaScript inside the sandbox.
21
+ const compiled = await tools.compileSource({
22
+ sourceText: "const answer: number = 42;\nconsole.log(answer);",
23
+ compilerOptions: { module: "ESNext", target: "ES2022" },
24
+ });
25
+ console.log(compiled.outputText);
26
+
27
+ // Type-check inside the sandbox and get structured diagnostics back.
28
+ const checked = await tools.typecheckSource({
29
+ sourceText: `const total: number = "not a number";`,
30
+ });
31
+ console.log(checked.success, checked.diagnostics);
32
+ ```
33
+
34
+ ## API
35
+
36
+ `createTypeScriptTools(options?)` returns:
37
+
38
+ - `compileSource({ sourceText, filePath?, cwd?, configFilePath?, compilerOptions? })`
39
+ -> `{ success, diagnostics, outputText, sourceMapText }`
40
+ - `typecheckSource({ sourceText, ... })` -> `{ success, diagnostics }`
41
+ - `compileProject({ cwd?, configFilePath? })`
42
+ -> `{ success, diagnostics, emitSkipped, emittedFiles }`
43
+ - `typecheckProject({ cwd?, configFilePath? })` -> `{ success, diagnostics }`
44
+
45
+ Each diagnostic is `{ code, category, message, filePath?, line?, column? }`.
46
+
47
+ Seed extra files into the VM with the `files` option, or project host
48
+ directories with the `mounts` option, to compile whole projects.
49
+
50
+ See the [documentation](https://secureexec.dev/docs) for details.
@@ -0,0 +1,97 @@
1
+ /**
2
+ * @secure-exec/typescript — run the TypeScript compiler inside the sandbox.
3
+ *
4
+ * The TypeScript compiler (`typescript.js`) is projected into the VM's virtual
5
+ * filesystem and the compile/type-check program is executed in-guest through
6
+ * the `secure-exec` `NodeRuntime`. The compiler never runs on the host: every
7
+ * `createSourceFile`/`createProgram`/`emit` call happens inside the kernel
8
+ * isolation boundary, over the VM's filesystem.
9
+ */
10
+ import type { HostDirectoryMount, NodeRuntimeCreateOptions } from "secure-exec";
11
+ /** VM permission policy, as accepted by `NodeRuntime.create`. */
12
+ export type Permissions = NonNullable<NodeRuntimeCreateOptions["permissions"]>;
13
+ /** A single TypeScript diagnostic, normalized for host consumption. */
14
+ export interface TypeScriptDiagnostic {
15
+ code: number;
16
+ category: "error" | "warning" | "suggestion" | "message";
17
+ message: string;
18
+ filePath?: string;
19
+ line?: number;
20
+ column?: number;
21
+ }
22
+ /** Result of a type-check (no emit). */
23
+ export interface TypeCheckResult {
24
+ success: boolean;
25
+ diagnostics: TypeScriptDiagnostic[];
26
+ }
27
+ /** Result of compiling a project (emit to the VM filesystem). */
28
+ export interface ProjectCompileResult extends TypeCheckResult {
29
+ emitSkipped: boolean;
30
+ emittedFiles: string[];
31
+ }
32
+ /** Result of compiling a single source string (emit returned in-memory). */
33
+ export interface SourceCompileResult extends TypeCheckResult {
34
+ outputText?: string;
35
+ sourceMapText?: string;
36
+ }
37
+ /** Options for the project-oriented tools. */
38
+ export interface ProjectCompilerOptions {
39
+ /** Working directory inside the VM. Defaults to `/root`. */
40
+ cwd?: string;
41
+ /** Explicit path to a `tsconfig.json` inside the VM. */
42
+ configFilePath?: string;
43
+ }
44
+ /** Options for the single-source tools. */
45
+ export interface SourceCompilerOptions {
46
+ /** TypeScript source text to compile or type-check. */
47
+ sourceText: string;
48
+ /** Virtual path the source should appear at. Defaults to a temp `.ts` file. */
49
+ filePath?: string;
50
+ /** Working directory inside the VM. Defaults to `/root`. */
51
+ cwd?: string;
52
+ /** Optional `tsconfig.json` whose `compilerOptions` are applied. */
53
+ configFilePath?: string;
54
+ /** Inline compiler options (esbuild/tsc JSON spelling). */
55
+ compilerOptions?: Record<string, unknown>;
56
+ }
57
+ /** Options for {@link createTypeScriptTools}. */
58
+ export interface TypeScriptToolsOptions {
59
+ /**
60
+ * Host directory of the `typescript` npm package to project into the VM.
61
+ * Defaults to the `typescript` package resolved from this package. The
62
+ * directory is mounted (read lazily) into the VM; the compiler never runs
63
+ * on the host.
64
+ */
65
+ compilerPackageDir?: string;
66
+ /**
67
+ * Guest path the `typescript` package is mounted at. Defaults to
68
+ * `/root/node_modules/typescript` so it resolves as the `typescript`
69
+ * package inside the VM.
70
+ */
71
+ compilerGuestDir?: string;
72
+ /** Extra files to seed into the VM (e.g. a `tsconfig.json` or sources). */
73
+ files?: Record<string, string | Uint8Array>;
74
+ /** Extra host directories to project into the VM, Docker-style. */
75
+ mounts?: HostDirectoryMount[];
76
+ /** Permission policy forwarded to the VM. */
77
+ permissions?: Permissions;
78
+ /** Environment variables visible to the guest compiler. */
79
+ env?: Record<string, string>;
80
+ }
81
+ /** The in-sandbox TypeScript tools returned by {@link createTypeScriptTools}. */
82
+ export interface TypeScriptTools {
83
+ /** Type-check a `tsconfig.json` project inside the VM. */
84
+ typecheckProject(options?: ProjectCompilerOptions): Promise<TypeCheckResult>;
85
+ /** Compile a `tsconfig.json` project, emitting into the VM filesystem. */
86
+ compileProject(options?: ProjectCompilerOptions): Promise<ProjectCompileResult>;
87
+ /** Type-check a single TypeScript source string inside the VM. */
88
+ typecheckSource(options: SourceCompilerOptions): Promise<TypeCheckResult>;
89
+ /** Compile a single TypeScript source string, returning the emitted JS. */
90
+ compileSource(options: SourceCompilerOptions): Promise<SourceCompileResult>;
91
+ }
92
+ /**
93
+ * Create a set of TypeScript tools whose compiler runs entirely inside the
94
+ * secure-exec sandbox. The compiler bundle is read from the host and projected
95
+ * into the VM filesystem; all compilation happens in-guest.
96
+ */
97
+ export declare function createTypeScriptTools(options?: TypeScriptToolsOptions): TypeScriptTools;
package/dist/index.js ADDED
@@ -0,0 +1,314 @@
1
+ /**
2
+ * @secure-exec/typescript — run the TypeScript compiler inside the sandbox.
3
+ *
4
+ * The TypeScript compiler (`typescript.js`) is projected into the VM's virtual
5
+ * filesystem and the compile/type-check program is executed in-guest through
6
+ * the `secure-exec` `NodeRuntime`. The compiler never runs on the host: every
7
+ * `createSourceFile`/`createProgram`/`emit` call happens inside the kernel
8
+ * isolation boundary, over the VM's filesystem.
9
+ */
10
+ import { createRequire } from "node:module";
11
+ import { dirname } from "node:path";
12
+ import { NodeRuntime } from "secure-exec";
13
+ const DEFAULT_COMPILER_GUEST_DIR = "/root/node_modules/typescript";
14
+ function resolveCompilerPackageDir(explicit) {
15
+ if (explicit) {
16
+ return explicit;
17
+ }
18
+ const require = createRequire(import.meta.url);
19
+ // `lib/typescript.js` is the full compiler bundle; its grandparent is the
20
+ // `typescript` package directory (containing package.json + lib/).
21
+ return dirname(dirname(require.resolve("typescript/lib/typescript.js")));
22
+ }
23
+ /**
24
+ * Create a set of TypeScript tools whose compiler runs entirely inside the
25
+ * secure-exec sandbox. The compiler bundle is read from the host and projected
26
+ * into the VM filesystem; all compilation happens in-guest.
27
+ */
28
+ export function createTypeScriptTools(options = {}) {
29
+ const compilerPackageDir = resolveCompilerPackageDir(options.compilerPackageDir);
30
+ const compilerGuestDir = options.compilerGuestDir ?? DEFAULT_COMPILER_GUEST_DIR;
31
+ const compilerGuestPath = `${compilerGuestDir}/lib/typescript.js`;
32
+ const compilerMount = {
33
+ guestPath: compilerGuestDir,
34
+ hostPath: compilerPackageDir,
35
+ readOnly: true,
36
+ };
37
+ const run = (request) => runCompilerRequest(request, compilerGuestPath, options, compilerMount);
38
+ return {
39
+ typecheckProject: (requestOptions = {}) => run({
40
+ kind: "typecheckProject",
41
+ options: requestOptions,
42
+ }),
43
+ compileProject: (requestOptions = {}) => run({
44
+ kind: "compileProject",
45
+ options: requestOptions,
46
+ }),
47
+ typecheckSource: (requestOptions) => run({ kind: "typecheckSource", options: requestOptions }),
48
+ compileSource: (requestOptions) => run({
49
+ kind: "compileSource",
50
+ options: requestOptions,
51
+ }),
52
+ };
53
+ }
54
+ async function runCompilerRequest(request, compilerGuestPath, toolsOptions, compilerMount) {
55
+ const createOptions = {
56
+ files: toolsOptions.files,
57
+ mounts: [compilerMount, ...(toolsOptions.mounts ?? [])],
58
+ permissions: toolsOptions.permissions,
59
+ env: toolsOptions.env,
60
+ };
61
+ const rt = await NodeRuntime.create(createOptions);
62
+ try {
63
+ const guestSource = buildCompilerGuestSource(request, compilerGuestPath);
64
+ const result = await rt.run(guestSource);
65
+ if (result.exitCode === 0 && result.value !== undefined) {
66
+ return result.value;
67
+ }
68
+ return createFailureResult(request.kind, result.stderr.trim() || `compiler exited with code ${result.exitCode}`);
69
+ }
70
+ catch (error) {
71
+ const message = error instanceof Error ? error.message : String(error);
72
+ return createFailureResult(request.kind, message);
73
+ }
74
+ finally {
75
+ await rt.dispose();
76
+ }
77
+ }
78
+ function createFailureResult(kind, errorMessage) {
79
+ const diagnostic = {
80
+ code: 0,
81
+ category: "error",
82
+ message: errorMessage || "TypeScript compiler failed",
83
+ };
84
+ if (kind === "compileProject") {
85
+ return {
86
+ success: false,
87
+ diagnostics: [diagnostic],
88
+ emitSkipped: true,
89
+ emittedFiles: [],
90
+ };
91
+ }
92
+ return { success: false, diagnostics: [diagnostic] };
93
+ }
94
+ /**
95
+ * Build the guest ES module that loads the projected compiler and runs the
96
+ * requested compile/type-check entirely inside the VM, then hands the result
97
+ * back to the host via `__return`.
98
+ */
99
+ function buildCompilerGuestSource(request, compilerGuestPath) {
100
+ return [
101
+ `import { createRequire } from "node:module";`,
102
+ `import fs from "node:fs";`,
103
+ `import path from "node:path";`,
104
+ `const require = createRequire(${JSON.stringify(compilerGuestPath)});`,
105
+ `const ts = require(${JSON.stringify(compilerGuestPath)});`,
106
+ `const request = ${JSON.stringify(request)};`,
107
+ `const result = (${compilerGuestMain.toString()})(ts, fs, path, request);`,
108
+ `globalThis.__return(result);`,
109
+ ].join("\n");
110
+ }
111
+ // NOTE: This function is serialized with `.toString()` and executed INSIDE the
112
+ // VM. It must be self-contained: it may only reference its parameters and the
113
+ // in-guest globals. Do not capture host-side variables.
114
+ function compilerGuestMain(ts, fs, path, request) {
115
+ function toDiagnostic(diagnostic) {
116
+ const message = ts
117
+ .flattenDiagnosticMessageText(diagnostic.messageText, "\n")
118
+ .trim();
119
+ const result = {
120
+ code: diagnostic.code,
121
+ category: toDiagnosticCategory(diagnostic.category),
122
+ message,
123
+ };
124
+ if (!diagnostic.file || diagnostic.start === undefined) {
125
+ return result;
126
+ }
127
+ const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
128
+ result.filePath = diagnostic.file.fileName.replace(/\\/g, "/");
129
+ result.line = line + 1;
130
+ result.column = character + 1;
131
+ return result;
132
+ }
133
+ function toDiagnosticCategory(category) {
134
+ switch (category) {
135
+ case ts.DiagnosticCategory.Warning:
136
+ return "warning";
137
+ case ts.DiagnosticCategory.Suggestion:
138
+ return "suggestion";
139
+ case ts.DiagnosticCategory.Message:
140
+ return "message";
141
+ default:
142
+ return "error";
143
+ }
144
+ }
145
+ function hasErrors(diagnostics) {
146
+ return diagnostics.some((diagnostic) => diagnostic.category === "error");
147
+ }
148
+ function convertCompilerOptions(compilerOptions, basePath) {
149
+ if (!compilerOptions) {
150
+ return {};
151
+ }
152
+ const converted = ts.convertCompilerOptionsFromJson(compilerOptions, basePath);
153
+ if (converted.errors.length > 0) {
154
+ throw new Error(converted.errors
155
+ .map((diagnostic) => toDiagnostic(diagnostic).message)
156
+ .join("\n"));
157
+ }
158
+ return converted.options;
159
+ }
160
+ function resolveProjectConfig(options, overrideCompilerOptions = {}) {
161
+ const cwd = path.resolve(options.cwd ?? "/root");
162
+ const configFilePath = options.configFilePath
163
+ ? path.resolve(cwd, options.configFilePath)
164
+ : ts.findConfigFile(cwd, ts.sys.fileExists, "tsconfig.json");
165
+ if (!configFilePath) {
166
+ throw new Error(`Unable to find tsconfig.json from '${cwd}'`);
167
+ }
168
+ const configFile = ts.readConfigFile(configFilePath, ts.sys.readFile);
169
+ if (configFile.error) {
170
+ return { parsed: null, diagnostics: [toDiagnostic(configFile.error)] };
171
+ }
172
+ const parsed = ts.parseJsonConfigFileContent(configFile.config, ts.sys, path.dirname(configFilePath), overrideCompilerOptions, configFilePath);
173
+ return { parsed, diagnostics: parsed.errors.map(toDiagnostic) };
174
+ }
175
+ function createSourceProgram(options, overrideCompilerOptions = {}) {
176
+ const cwd = path.resolve(options.cwd ?? "/root");
177
+ const filePath = path.resolve(cwd, options.filePath ?? "__secure_exec_typescript_input__.ts");
178
+ const projectCompilerOptions = options.configFilePath
179
+ ? resolveProjectConfig({ cwd, configFilePath: options.configFilePath }, overrideCompilerOptions)
180
+ : { parsed: null, diagnostics: [] };
181
+ if (projectCompilerOptions.diagnostics.length > 0) {
182
+ return {
183
+ filePath,
184
+ program: null,
185
+ diagnostics: projectCompilerOptions.diagnostics,
186
+ };
187
+ }
188
+ const compilerOptions = {
189
+ target: ts.ScriptTarget.ES2022,
190
+ module: ts.ModuleKind.ESNext,
191
+ ...projectCompilerOptions.parsed?.options,
192
+ ...convertCompilerOptions(options.compilerOptions, cwd),
193
+ ...overrideCompilerOptions,
194
+ };
195
+ const host = ts.createCompilerHost(compilerOptions);
196
+ const normalize = (candidate) => ts.sys.useCaseSensitiveFileNames ? candidate : candidate.toLowerCase();
197
+ const normalizedFilePath = normalize(filePath);
198
+ const defaultGetSourceFile = host.getSourceFile.bind(host);
199
+ const defaultReadFile = host.readFile.bind(host);
200
+ const defaultFileExists = host.fileExists.bind(host);
201
+ host.fileExists = (candidate) => normalize(candidate) === normalizedFilePath ||
202
+ defaultFileExists(candidate);
203
+ host.readFile = (candidate) => normalize(candidate) === normalizedFilePath
204
+ ? options.sourceText
205
+ : defaultReadFile(candidate);
206
+ host.getSourceFile = (candidate, languageVersion, onError, shouldCreate) => normalize(candidate) === normalizedFilePath
207
+ ? ts.createSourceFile(candidate, options.sourceText, languageVersion, true)
208
+ : defaultGetSourceFile(candidate, languageVersion, onError, shouldCreate);
209
+ return {
210
+ filePath,
211
+ program: ts.createProgram([filePath], compilerOptions, host),
212
+ diagnostics: [],
213
+ };
214
+ }
215
+ switch (request.kind) {
216
+ case "typecheckProject": {
217
+ const { parsed, diagnostics } = resolveProjectConfig(request.options, {
218
+ noEmit: true,
219
+ });
220
+ if (!parsed) {
221
+ return { success: false, diagnostics };
222
+ }
223
+ const program = ts.createProgram({
224
+ rootNames: parsed.fileNames,
225
+ options: parsed.options,
226
+ projectReferences: parsed.projectReferences,
227
+ });
228
+ const combined = ts
229
+ .sortAndDeduplicateDiagnostics([
230
+ ...parsed.errors,
231
+ ...ts.getPreEmitDiagnostics(program),
232
+ ])
233
+ .map(toDiagnostic);
234
+ return { success: !hasErrors(combined), diagnostics: combined };
235
+ }
236
+ case "compileProject": {
237
+ const { parsed, diagnostics } = resolveProjectConfig(request.options);
238
+ if (!parsed) {
239
+ return {
240
+ success: false,
241
+ diagnostics,
242
+ emitSkipped: true,
243
+ emittedFiles: [],
244
+ };
245
+ }
246
+ const program = ts.createProgram({
247
+ rootNames: parsed.fileNames,
248
+ options: parsed.options,
249
+ projectReferences: parsed.projectReferences,
250
+ });
251
+ const emittedFiles = [];
252
+ const emitResult = program.emit(undefined, (fileName, text) => {
253
+ fs.mkdirSync(path.dirname(fileName), { recursive: true });
254
+ fs.writeFileSync(fileName, text, "utf8");
255
+ emittedFiles.push(fileName.replace(/\\/g, "/"));
256
+ });
257
+ const combined = ts
258
+ .sortAndDeduplicateDiagnostics([
259
+ ...parsed.errors,
260
+ ...ts.getPreEmitDiagnostics(program),
261
+ ...emitResult.diagnostics,
262
+ ])
263
+ .map(toDiagnostic);
264
+ return {
265
+ success: !hasErrors(combined),
266
+ diagnostics: combined,
267
+ emitSkipped: emitResult.emitSkipped,
268
+ emittedFiles,
269
+ };
270
+ }
271
+ case "typecheckSource": {
272
+ const { program, diagnostics } = createSourceProgram(request.options, {
273
+ noEmit: true,
274
+ });
275
+ if (!program) {
276
+ return { success: false, diagnostics };
277
+ }
278
+ const combined = ts
279
+ .sortAndDeduplicateDiagnostics(ts.getPreEmitDiagnostics(program))
280
+ .map(toDiagnostic);
281
+ return { success: !hasErrors(combined), diagnostics: combined };
282
+ }
283
+ case "compileSource": {
284
+ const { program, diagnostics } = createSourceProgram(request.options);
285
+ if (!program) {
286
+ return { success: false, diagnostics };
287
+ }
288
+ let outputText;
289
+ let sourceMapText;
290
+ const emitResult = program.emit(undefined, (fileName, text) => {
291
+ if (fileName.endsWith(".js") ||
292
+ fileName.endsWith(".mjs") ||
293
+ fileName.endsWith(".cjs")) {
294
+ outputText = text;
295
+ }
296
+ else if (fileName.endsWith(".map")) {
297
+ sourceMapText = text;
298
+ }
299
+ });
300
+ const combined = ts
301
+ .sortAndDeduplicateDiagnostics([
302
+ ...ts.getPreEmitDiagnostics(program),
303
+ ...emitResult.diagnostics,
304
+ ])
305
+ .map(toDiagnostic);
306
+ return {
307
+ success: !hasErrors(combined),
308
+ diagnostics: combined,
309
+ outputText,
310
+ sourceMapText,
311
+ };
312
+ }
313
+ }
314
+ }
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@secure-exec/typescript",
3
+ "version": "0.0.0-agentos-dylib-base.edaa4a4",
4
+ "type": "module",
5
+ "license": "Apache-2.0",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "README.md"
11
+ ],
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/rivet-dev/secure-exec.git",
15
+ "directory": "packages/typescript"
16
+ },
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js",
21
+ "default": "./dist/index.js"
22
+ }
23
+ },
24
+ "scripts": {
25
+ "build": "tsc",
26
+ "check-types": "tsc --noEmit"
27
+ },
28
+ "dependencies": {
29
+ "@secure-exec/core": "0.0.0-agentos-dylib-base.edaa4a4",
30
+ "secure-exec": "0.0.0-agentos-dylib-base.edaa4a4",
31
+ "typescript": "^5.9.3"
32
+ },
33
+ "devDependencies": {
34
+ "@types/node": "^22.10.2"
35
+ }
36
+ }