Skip to content

Latest commit

 

History

History
234 lines (187 loc) · 7 KB

File metadata and controls

234 lines (187 loc) · 7 KB
title Node.js System Driver
sidebarTitle Node.js
description Server-side system driver with full host capabilities.
icon node-js

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

Option Type Description
filesystem VirtualFileSystem Custom filesystem implementation. Falls back to the built-in ModuleAccessFileSystem.
moduleAccess ModuleAccessOptions Configure the node_modules overlay (see Module access).
networkAdapter NetworkAdapter Custom network adapter.
commandExecutor CommandExecutor Custom command executor for child processes (see Child processes).
permissions Permissions Permission callbacks for fs, network, child process, and env access.
useDefaultNetwork boolean Use the built-in network adapter (fetch, DNS, HTTP client).
loopbackExemptPorts number[] Loopback ports that bypass SSRF checks when using the default network adapter.
processConfig ProcessConfig Values for process.cwd(), process.env, etc. inside the sandbox.
osConfig OSConfig Values 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

Permission Request fields
fs op ("read", "write", "mkdir", "stat", "rm", "rename", ...), path
network op ("fetch", "http", "dns", "listen"), url?, method?, hostname?
childProcess command, args, cwd?, env?
env op ("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",
  },
});
Option Type Description
cwd string Absolute 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",
  },
});