@aiconnect/codelets-runner
v0.2.0
Published
Minimal, secure runtime for executing untrusted JavaScript codelets with vm2 sandboxing
Maintainers
Readme
@aiconnect/codelets-runner
Minimal, secure runtime for executing untrusted JavaScript codelets with vm2 sandboxing.
Overview
The Codelets Runner provides a safe, isolated environment for executing small JavaScript code snippets ("codelets") with:
- vm2-based sandboxing: Default-deny security policy
- Timeout enforcement: Prevents infinite loops (default: 2000ms)
- Console capture: Captures console.log/info/warn/error with timestamps
- Structured error handling: Standardized error codes and sanitized messages
- TypeScript-first: Full type safety with ESM + CJS support
Installation
npm install @aiconnect/codelets-runnerBasic Usage
import { runCodelet } from '@aiconnect/codelets-runner';
// Define a codelet (must export async function main)
const codeletSource = {
type: 'string' as const,
code: `
export async function main(input, { logger }) {
logger.info('Processing input:', input);
return { result: input.value * 2 };
}
`
};
// Run the codelet
const result = await runCodelet(codeletSource, {
input: { value: 21 },
timeout_ms: 2000
});
if (result.ok) {
console.log('Result:', result.value); // { result: 42 }
console.log('Logs:', result.logs);
} else {
console.error('Error:', result.error);
}API
runCodelet(source, options)
Executes a codelet and returns a result.
Parameters:
source: CodeletSource- The codelet source codetype: 'string'- Only string sources supported in MVPcode: string- JavaScript code that exportsasync function main(input, helpers)export_name?: string- Name of exported function (default: 'main')
options: CodeletRunOptions- Execution optionsinput?: unknown- Input data passed to codelettimeout_ms?: number- Timeout in milliseconds (default: 2000)globals?: Record<string, unknown>- Read-only global variables
Returns: Promise<CodeletResult>
ok: boolean- Whether execution succeededvalue?: unknown- Return value if successfulerror?: CodeletError- Error details if failedlogs: ConsoleLog[]- Captured console output (max 100 logs)duration_ms: number- Execution time in milliseconds
createCodeletRunner()
Creates a reusable runner instance (factory function).
import { createCodeletRunner } from '@aiconnect/codelets-runner';
const runner = createCodeletRunner();
const result = await runner.run(source, options);
runner.dispose(); // Clean up resourcesCodelet Signature
All codelets must export a main function with this signature:
export async function main(input: unknown, helpers: { logger: Logger }): Promise<unknown> {
// Your code here
return result;
}Helpers (MVP):
logger.info(message)- Log info messagelogger.warn(message)- Log warning messagelogger.error(message)- Log error message
Error Codes
TIMEOUT- Execution exceeded timeoutSYNTAX_ERROR- Invalid JavaScript syntaxRUNTIME_ERROR- Unhandled exception in codeletPOLICY_VIOLATION- Attempted disallowed capability (require, eval, etc.)
Compatibility
| Component | Version/Support | |-----------|----------------| | Node.js | >= 18.x | | Module Systems | ESM and CJS (via package.json exports) | | TypeScript | >= 4.5 (optional, types included) | | vm2 | 3.10.0 (pinned) |
Known Issues and Limitations
vm2 Timeout Limitations
⚠️ Important: vm2's timeout mechanism has significant limitations and cannot interrupt:
- Tight synchronous loops:
while(true) {}, tightforloops without async yields - Native timer functions:
setTimeout(),setInterval(),setImmediate()(run outside VM control) - Native function calls: Frequent calls to
Date.now(),Math.random(), etc. may not be interrupted
Example of non-interruptible code:
// This CANNOT be interrupted by vm2 timeout - will hang indefinitely
export async function main(input) {
while (true) {} // Infinite loop
}
// This ALSO cannot be interrupted - setTimeout runs outside VM
export async function main(input) {
await new Promise(r => setTimeout(r, 60000));
}Workaround: Always run the codelet runner with external process monitoring:
- Docker:
--cpus="0.5" --memory="512m" --pids-limit=100 - Kubernetes:
resources.limits.cpuandresources.limits.memory - systemd:
CPUQuota=50%andMemoryLimit=512M - Node.js:
--max-old-space-size=512
See Integration Guide for detailed examples.
No Memory Limits
The runner does not enforce memory limits in this MVP. Memory-bound codelets can cause OOM (Out of Memory) errors. Use external resource limits (see above).
CPU-Bound Codelets
Avoid running CPU-intensive codelets without external safeguards. The sandbox provides execution isolation but not resource isolation.
Operational Recommendations
For Production Deployments:
- Always use external resource limits (Docker/K8s/systemd)
- Monitor execution metrics: CPU, memory, duration, error rates
- Set appropriate timeouts: 2-5 seconds for most use cases
- Alert on anomalies: Frequent timeouts may indicate malicious codelets
See the Integration Guide for comprehensive operational guidance.
Security & Limitations (MVP)
Security Policy (Default-Deny):
- No
require()or module imports - No network access
- No filesystem access
- No
eval()or dynamic code generation - Frozen global scope
MVP Limitations:
- JavaScript-only (no TypeScript transpilation)
- String sources only (no file or module loading)
- No memory limits (timeout only)
- No allowlist support (all capabilities denied)
- Basic error sanitization (regex-based path stripping)
- Console capture limited to 100 logs per execution
Dependencies:
[email protected]- Exact version pinned for security
Monitoring: Actively monitor vm2 security advisories. See project SECURITY.md for CVE tracking.
Post-MVP Features
The following features are deferred to future releases:
- Policy allowlists (require, env, network)
- Deterministic execution (seeded PRNG, controllable clock)
- Memory and CPU limits
- Observable execution (metrics, events)
- Context pooling
- Alternative sandbox adapters (node:vm, isolated-vm)
- Advanced helpers (env, random, now, fetch)
License
MIT
