Skip to main content
The Node system driver provides sandboxed code with access to the host filesystem, networking, child processes, and environment. All capabilities sit behind a permission layer.

Basic setup

With no options, the driver provides a filesystem with a read-only node_modules overlay and no network or child process access.
import { createNodeDriver } from "secure-exec";

const driver = createNodeDriver();

Configuring capabilities

Pass options to enable and configure specific host capabilities.
import {
  createNodeDriver,
  createDefaultNetworkAdapter,
  allowAllFs,
  allowAllNetwork,
} from "secure-exec";

const driver = createNodeDriver({
  useDefaultNetwork: true,
  permissions: {
    fs: allowAllFs,
    network: allowAllNetwork,
  },
  processConfig: {
    cwd: "/app",
    env: { NODE_ENV: "production" },
  },
});

All options

OptionTypeDescription
filesystemVirtualFileSystemCustom filesystem implementation. Falls back to the built-in ModuleAccessFileSystem.
moduleAccessModuleAccessOptionsConfigure the node_modules overlay (see Module access).
networkAdapterNetworkAdapterCustom network adapter.
commandExecutorCommandExecutorCustom command executor for child processes (see Child processes).
permissionsPermissionsPermission callbacks for fs, network, child process, and env access.
useDefaultNetworkbooleanUse the built-in network adapter (fetch, DNS, HTTP client, loopback HTTP server).
processConfigProcessConfigValues for process.cwd(), process.env, etc. inside the sandbox.
osConfigOSConfigValues for os.platform(), os.arch(), etc. inside the sandbox.

Permissions

Permissions are deny-by-default. Each capability (filesystem, network, child process, env) is controlled by a function that receives a request object and returns a PermissionDecision.

Function-based permissions

Each permission callback receives a typed request and returns { allow: boolean, reason?: string }.
import type {
  FsAccessRequest,
  NetworkAccessRequest,
  ChildProcessAccessRequest,
  EnvAccessRequest,
  PermissionDecision,
} from "secure-exec";

const driver = createNodeDriver({
  permissions: {
    fs: (request: FsAccessRequest): PermissionDecision => {
      if (request.path.startsWith("/tmp")) return { allow: true };
      return { allow: false, reason: "Only /tmp is writable" };
    },
    network: (request: NetworkAccessRequest): PermissionDecision => {
      if (request.hostname === "api.example.com") return { allow: true };
      return { allow: false };
    },
    childProcess: (request: ChildProcessAccessRequest): PermissionDecision => {
      if (request.command === "ls") return { allow: true };
      return { allow: false, reason: `Blocked command: ${request.command}` };
    },
    env: (request: EnvAccessRequest): PermissionDecision => {
      if (["NODE_ENV", "PATH"].includes(request.key)) return { allow: true };
      return { allow: false };
    },
  },
});

Request types

PermissionRequest fields
fsop ("read", "write", "mkdir", "stat", "rm", "rename", …), path
networkop ("fetch", "http", "dns", "listen"), url?, method?, hostname?
childProcesscommand, args, cwd?, env?
envop ("read", "write"), key, value?

Allow-all helpers

For development or trusted environments, use the built-in helpers.
import { allowAllFs, allowAllNetwork, allowAllChildProcess, allowAllEnv, allowAll } from "secure-exec";

const driver = createNodeDriver({
  permissions: { ...allowAllFs, ...allowAllNetwork },
});

// Or allow everything:
const permissive = createNodeDriver({ permissions: allowAll });

Filesystem

By default, the driver uses ModuleAccessFileSystem, which provides a read-only overlay of the host’s node_modules. You can supply a custom VirtualFileSystem implementation or use the built-in in-memory filesystem.
import { createNodeDriver, createInMemoryFileSystem } from "secure-exec";

const fs = createInMemoryFileSystem();
await fs.writeFile("/app/data.json", '{"key": "value"}');

const driver = createNodeDriver({ filesystem: fs });

Module access

The moduleAccess option configures which host node_modules directory is projected into the sandbox as a read-only overlay. By default it uses process.cwd() to locate node_modules.
const driver = createNodeDriver({
  moduleAccess: {
    cwd: "/path/to/your/project",
  },
});
OptionTypeDescription
cwdstringAbsolute path used to resolve node_modules. Defaults to process.cwd().
Inside the sandbox, modules appear at /root/node_modules/... and are read-only. Write operations to the overlay throw EACCES. Native .node addons are rejected.

Child processes

Provide a CommandExecutor to allow sandboxed code to spawn processes. This is gated behind the childProcess permission.

CommandExecutor interface

interface SpawnedProcess {
  writeStdin(data: Uint8Array | string): void;
  closeStdin(): void;
  kill(signal?: number): void;
  wait(): Promise<number>;
}

interface CommandExecutor {
  spawn(
    command: string,
    args: string[],
    options: {
      cwd?: string;
      env?: Record<string, string>;
      onStdout?: (data: Uint8Array) => void;
      onStderr?: (data: Uint8Array) => void;
    },
  ): SpawnedProcess;
}

Custom executor example

import { spawn } from "node:child_process";
import type { CommandExecutor } from "secure-exec";

const commandExecutor: CommandExecutor = {
  spawn(command, args, options) {
    const proc = spawn(command, args, {
      cwd: options.cwd,
      env: options.env,
    });
    proc.stdout?.on("data", (chunk) => options.onStdout?.(chunk));
    proc.stderr?.on("data", (chunk) => options.onStderr?.(chunk));

    return {
      writeStdin: (data) => proc.stdin?.write(data),
      closeStdin: () => proc.stdin?.end(),
      kill: (signal) => proc.kill(signal),
      wait: () =>
        new Promise((resolve) =>
          proc.on("close", (code) => resolve(code ?? 1)),
        ),
    };
  },
};

const driver = createNodeDriver({
  commandExecutor,
  permissions: {
    childProcess: (req) => {
      if (req.command === "node") return { allow: true };
      return { allow: false };
    },
  },
});

Process and OS configuration

Use processConfig and osConfig to control what the sandbox sees for process.cwd(), process.env, os.platform(), and similar APIs.
const driver = createNodeDriver({
  processConfig: {
    cwd: "/app",
    env: { NODE_ENV: "production", API_KEY: "sk-..." },
  },
  osConfig: {
    platform: "linux",
    arch: "x64",
  },
});