@memberjunction/code-execution
v2.123.1
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.
Overview
This package provides secure, isolated execution of JavaScript code in a sandboxed environment using isolated-vm with worker process isolation. It's designed to enable AI agents to generate and run code for data analysis, transformations, and calculations without compromising system security or stability.
⚠️ Security: This package handles untrusted code execution. Please review security-research.md for comprehensive security analysis, threat model, and implementation details.
Features
- Multi-Layer Security: 5 independent defense layers (process isolation, V8 isolates, module blocking, resource limits, library allowlist)
- Fault Isolation: Workers run in separate processes - crashes don't affect main application
- Automatic Recovery: Crashed workers automatically restart with circuit breaker protection
- Timeout Protection: Configurable execution timeouts (default: 30 seconds)
- Memory Limits: Configurable per-execution memory limits (default: 128MB)
- Safe Library Access: Curated allowlist of npm packages with inline implementations
- Console Logging: Captures console output for debugging
- Type Safety: Full TypeScript support with comprehensive interfaces
- Audit Trail: Integrated with MemberJunction action logging
Installation
npm install @memberjunction/code-executionUsage
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 (default: 60s)
});
// Initialize the worker pool (can also be done automatically 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: execution timeout (default: 30)
memoryLimitMB: 128 // Optional: memory limit (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 Actions (for AI Agents)
Agents use 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]}"
}
}
}Supported Libraries
The sandbox includes these pre-approved npm packages:
- lodash - Data manipulation and utilities
- date-fns - Modern date library
- mathjs - Advanced mathematics
- papaparse - CSV parsing
- uuid - UUID generation
- validator - String validation and sanitization
Using Libraries in Code
const result = await service.execute({
code: `
const _ = require('lodash');
const { format, addDays } = require('date-fns');
const math = require('mathjs');
// Group data by category
const grouped = _.groupBy(input.data, 'category');
// Calculate statistics
const stats = {};
for (const [category, items] of Object.entries(grouped)) {
const values = items.map(i => i.value);
stats[category] = {
mean: math.mean(values),
median: math.median(values),
total: math.sum(values)
};
}
output = stats;
`,
language: 'javascript',
inputData: {
data: [
{ category: 'A', value: 10 },
{ category: 'B', value: 20 },
{ category: 'A', value: 15 }
]
}
});Security Model
For detailed security analysis, see security-research.md
This package uses a defense-in-depth approach with five independent security layers:
- Process Isolation - Workers run in separate OS processes
- V8 Isolates - Each execution runs in isolated V8 context (via
isolated-vm) - Module Blocking - Dangerous Node.js modules are blocked
- Resource Limits - Enforced timeout and memory limits
- Library Allowlist - Only pre-vetted libraries available
What Code CAN Do
- Access input data via
inputvariable - Set output data via
outputvariable - Use console methods (log, error, warn, info)
- Require allowed npm packages (lodash, date-fns, uuid, validator)
- Perform calculations and data transformations
- Use safe built-in objects (JSON, Math, Date, Array, Object, String, Number, Boolean)
What Code CANNOT Do
- Access filesystem (
fs,pathmodules blocked) - Make network requests (
http,https,net,axiosmodules blocked,fetch()API disabled) - Spawn processes (
child_process,clusterblocked) - Access system information (
os,processblocked) - Access environment variables
- Use
eval()orFunctionconstructor (for user code) - Run indefinitely (timeout enforced)
- Exceed memory limits (128MB default)
API Reference
CodeExecutionService
execute(params: CodeExecutionParams): Promise<CodeExecutionResult>
Executes code in a sandboxed environment.
Parameters:
interface CodeExecutionParams {
code: string; // JavaScript code to execute
language: 'javascript'; // Currently only 'javascript' supported
inputData?: any; // Data available as 'input' variable
timeoutSeconds?: number; // Execution timeout (default: 30)
memoryLimitMB?: number; // Memory limit (default: 128)
}Returns:
interface CodeExecutionResult {
success: boolean; // Whether execution succeeded
output?: any; // Value of 'output' variable
logs?: string[]; // Console output
error?: string; // Error message if failed
errorType?: 'TIMEOUT' | 'MEMORY_LIMIT' | 'SYNTAX_ERROR' | 'RUNTIME_ERROR' | 'SECURITY_ERROR';
executionTimeMs?: number; // Execution duration
}Error Handling
The service handles various error conditions gracefully:
const result = await service.execute({
code: 'invalid syntax here',
language: 'javascript'
});
if (!result.success) {
switch (result.errorType) {
case 'SYNTAX_ERROR':
console.error('Code has syntax errors:', result.error);
break;
case 'TIMEOUT':
console.error('Code execution timed out');
break;
case 'RUNTIME_ERROR':
console.error('Runtime error:', result.error);
break;
case 'SECURITY_ERROR':
console.error('Security violation:', result.error);
break;
}
}Examples
Data Analysis
const result = await service.execute({
code: `
const _ = require('lodash');
// Calculate key metrics
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')
}))
.orderBy('revenue', 'desc')
.take(5)
.value()
};
output = metrics;
`,
language: 'javascript',
inputData: {
sales: [
{ product: 'Widget', amount: 100 },
{ product: 'Gadget', amount: 200 },
// ... more sales
]
}
});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
});
// Filter and transform
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'
}
});Best Practices
- Initialize Once: Create one
CodeExecutionServiceinstance and reuse it (worker pool is shared) - Shutdown Gracefully: Call
service.shutdown()during application shutdown to clean up workers - Set Appropriate Timeouts: Adjust
timeoutSecondsbased on expected workload - Validate Input Data: Ensure input data is in expected format before execution
- Handle Errors Gracefully: Always check
result.successbefore usingresult.output - Monitor Pool Health: Use
service.getStats()to monitor worker health and queue depth - Use Console Logging: Add
console.log()statements for debugging - Leverage Libraries: Use lodash, date-fns, etc. instead of reinventing the wheel
- Keep Code Focused: Break complex logic into smaller execution chunks
- Review Security Docs: Read security-research.md for threat model and security considerations
Architecture
Worker Process Pool
The service maintains a pool of worker processes for fault isolation:
┌─────────────────────────────────────┐
│ Main Application Process │
│ │
│ ┌────────────────────────────┐ │
│ │ CodeExecutionService │ │
│ │ │ │
│ │ ┌──────────────────────┐ │ │
│ │ │ WorkerPool │ │ │
│ │ │ - Queue Requests │ │ │
│ │ │ - Monitor Health │ │ │
│ │ │ - Auto Restart │ │ │
│ │ └──────────────────────┘ │ │
│ └────────────────────────────┘ │
│ │ │ │
└──────────┼──────────┼───────────────┘
│ │ IPC (JSON)
┌──────▼───┐ ┌───▼──────┐
│ Worker 1 │ │ Worker 2 │ (Separate OS Processes)
│ │ │ │
│ isolated │ │ isolated │
│ -vm │ │ -vm │
│ │ │ │
└──────────┘ └──────────┘Benefits of Process Isolation
- Fault Tolerance: Worker crashes don't affect main app or other workers
- Resource Isolation: Per-process memory limits and cleanup
- Automatic Recovery: Crashed workers restart automatically
- Circuit Breaker: Too many crashes → worker disabled to prevent crash loops
Limitations
- Language Support: Currently only JavaScript (Python may be added in future)
- Async Code: No support for
async/awaitor Promises (synchronous code only) - Library Expansion: New libraries require code changes (not configurable at runtime)
- Throughput: ~100-200 executions/sec per worker (horizontal scaling recommended for higher loads)
Contributing
This package is part of the MemberJunction project. See the main repository for contribution guidelines.
License
ISC
