@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-execution
Usage
Direct TypeScript Usage
import { CodeExecutionService } from '@memberjunction/code-execution';
const service = new CodeExecutionService({
poolSize: 2,
maxQueueSize: 100,
maxCrashesPerWorker: 3,
crashTimeWindow: 60000
});
await service.initialize();
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,
memoryLimitMB: 128
});
if (result.success) {
console.log(result.output);
console.log(result.logs);
} else {
console.error(result.error);
console.error(result.errorType);
}
const stats = service.getStats();
console.log('Active workers:', stats.activeWorkers);
console.log('Busy workers:', stats.busyWorkers);
console.log('Queue length:', stats.queueLength);
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
input variable
- Set output data via
output variable
- 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, path modules blocked)
- Make network requests (
http, https, net, axios modules blocked, fetch() API disabled)
- Spawn processes (
child_process, cluster blocked)
- Access system information (
os, process blocked)
- Access environment variables
- Use
eval() or Function constructor (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;
language: 'javascript';
inputData?: any;
timeoutSeconds?: number;
memoryLimitMB?: number;
}
Returns:
interface CodeExecutionResult {
success: boolean;
output?: any;
logs?: string[];
error?: string;
errorType?: 'TIMEOUT' | 'MEMORY_LIMIT' | 'SYNTAX_ERROR' | 'RUNTIME_ERROR' | 'SECURITY_ERROR';
executionTimeMs?: number;
}
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 },
]
}
});
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
CodeExecutionService instance and reuse it (worker pool is shared)
- Shutdown Gracefully: Call
service.shutdown() during application shutdown to clean up workers
- Set Appropriate Timeouts: Adjust
timeoutSeconds based on expected workload
- Validate Input Data: Ensure input data is in expected format before execution
- Handle Errors Gracefully: Always check
result.success before using result.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/await or 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