Skip to main content
NodeRuntime runs JavaScript code in an isolated V8 isolate. The sandbox supports memory limits, CPU time budgets, and timing side-channel mitigation.

Creating a runtime

A NodeRuntime requires a system driver and a runtime driver factory.
import {
  NodeRuntime,
  createNodeDriver,
  createNodeRuntimeDriverFactory,
} from "@secure-exec/node";

const runtime = new NodeRuntime({
  systemDriver: createNodeDriver(),
  runtimeDriverFactory: createNodeRuntimeDriverFactory(),
});
These exports are also available from "secure-exec" for backward compatibility.

exec vs run

Use exec() when you care about side effects (logging, file writes) but don’t need a return value.
const result = await runtime.exec("const label = 'done'; console.log(label)");
console.log(result.code); // 0
Use run() when you need a value back. The sandboxed code should use export default.
const result = await runtime.run<{ default: number }>("export default 40 + 2");
console.log(result.exports?.default); // 42

Capturing output

Console output is not buffered into the result. Use the onStdio hook to capture it.
const logs: string[] = [];
await runtime.exec("console.log('hello'); console.error('oops')", {
  onStdio: (event) => logs.push(`[${event.channel}] ${event.message}`),
});
// logs: ["[stdout] hello", "[stderr] oops"]

TypeScript workflows

NodeRuntime executes JavaScript only. For sandboxed TypeScript type checking or compilation, use the separate @secure-exec/typescript package. See TypeScript support.

Resource limits

You can cap memory and CPU time to prevent runaway code from exhausting host resources.
const runtime = new NodeRuntime({
  systemDriver: createNodeDriver(),
  runtimeDriverFactory: createNodeRuntimeDriverFactory(),
  memoryLimit: 128,        // 128 MB isolate memory cap
  cpuTimeLimitMs: 5000,    // 5 second CPU budget
  timingMitigation: "freeze", // freeze high-resolution timers (default)
});

Module loading

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.

TypeScript support

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. Shared setup:
import { createInMemoryFileSystem } from "@secure-exec/core";
import {
  createNodeDriver,
  createNodeRuntimeDriverFactory,
} from "@secure-exec/node";
import { createTypeScriptTools } from "@secure-exec/typescript";

const filesystem = createInMemoryFileSystem();
const systemDriver = createNodeDriver({ filesystem });
const runtimeDriverFactory = createNodeRuntimeDriverFactory();
const ts = createTypeScriptTools({
  systemDriver,
  runtimeDriverFactory,
});

Type check source

Use typecheckSource(...) when you want to quickly validate AI-generated code before running it:
const result = await ts.typecheckSource({
  filePath: "/root/snippet.ts",
  sourceText: `
    export function greet(name: string) {
      return "hello " + missingValue;
    }
  `,
});

console.log(result.success); // false
console.log(result.diagnostics[0]?.message);

Type check project

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.
await filesystem.mkdir("/root/src");
await filesystem.writeFile(
  "/root/tsconfig.json",
  JSON.stringify({
    compilerOptions: {
      module: "nodenext",
      moduleResolution: "nodenext",
      target: "es2022",
    },
    include: ["src/**/*.ts"],
  }),
);
await filesystem.writeFile(
  "/root/src/index.ts",
  "export const value: string = 123;\n",
);

const result = await ts.typecheckProject({ cwd: "/root" });

console.log(result.success); // false
console.log(result.diagnostics[0]?.message);

Compile source

Use compileSource(...) when you want to transpile one TypeScript source string and then hand the emitted JavaScript to NodeRuntime:
import { NodeRuntime } from "@secure-exec/node";

const compiled = await ts.compileSource({
  filePath: "/root/example.ts",
  sourceText: "export default 40 + 2;",
  compilerOptions: {
    module: "esnext",
    target: "es2022",
  },
});

const runtime = new NodeRuntime({
  systemDriver,
  runtimeDriverFactory,
});

const execution = await runtime.run<{ default: number }>(
  compiled.outputText ?? "",
  "/root/example.js",
);

console.log(execution.exports?.default); // 42

Compile project

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.
import {
  NodeRuntime,
} from "secure-exec";
await filesystem.mkdir("/root/src");
await filesystem.writeFile(
  "/root/tsconfig.json",
  JSON.stringify({
    compilerOptions: {
      module: "commonjs",
      target: "es2022",
      outDir: "/root/dist",
    },
    include: ["src/**/*.ts"],
  }),
);
await filesystem.writeFile(
  "/root/src/index.ts",
  "export const answer: number = 42;\n",
);

await ts.compileProject({ cwd: "/root" });

const runtime = new NodeRuntime({
  systemDriver,
  runtimeDriverFactory,
});

const execution = await runtime.run<{ answer: number }>(
  "module.exports = require('./dist/index.js');",
  "/root/entry.js",
);

console.log(execution.exports); // { answer: 42 }