tests — sandbox

Module: tests-sandbox Cohesion: 0.80 Members: 0

tests — sandbox

The sandbox module provides a robust framework for safely executing untrusted code and commands within the system. It encompasses various levels of sandboxing, from OS-level process isolation to containerization and secure JavaScript evaluation. The primary goal is to prevent malicious or erroneous code from impacting the host system or accessing sensitive resources.

This documentation covers the key components, their responsibilities, and how they interact to provide a layered security approach.

Core Concepts

The sandbox module employs several core concepts to achieve its goals:

Architecture Overview

The following diagram illustrates the high-level interaction between the main components when a command needs to be executed:

graph TD
    A[AutoSandboxRouter] -->|1. shouldSandbox(cmd)| B{ExecPolicy};
    B -->|PolicyAction (allow/deny/ask/sandbox)| A;
    A -->|2. route(cmd) if sandbox needed| C{SandboxRegistry};
    C -->|3. getActiveSandboxBackend()| D{Available Backends};
    D -->|Selects by Priority & Availability| E[OSSandbox];
    D -->|Selects by Priority & Availability| F[DockerSandbox];
    C -->|4. sandboxExecute(cmd)| E;
    C -->|4. sandboxExecute(cmd)| F;
    E -->|Executes via| G[Bubblewrap/Seatbelt/Landlock];
    F -->|Executes via| H[Docker CLI];

    subgraph Safe JS Evaluation
        I[safeEval/safeEvalAsync/safeEvalCondition/safeInterpolate]
    end

  1. The AutoSandboxRouter is the entry point for deciding how to handle a command.
  2. It consults the ExecPolicy to determine the recommended action.
  3. If the policy dictates sandboxing, the AutoSandboxRouter delegates to the SandboxRegistry.
  4. The SandboxRegistry identifies the best available sandbox backend (either OSSandbox or DockerSandbox).
  5. The chosen backend then executes the command using its underlying isolation mechanism.
  6. Separately, the safe-eval functions provide a dedicated environment for secure JavaScript execution.

Key Components

ExecPolicy

The ExecPolicy class (src/sandbox/execpolicy.ts) is responsible for defining and evaluating rules that govern command execution. It acts as a gatekeeper, determining whether a command should be allowed, denied, sandboxed, or require user confirmation.

Purpose: To provide a flexible, rule-based system for enforcing security policies on external command execution.

Key Features:

Usage:

import { ExecPolicy, PolicyAction, getExecPolicy, initializeExecPolicy } from '../../src/sandbox/execpolicy';

class="hl-cmt">// Get the singleton instance (or initialize it)
const policy = await initializeExecPolicy();

class="hl-cmt">// Evaluate a command
const evaluation = policy.evaluate('npm', ['install', 'express']);
console.log(`Command 'npm install express' action: ${evaluation.action}`); class="hl-cmt">// 'ask' or 'sandbox'

class="hl-cmt">// Check if a command is generally allowed
if (policy.isAllowed('ls')) {
  console.log('ls is allowed');
}

class="hl-cmt">// Add a custom rule
policy.addRule({
  name: 'Allow My Custom Script',
  pattern: '^my-script\\.sh$',
  isRegex: true,
  action: 'allow',
  priority: 150,
  enabled: true,
});

OSSandbox

The OSSandbox class (src/sandbox/os-sandbox.ts) provides OS-level process isolation using platform-specific tools. It aims to run commands with minimal privileges and restricted access to the filesystem and network.

Purpose: To execute commands in a lightweight, isolated environment directly on the host OS, leveraging native sandboxing capabilities.

Key Features:

Usage:

import { OSSandbox, getOSSandbox } from '../../src/sandbox/os-sandbox';

const sandbox = getOSSandbox();

class="hl-cmt">// Initialize the sandbox (detects capabilities and selects backend)
await sandbox.initialize();

if (await sandbox.isAvailable()) {
  console.log(`OS Sandbox backend: ${sandbox.getBackend()}`);

  class="hl-cmt">// Execute a command with network disabled and a specific work directory
  const result = await sandbox.exec('npm', ['install'], {
    workDir: '/tmp/my-project',
    allowNetwork: false,
    timeout: 60000,
  });

  if (result.success) {
    console.log('Command output:', result.stdout);
  } else {
    console.error('Command failed:', result.stderr || result.error);
  }
} else {
  console.warn('OS Sandbox not available on this system.');
}

Landlock/Seccomp Specifics: The checkLandlockSupport() function determines Landlock availability based on kernel version or /proc entry. generateSeccompFilter() creates a BPF filter that blocks specific dangerous syscalls (e.g., reboot, kexec_file_load). When OSSandbox uses the landlock backend, it writes this filter to a temporary file and passes it to bwrap using the --seccomp flag.

DockerSandbox

The DockerSandbox class (src/sandbox/docker-sandbox.ts) provides container-based isolation using Docker. This offers a higher level of isolation compared to OS-level sandboxing, as commands run within a separate container environment.

Purpose: To execute commands within isolated Docker containers, providing strong resource limits and environment control.

Key Features:

Usage:

import { DockerSandbox } from '../../src/sandbox/docker-sandbox';

const dockerSandbox = new DockerSandbox({
  image: 'node:22-slim',
  memoryLimit: '512m',
  cpuLimit: '0.5',
  networkEnabled: false,
  workspaceMount: '/path/to/host/project',
  timeout: 30000,
});

if (await DockerSandbox.isAvailable()) {
  const result = await dockerSandbox.execute('npm test');

  if (result.success) {
    console.log('Docker command output:', result.output);
  } else {
    console.error('Docker command failed:', result.error);
  }
} else {
  console.warn('Docker is not available.');
}

SandboxRegistry

The SandboxRegistry (src/sandbox/sandbox-registry.ts) acts as a central hub for managing and selecting available sandbox backends. It allows different sandbox implementations (like OSSandbox and DockerSandbox) to register themselves and be chosen based on priority and availability.

Purpose: To abstract away the complexity of choosing the "best" sandbox backend, providing a unified sandboxExecute interface.

Key Features:

Usage:

import {
  registerSandboxBackend,
  getActiveSandboxBackend,
  sandboxExecute,
  listSandboxBackends,
} from '../../src/sandbox/sandbox-registry';
import { OSSandbox } from '../../src/sandbox/os-sandbox';
import { DockerSandbox } from '../../src/sandbox/docker-sandbox';

class="hl-cmt">// Register backends (typically done during module initialization)
registerSandboxBackend(new OSSandbox(), 100); class="hl-cmt">// Higher priority
registerSandboxBackend(new DockerSandbox(), 50); class="hl-cmt">// Lower priority

class="hl-cmt">// Get the active backend
const activeBackend = await getActiveSandboxBackend();
if (activeBackend) {
  console.log(`Active sandbox backend: ${activeBackend.name}`);
}

class="hl-cmt">// Execute a command using the active backend
const result = await sandboxExecute('echo "Hello from sandbox!"');
console.log(result.output);

class="hl-cmt">// List all registered backends
const backends = await listSandboxBackends();
console.log('Registered backends:', backends);

AutoSandboxRouter

The AutoSandboxRouter (src/sandbox/auto-sandbox.ts) is the top-level component that orchestrates the decision-making process for command execution. It combines the ExecPolicy with the SandboxRegistry to automatically route commands to either direct execution or a suitable sandbox.

Purpose: To provide an intelligent, automated routing mechanism for commands, applying sandboxing only when necessary and feasible.

Key Features:

Usage:

import { AutoSandboxRouter } from '../../src/sandbox/auto-sandbox';

const router = new AutoSandboxRouter({ enabled: true });

class="hl-cmt">// Check if a command should be sandboxed
const checkResult = router.shouldSandbox('npm install');
console.log(`'npm install' should sandbox: ${checkResult.sandbox} (Reason: ${checkResult.reason})`);

class="hl-cmt">// Route and execute a command
const routeResult = await router.route('ls -la');
console.log(`'ls -la' executed in mode: ${routeResult.mode}`); class="hl-cmt">// 'direct'

const npmRouteResult = await router.route('npm install');
console.log(`'npm install' executed in mode: ${npmRouteResult.mode}`); class="hl-cmt">// 'sandbox' or 'direct' (if no backend)

class="hl-cmt">// Disable the router
router.setEnabled(false);
const disabledCheck = router.shouldSandbox('npm install');
console.log(`After disabling, 'npm install' should sandbox: ${disabledCheck.sandbox}`); class="hl-cmt">// false

Safe JavaScript Evaluation (safe-eval.ts)

The safe-eval.ts module provides functions for securely evaluating JavaScript code snippets within a restricted environment. This is crucial for scenarios where user-provided or untrusted JavaScript needs to be executed without granting access to the full Node.js runtime.

Purpose: To safely execute JavaScript expressions and template strings, preventing access to sensitive global objects and modules.

Key Functions:

Key Features:

Usage:

import { safeEval, safeEvalAsync, safeEvalCondition, safeInterpolate } from '../../src/sandbox/safe-eval';

class="hl-cmt">// Synchronous evaluation
const sum = safeEval('x + y', { context: { x: 10, y: 20 } });
console.log('Sum:', sum); class="hl-cmt">// 30

class="hl-cmt">// Asynchronous evaluation
async function runAsyncEval() {
  const result = await safeEvalAsync('const p = Promise.resolve(100); return await p + val;', { context: { val: 1 } });
  console.log('Async result:', result); class="hl-cmt">// 101
}
runAsyncEval();

class="hl-cmt">// Conditional evaluation
const isAllowed = safeEvalCondition(&#39;user.role === "admin" && item.price < 100&#39;, {
  user: { role: &#39;admin&#39; },
  item: { price: 50 },
});
console.log(&#39;Is allowed:&#39;, isAllowed); class="hl-cmt">// true

class="hl-cmt">// Template interpolation
const message = safeInterpolate(&#39;Hello {{user.name}}, your total is ${{order.total.toFixed(2)}}.&#39;, {
  user: { name: &#39;Alice&#39; },
  order: { total: 123.456 },
});
console.log(&#39;Message:&#39;, message); class="hl-cmt">// "Hello Alice, your total is $123.46."

class="hl-cmt">// Attempting unsafe operations will throw
try {
  safeEval(&#39;process.exit(1)&#39;);
} catch (e) {
  console.error(&#39;Caught unsafe eval attempt:&#39;, e.message);
}

Developer Notes