Sandboxed code execution service for MemberJunction actions and agents
npm install @memberjunction/code-executionSandboxed JavaScript code execution service for MemberJunction AI agents and workflows.
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.
- 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
``bash`
npm install @memberjunction/code-execution
`typescript
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();
`
Agents use the "Execute Code" action:
`json`
{
"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]}"
}
}
}
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
`typescript
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 }
]
}
});
For detailed security analysis, see security-research.md
This package uses a defense-in-depth approach with five independent security layers:
1. Process Isolation - Workers run in separate OS processes
2. V8 Isolates - Each execution runs in isolated V8 context (via isolated-vm)
3. Module Blocking - Dangerous Node.js modules are blocked
4. Resource Limits - Enforced timeout and memory limits
5. Library Allowlist - Only pre-vetted libraries available
- Access input data via input variableoutput
- Set output data via 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)
- Access filesystem (fs, path modules blocked)http
- Make network requests (, https, net, axios modules blocked, fetch() API disabled)child_process
- Spawn processes (, cluster blocked)os
- Access system information (, process blocked)eval()
- Access environment variables
- Use or Function constructor (for user code)
- Run indefinitely (timeout enforced)
- Exceed memory limits (128MB default)
#### execute(params: CodeExecutionParams): Promise
Executes code in a sandboxed environment.
Parameters:
`typescript`
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:
`typescript`
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
}
The service handles various error conditions gracefully:
`typescript
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;
}
}
`
`typescript
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
]
}
});
`typescript
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'
}
});
`typescript
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'
}
});
1. Initialize Once: Create one CodeExecutionService instance and reuse it (worker pool is shared)service.shutdown()
2. Shutdown Gracefully: Call during application shutdown to clean up workerstimeoutSeconds
3. Set Appropriate Timeouts: Adjust based on expected workloadresult.success
4. Validate Input Data: Ensure input data is in expected format before execution
5. Handle Errors Gracefully: Always check before using result.outputservice.getStats()
6. Monitor Pool Health: Use to monitor worker health and queue depthconsole.log()
7. Use Console Logging: Add statements for debugging
8. Leverage Libraries: Use lodash, date-fns, etc. instead of reinventing the wheel
9. Keep Code Focused: Break complex logic into smaller execution chunks
10. Review Security Docs: Read security-research.md for threat model and security considerations
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 │
│ │ │ │
└──────────┘ └──────────┘
- 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
- 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)
This package is part of the MemberJunction project. See the main repository for contribution guidelines.
ISC