npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@memberjunction/code-execution

v5.14.0

Published

Sandboxed code execution service for MemberJunction actions and agents

Readme

@memberjunction/code-execution

Sandboxed JavaScript code execution service for MemberJunction AI agents and workflows. Executes untrusted code in isolated worker processes using V8 isolates (isolated-vm) with a defense-in-depth security model.

Parent package: This package is part of the MemberJunction Actions Framework. For general Actions architecture, design philosophy, and the boundary between Actions and internal code, see the parent CLAUDE.md.

Overview

This package provides secure, isolated execution of JavaScript code for scenarios where AI agents or workflows need to generate and run code dynamically -- data analysis, transformations, calculations, and more. It uses a multi-layer security architecture to ensure that untrusted code cannot compromise system security or stability.

The service manages a pool of worker processes, each running code inside V8 isolates via the isolated-vm library. If a worker crashes due to a catastrophic error (e.g., V8 OOM), only that worker terminates and is automatically restarted -- the main application remains unaffected.

For comprehensive security analysis, threat model, and implementation details, see security-research.md.

Installation

npm install @memberjunction/code-execution

This package is server-side only (Node.js). It requires the isolated-vm native module, which compiles during installation.

Architecture

Execution Flow

flowchart TB
    subgraph MainProcess["Main Application Process"]
        CES["CodeExecutionService"]
        WP["WorkerPool"]
        Q["Request Queue"]
        CES --> WP
        WP --> Q
    end

    subgraph W1["Worker Process 1"]
        IV1["isolated-vm Isolate"]
        CTX1["V8 Context"]
        SB1["Sandbox\n(user code)"]
        IV1 --> CTX1 --> SB1
    end

    subgraph W2["Worker Process 2"]
        IV2["isolated-vm Isolate"]
        CTX2["V8 Context"]
        SB2["Sandbox\n(user code)"]
        IV2 --> CTX2 --> SB2
    end

    WP -- "IPC (JSON)" --> W1
    WP -- "IPC (JSON)" --> W2

    style MainProcess fill:#2d6a9f,stroke:#1a4971,color:#fff
    style W1 fill:#2d8659,stroke:#1a5c3a,color:#fff
    style W2 fill:#2d8659,stroke:#1a5c3a,color:#fff
    style CES fill:#7c5295,stroke:#563a6b,color:#fff
    style WP fill:#7c5295,stroke:#563a6b,color:#fff
    style Q fill:#b8762f,stroke:#8a5722,color:#fff
    style IV1 fill:#2d6a9f,stroke:#1a4971,color:#fff
    style IV2 fill:#2d6a9f,stroke:#1a4971,color:#fff
    style CTX1 fill:#b8762f,stroke:#8a5722,color:#fff
    style CTX2 fill:#b8762f,stroke:#8a5722,color:#fff
    style SB1 fill:#7c5295,stroke:#563a6b,color:#fff
    style SB2 fill:#7c5295,stroke:#563a6b,color:#fff

Security Layers

The package uses a defense-in-depth approach with five independent security layers:

flowchart LR
    Code["Untrusted\nCode"] --> L1
    L1["Layer 1\nProcess\nIsolation"] --> L2["Layer 2\nV8 Isolate\n(isolated-vm)"]
    L2 --> L3["Layer 3\nModule\nBlocking"]
    L3 --> L4["Layer 4\nResource\nLimits"]
    L4 --> L5["Layer 5\nLibrary\nAllowlist"]
    L5 --> Exec["Safe\nExecution"]

    style Code fill:#b8762f,stroke:#8a5722,color:#fff
    style L1 fill:#2d6a9f,stroke:#1a4971,color:#fff
    style L2 fill:#2d6a9f,stroke:#1a4971,color:#fff
    style L3 fill:#2d6a9f,stroke:#1a4971,color:#fff
    style L4 fill:#2d6a9f,stroke:#1a4971,color:#fff
    style L5 fill:#2d6a9f,stroke:#1a4971,color:#fff
    style Exec fill:#2d8659,stroke:#1a5c3a,color:#fff

| Layer | Mechanism | Protects Against | |-------|-----------|-----------------| | Process Isolation | Workers run in separate OS processes via child_process.fork() | V8 catastrophic failures crashing the main app | | V8 Isolates | Each execution runs in an isolated V8 context (isolated-vm) | Sandbox escape, cross-execution data leakage | | Module Blocking | Dangerous Node.js modules blocked (fs, http, child_process, etc.) | Filesystem access, network requests, process spawning | | Resource Limits | Configurable timeout and memory limits per execution | Denial-of-service via infinite loops or memory exhaustion | | Library Allowlist | Only pre-vetted libraries available via require() | Supply chain attacks, unsafe library usage |

Internal Module Structure

flowchart TB
    IDX["index.ts\n(public exports)"]
    CES["CodeExecutionService\n(main entry point)"]
    WP["WorkerPool\n(process management)"]
    WKR["worker.ts\n(child process)"]
    LIB["libraries/\n(allowlisted modules)"]
    TYP["types/\n(interfaces)"]

    IDX --> CES
    IDX --> WP
    IDX --> LIB
    IDX --> TYP

    CES --> WP
    CES --> TYP
    WP --> TYP
    WKR --> TYP
    WKR --> LIB

    style IDX fill:#7c5295,stroke:#563a6b,color:#fff
    style CES fill:#2d6a9f,stroke:#1a4971,color:#fff
    style WP fill:#2d6a9f,stroke:#1a4971,color:#fff
    style WKR fill:#2d8659,stroke:#1a5c3a,color:#fff
    style LIB fill:#b8762f,stroke:#8a5722,color:#fff
    style TYP fill:#b8762f,stroke:#8a5722,color:#fff

Usage

Direct TypeScript Usage

import { CodeExecutionService } from '@memberjunction/code-execution';

// Create service with optional worker pool configuration
const service = new CodeExecutionService({
  poolSize: 2,              // Number of worker processes (default: 2)
  maxQueueSize: 100,        // Max queued requests (default: 100)
  maxCrashesPerWorker: 3,   // Crashes before marking unhealthy (default: 3)
  crashTimeWindow: 60000    // Time window for crash counting in ms (default: 60000)
});

// Initialize the worker pool (also auto-initializes on first execute)
await service.initialize();

// Execute code
const result = await service.execute({
  code: `
    const sum = input.values.reduce((a, b) => a + b, 0);
    const average = sum / input.values.length;
    output = { sum, average };
  `,
  language: 'javascript',
  inputData: { values: [10, 20, 30, 40, 50] },
  timeoutSeconds: 30,       // Optional (default: 30)
  memoryLimitMB: 128        // Optional (default: 128)
});

if (result.success) {
  console.log(result.output); // { sum: 150, average: 30 }
  console.log(result.logs);   // Any console.log output from the code
} else {
  console.error(result.error);
  console.error(result.errorType); // 'TIMEOUT' | 'MEMORY_LIMIT' | 'SYNTAX_ERROR' | etc.
}

// Check worker pool health
const stats = service.getStats();
console.log('Active workers:', stats.activeWorkers);
console.log('Busy workers:', stats.busyWorkers);
console.log('Queue length:', stats.queueLength);

// Graceful shutdown (important for clean application exit)
await service.shutdown();

With MemberJunction Actions (for AI Agents)

AI agents interact with the sandbox through the "Execute Code" action:

{
  "type": "Action",
  "action": {
    "name": "Execute Code",
    "params": {
      "code": "const total = input.prices.reduce((sum, p) => sum + p, 0); output = total;",
      "language": "javascript",
      "inputData": "{\"prices\": [10, 20, 30]}"
    }
  }
}

API Reference

CodeExecutionService

The main entry point for executing sandboxed code.

Constructor

constructor(options?: WorkerPoolOptions)

| Option | Type | Default | Description | |--------|------|---------|-------------| | poolSize | number | 2 | Number of worker processes to maintain | | maxQueueSize | number | 100 | Maximum requests to queue before rejecting | | maxCrashesPerWorker | number | 3 | Crash count threshold before disabling a worker | | crashTimeWindow | number | 60000 | Time window (ms) for crash counting |

Methods

| Method | Returns | Description | |--------|---------|-------------| | initialize() | Promise<void> | Start worker processes. Called automatically on first execute() if not called explicitly. | | execute(params) | Promise<CodeExecutionResult> | Execute code in a sandboxed worker. See CodeExecutionParams below. | | getStats() | PoolStats | Get current pool statistics (worker count, busy count, queue length). | | shutdown() | Promise<void> | Gracefully terminate all workers and reject pending requests. |

CodeExecutionParams

interface CodeExecutionParams {
  code: string;              // JavaScript code to execute
  language: 'javascript';    // Currently only 'javascript' supported
  inputData?: unknown;       // Data available as the 'input' variable in sandbox
  timeoutSeconds?: number;   // Max execution time in seconds (default: 30)
  memoryLimitMB?: number;    // Memory limit per execution in MB (default: 128)
}

CodeExecutionResult

interface CodeExecutionResult {
  success: boolean;           // Whether execution completed without error
  output?: unknown;           // Value of the 'output' variable set by executed code
  logs?: string[];            // Captured console output (log, error, warn, info)
  error?: string;             // Error message if execution failed
  errorType?: 'TIMEOUT' | 'MEMORY_LIMIT' | 'SYNTAX_ERROR' | 'RUNTIME_ERROR' | 'SECURITY_ERROR';
  executionTimeMs?: number;   // Wall-clock execution duration in milliseconds
}

WorkerPool

Manages the pool of worker processes. Typically used through CodeExecutionService, but can be instantiated directly for advanced use cases.

| Method | Returns | Description | |--------|---------|-------------| | initialize() | Promise<void> | Fork worker processes and wait for them to signal ready. | | execute(params) | Promise<CodeExecutionResult> | Queue and execute a request in the next available worker. | | getStats() | PoolStats | Return totalWorkers, activeWorkers, busyWorkers, queueLength. | | shutdown() | Promise<void> | Gracefully kill all workers with SIGTERM, force-kill after 5 seconds. |

Library Functions

| Function | Returns | Description | |----------|---------|-------------| | isModuleAllowed(name) | boolean | Check if a module name is in the allowlist. | | getAllowedModuleNames() | string[] | Return all allowed module names. | | getLibrarySource(name) | string \| null | Return the JavaScript source for a library (inline or bundled). |

Sandbox Environment

What Code CAN Do

  • Access input data via the input variable
  • Set output data via the output variable
  • Use console methods (console.log, console.error, console.warn, console.info)
  • Require allowed npm packages (see below)
  • Perform calculations and data transformations
  • Use safe built-in objects: JSON, Math, Date, Array, Object, String, Number, Boolean, RegExp, Error

What Code CANNOT Do

  • Access the filesystem (fs, path modules blocked)
  • Make network requests (http, https, net, axios blocked; fetch() API disabled)
  • Spawn processes (child_process, cluster blocked)
  • Access system information (os, process blocked)
  • Access environment variables
  • Use eval() or Function constructor (within user code scope)
  • Run indefinitely (timeout enforced)
  • Exceed memory limits (per-isolate enforcement)

Allowed Libraries

The sandbox provides a curated set of pre-vetted libraries accessible via require():

| Library | Type | Description | |---------|------|-------------| | lodash | Inline subset | Array/collection/object utilities (chunk, groupBy, sortBy, pick, omit, get, set, sum, sumBy, mean, meanBy, cloneDeep, and more) | | date-fns | Inline subset | Date manipulation (format, addDays, subDays, addMonths, differenceInDays, isAfter, isBefore, startOfDay, endOfDay, parseISO) | | mathjs | Bundled full | Comprehensive mathematics library | | papaparse | Bundled full | CSV parsing and generation | | jstat | Bundled full | Statistical distributions, hypothesis testing, regression | | uuid | Inline | UUID v4 generation | | validator | Inline subset | String validation (isEmail, isURL, isNumeric, isAlpha, isAlphanumeric, isEmpty, isLength, isIn, matches) |

Libraries marked "Inline subset" are lightweight re-implementations of the most commonly used functions. Libraries marked "Bundled full" are the complete libraries loaded from pre-built JavaScript bundles.

Error Handling

The service classifies errors into five categories:

const result = await service.execute({
  code: 'while(true) {}',  // infinite loop
  language: 'javascript'
});

if (!result.success) {
  switch (result.errorType) {
    case 'TIMEOUT':
      // Code exceeded the configured timeout
      break;
    case 'MEMORY_LIMIT':
      // Code exceeded the configured memory limit
      break;
    case 'SYNTAX_ERROR':
      // Code has JavaScript syntax errors
      break;
    case 'RUNTIME_ERROR':
      // Code threw an unhandled exception
      break;
    case 'SECURITY_ERROR':
      // Code attempted a blocked operation (e.g., requiring 'fs')
      break;
  }
}

Worker Pool Lifecycle

The worker pool implements automatic recovery with circuit breaker protection:

stateDiagram-v2
    [*] --> Idle: Worker created
    Idle --> Busy: Request assigned
    Busy --> Idle: Execution complete
    Busy --> Crashed: Process exit
    Crashed --> Restarting: crashCount < max
    Crashed --> Disabled: crashCount >= max
    Restarting --> Idle: Worker ready
    Disabled --> [*]: Removed from pool
  • Idle: Worker is ready to accept work.
  • Busy: Worker is executing code. New requests are queued.
  • Crashed: Worker process exited unexpectedly. Current request is rejected with an error.
  • Restarting: Worker is being restarted (automatic). Crash count is tracked within the configured time window.
  • Disabled: Too many crashes in the time window. Worker is removed from the pool to prevent crash loops.

Examples

Data Analysis

const result = await service.execute({
  code: `
    const _ = require('lodash');

    const metrics = {
      totalSales: _.sumBy(input.sales, 'amount'),
      avgSale: _.meanBy(input.sales, 'amount'),
      topProducts: _.chain(input.sales)
        .groupBy('product')
        .map((items, product) => ({
          product,
          revenue: _.sumBy(items, 'amount')
        }))
        .sortBy(x => -x.revenue)
        .take(5)
        .value()
    };

    output = metrics;
  `,
  language: 'javascript',
  inputData: {
    sales: [
      { product: 'Widget', amount: 100 },
      { product: 'Gadget', amount: 200 },
      { product: 'Widget', amount: 150 }
    ]
  }
});

Date Manipulation

const result = await service.execute({
  code: `
    const { format, addDays, differenceInDays } = require('date-fns');

    const start = new Date(input.startDate);
    const end = new Date(input.endDate);

    output = {
      duration: differenceInDays(end, start),
      milestones: [
        format(addDays(start, 30), 'yyyy-MM-dd'),
        format(addDays(start, 60), 'yyyy-MM-dd'),
        format(addDays(start, 90), 'yyyy-MM-dd')
      ]
    };
  `,
  language: 'javascript',
  inputData: {
    startDate: '2025-01-01',
    endDate: '2025-12-31'
  }
});

CSV Processing

const result = await service.execute({
  code: `
    const Papa = require('papaparse');

    const parsed = Papa.parse(input.csvData, {
      header: true,
      dynamicTyping: true
    });

    const processed = parsed.data
      .filter(row => row.status === 'active')
      .map(row => ({
        id: row.id,
        name: row.name,
        value: row.value * 1.1 // 10% markup
      }));

    output = processed;
  `,
  language: 'javascript',
  inputData: {
    csvData: 'id,name,value,status\\n1,Item A,100,active\\n2,Item B,200,inactive'
  }
});

Statistical Analysis

const result = await service.execute({
  code: `
    const math = require('mathjs');

    const values = input.measurements;
    output = {
      mean: math.mean(values),
      median: math.median(values),
      std: math.std(values),
      variance: math.variance(values),
      min: math.min(values),
      max: math.max(values)
    };
  `,
  language: 'javascript',
  inputData: {
    measurements: [23.1, 24.5, 22.8, 25.0, 23.7, 24.2, 22.9]
  }
});

Best Practices

  1. Initialize Once: Create a single CodeExecutionService instance and reuse it across your application. The worker pool is shared and handles concurrency internally.
  2. Shutdown Gracefully: Call service.shutdown() during application shutdown to terminate workers cleanly and reject pending requests.
  3. Set Appropriate Timeouts: Adjust timeoutSeconds based on expected workload. Short timeouts (5-10s) for simple calculations, longer (30-60s) for complex data processing.
  4. Validate Input Data: Ensure input data is JSON-serializable and in the expected format before execution. All data crosses an IPC boundary via JSON serialization.
  5. Check Result Success: Always check result.success before accessing result.output. Errors are returned in the result object, not thrown as exceptions.
  6. Monitor Pool Health: Use service.getStats() to monitor worker health and queue depth in production.
  7. Use Console Logging: Add console.log() statements in sandbox code for debugging. Output is captured in result.logs.
  8. Leverage Libraries: Use the provided allowlisted libraries (lodash, date-fns, mathjs, etc.) rather than reimplementing common operations.
  9. Keep Code Synchronous: The sandbox does not support async/await or Promises. All code must be synchronous.

Dependencies

| Package | Purpose | |---------|---------| | @memberjunction/core | MJ core framework (logging via LogError, LogStatus) | | @memberjunction/global | MJ global utilities | | isolated-vm | V8 isolate-based sandboxing (native module) | | lodash | Source for inline lodash subset | | date-fns | Source for inline date-fns subset | | mathjs | Bundled for sandbox use | | papaparse | Bundled for sandbox use | | uuid | Source for inline UUID implementation | | validator | Source for inline validator subset |

Related Packages

Limitations

  • Language Support: Currently only JavaScript. Python support may be added in the future.
  • Async Code: No support for async/await or Promises. Sandbox code must be synchronous.
  • Library Expansion: Adding new libraries requires code changes to the allowlist; they are not configurable at runtime.
  • Throughput: Approximately 100-200 executions per second per worker. Use poolSize configuration or horizontal scaling for higher loads.
  • Data Size: All input/output data crosses an IPC boundary via JSON serialization, so extremely large payloads may impact performance.

License

MIT