
Security News
RubyGems Adds Cooldown Feature to Bundler for Newly Published Gems
RubyGems and Bundler 4.0.13 introduced an opt-in cooldown feature that delays newly published gems during dependency resolution.
@enclave-vm/core
Advanced tools
Secure AgentScript execution environment with defense-in-depth architecture
The @enclave-vm/core package provides a hardened execution environment for running LLM-generated JavaScript code (AgentScript) safely. It combines AST validation, code transformation, runtime guards, and VM sandboxing to prevent sandbox escapes and resource exhaustion.
| Metric | Value |
|---|---|
| Security Tests | 1900+ tests, 100% pass rate |
| Attack Vectors | 150+ blocked (including function gadget attacks) |
| CVE Protection | Covers known vm2, isolated-vm, node-vm exploits |
| Defense Layers | 6 (Pre-Scanner, AST, Transform, Scoring, VM/Worker Pool, Sanitization) |
For the full security audit report, see SECURITY-AUDIT.md.
callTool() execution with iteration and call limitsvm modulenpm install @enclave-vm/core
# or
yarn add @enclave-vm/core
# or
pnpm add @enclave-vm/core
import { Enclave } from '@enclave-vm/core';
// Create enclave with a tool handler
const enclave = new Enclave({
timeout: 5000,
maxToolCalls: 10,
maxIterations: 1000,
toolHandler: async (toolName, args) => {
// Your tool execution logic
return { success: true, data: `Called ${toolName}` };
},
});
// Execute AgentScript code
const result = await enclave.run(`
const user = await callTool('getUser', { id: 123 });
const orders = await callTool('getOrders', { userId: user.id });
return { user, orderCount: orders.length };
`);
if (result.success) {
console.log('Result:', result.value);
} else {
console.error('Error:', result.error);
}
// Always dispose when done
enclave.dispose();
interface CreateEnclaveOptions {
// Execution limits
timeout?: number; // Execution timeout in ms (default: 5000)
maxToolCalls?: number; // Maximum tool calls allowed (default: 100)
maxIterations?: number; // Maximum loop iterations (default: 10000)
// Security
securityLevel?: SecurityLevel; // STRICT | SECURE | STANDARD | PERMISSIVE
validate?: boolean; // Enable AST validation (default: true)
transform?: boolean; // Enable code transformation (default: true)
// Tool execution
toolHandler?: ToolHandler; // Function to execute tool calls
// Custom globals
globals?: Record<string, unknown>; // Additional globals for the VM
allowFunctionsInGlobals?: boolean; // Allow functions in globals (default: false)
// Sidecar (large data handling)
sidecar?: ReferenceSidecarOptions;
// Double VM security layer
doubleVm?: PartialDoubleVmConfig; // See "Double VM Security Layer" section
}
| Level | Description | Use Case |
|---|---|---|
STRICT | Maximum security, absolute minimum globals | Untrusted code |
SECURE | High security with safe utility functions | Semi-trusted code |
STANDARD | Balanced security for most use cases | Trusted code |
PERMISSIVE | Adds console for debugging | Internal/development |
import { Enclave, SecurityLevel } from '@enclave-vm/core';
const enclave = new Enclave({
securityLevel: SecurityLevel.STRICT,
toolHandler: async (name, args) => {
/* ... */
},
});
Both AST validation and runtime sandbox enforce the same allowed globals for defense-in-depth:
| Global | STRICT | SECURE | STANDARD | PERMISSIVE |
|---|---|---|---|---|
| Core API | ||||
callTool | ✅ | ✅ | ✅ | ✅ |
| Data Types | ||||
Math | ✅ | ✅ | ✅ | ✅ |
JSON | ✅ | ✅ | ✅ | ✅ |
Array | ✅ | ✅ | ✅ | ✅ |
Object | ✅ | ✅ | ✅ | ✅ |
String | ✅ | ✅ | ✅ | ✅ |
Number | ✅ | ✅ | ✅ | ✅ |
Date | ✅ | ✅ | ✅ | ✅ |
| Constants | ||||
undefined | ✅ | ✅ | ✅ | ✅ |
NaN | ✅ | ✅ | ✅ | ✅ |
Infinity | ✅ | ✅ | ✅ | ✅ |
| Utility Functions | ||||
parseInt | ❌ | ✅ | ✅ | ✅ |
parseFloat | ❌ | ✅ | ✅ | ✅ |
isNaN | ❌ | ✅ | ✅ | ✅ |
isFinite | ❌ | ✅ | ✅ | ✅ |
encodeURI | ❌ | ✅ | ✅ | ✅ |
decodeURI | ❌ | ✅ | ✅ | ✅ |
encodeURIComponent | ❌ | ✅ | ✅ | ✅ |
decodeURIComponent | ❌ | ✅ | ✅ | ✅ |
| Debugging | ||||
console | ❌ | ❌ | ❌ | ✅ |
These dangerous globals are always blocked regardless of security level:
| Category | Blocked Globals |
|---|---|
| Code Execution | eval, Function, AsyncFunction, GeneratorFunction |
| System Access | process, require, module, exports, __dirname, __filename, Buffer |
| Global Objects | window, globalThis, self, global, this |
| Prototype | constructor, __proto__, prototype |
| Reflection | Proxy, Reflect |
| Async Flooding | Promise, setTimeout, setInterval, setImmediate, queueMicrotask |
| Network | fetch, XMLHttpRequest, WebSocket |
| Workers | Worker, SharedWorker, ServiceWorker |
| Memory Hazards | Map, Set, WeakMap, WeakSet, WeakRef, FinalizationRegistry |
| Binary/Wasm | WebAssembly, ArrayBuffer, SharedArrayBuffer, Atomics |
| Error Types | Error, TypeError, ReferenceError, SyntaxError, RangeError |
| Symbols/Regex | Symbol, RegExp |
The preset option controls AST-level validation - what JavaScript constructs are allowed before code transformation. This is separate from securityLevel which controls runtime security.
| Preset | Description | Custom Globals |
|---|---|---|
agentscript | Default. Safe subset for LLM-generated code | ✅ Supported |
strict | Maximum restrictions, blocks most JS features | ❌ Not supported |
secure | High security with essential features | ❌ Not supported |
standard | Balanced security for general use | ❌ Not supported |
permissive | Minimal AST restrictions | ❌ Not supported |
import { Enclave } from '@enclave-vm/core';
const enclave = new Enclave({
preset: 'agentscript', // AST validation preset (default)
securityLevel: SecurityLevel.STRICT, // Runtime security level
toolHandler: async (name, args) => {
/* ... */
},
});
Key Differences:
| Aspect | preset (AST) | securityLevel (Runtime) |
|---|---|---|
| When applied | Before execution | During execution |
| What it controls | JavaScript syntax/constructs | Resource limits, timeouts |
| Function expressions | Blocked in agentscript | N/A |
| Getter/setter syntax | Blocked in agentscript | N/A |
| Custom globals whitelist | Only agentscript preset | Always available at runtime |
Note: Only the agentscript preset supports the allowedGlobals option for whitelisting custom global identifiers in AST validation. Other presets use their own fixed rule sets.
For environments requiring OS-level memory isolation, enable the Worker Pool Adapter. This provides a dual-layer sandbox with hard halt capability:
import { Enclave } from '@enclave-vm/core';
const enclave = new Enclave({
adapter: 'worker_threads', // Enable Worker Pool
workerPoolConfig: {
minWorkers: 2,
maxWorkers: 8,
memoryLimitPerWorker: 256 * 1024 * 1024, // 256MB
},
toolHandler: async (name, args) => {
/* ... */
},
});
| Scenario | Recommendation |
|---|---|
| Trusted internal scripts | Standard VM (lower overhead) |
| Multi-tenant execution | Worker Pool (OS isolation) |
| Untrusted AI-generated code | Worker Pool (hard halt) |
| Memory-sensitive workloads | Worker Pool (per-worker limits) |
| Level | minWorkers | maxWorkers | memoryLimit | messagesPerSec |
|---|---|---|---|---|
| STRICT | 2 | 4 | 64MB | 100 |
| SECURE | 2 | 8 | 128MB | 500 |
| STANDARD | 2 | 16 | 256MB | 1000 |
| PERMISSIVE | 4 | 32 | 512MB | 5000 |
| Option | Default | Description |
|---|---|---|
minWorkers | 2 | Minimum warm workers |
maxWorkers | CPU count | Maximum concurrent workers |
memoryLimitPerWorker | 128MB | Per-worker memory limit |
maxMessagesPerSecond | 1000 | Rate limit per worker |
maxExecutionsPerWorker | 1000 | Recycle after N executions |
maxQueueSize | 100 | Maximum pending executions |
idleTimeoutMs | 30000 | Time before idle worker release |
When using Worker Pool, code runs in a dual-layer sandbox:
┌─────────────────────────────────────────────────┐
│ Worker Thread (OS-level isolation) │
│ ┌─────────────────────────────────────────────┐│
│ │ VM Context (prototype isolation) ││
│ │ - Whitelist-only globals ││
│ │ - __safe_* runtime functions ││
│ └─────────────────────────────────────────────┘│
│ - parentPort removed from globals │
│ - JSON-only message serialization │
│ - Hard halt via worker.terminate() │
└─────────────────────────────────────────────────┘
worker.terminate(): Hard halt runaway scripts when VM timeout fails--max-old-space-size: Per-worker memory limits enforced by V8The Double VM provides defense-in-depth by running user code in a nested VM structure. User code runs in an Inner VM which is isolated inside a Parent VM, providing enhanced protection against VM escape attacks.
const enclave = new Enclave({
toolHandler: async (name, args) => {
/* ... */
},
doubleVm: {
enabled: true, // Default: true
parentTimeoutBuffer: 1000, // Extra timeout buffer for parent VM (ms)
parentValidation: {
validateOperationNames: true, // Validate tool names against patterns
allowedOperationPattern: /^(db|api):[a-z]+$/i, // Whitelist pattern
blockedOperationPatterns: [/^shell:/i, /^system:/i], // Blacklist patterns
maxOperationsPerSecond: 100, // Rate limiting
blockSuspiciousSequences: true, // Enable suspicious pattern detection
rapidEnumerationThreshold: 30, // Max same-operation calls in 5s
rapidEnumerationOverrides: {
// Per-operation overrides for rapidEnumerationThreshold
search: 100, // Allow 100 search calls
'users.list': 50, // Allow 50 user list calls
},
},
},
});
| Option | Type | Default | Description |
|---|---|---|---|
validateOperationNames | boolean | true | Enable operation name validation |
allowedOperationPattern | RegExp | - | Whitelist pattern for operation names |
blockedOperationPatterns | RegExp[] | - | Blacklist patterns for operation names |
maxOperationsPerSecond | number | 100 | Rate limit for operations per second |
blockSuspiciousSequences | boolean | true | Enable suspicious pattern detection |
rapidEnumerationThreshold | number | 30 | Max same-operation calls in 5 seconds |
rapidEnumerationOverrides | Record<string,number> | {} | Per-operation thresholds (overrides default) |
suspiciousPatterns | SuspiciousPattern[] | [] | Custom suspicious pattern detectors |
When blockSuspiciousSequences: true, the following patterns are detected:
| Pattern ID | Description |
|---|---|
EXFIL_LIST_SEND | List/query followed by send/export (data exfil) |
RAPID_ENUMERATION | Same operation called too many times (configurable) |
CREDENTIAL_EXFIL | Credential access followed by external operation |
BULK_OPERATION | Bulk/batch operations (mass data extraction) |
DELETE_AFTER_ACCESS | Delete operation after data access (cover-up) |
The RAPID_ENUMERATION pattern blocks excessive repeated calls to the same operation. Use rapidEnumerationThreshold and rapidEnumerationOverrides to tune this:
const enclave = new Enclave({
toolHandler: async (name, args) => {
/* ... */
},
doubleVm: {
parentValidation: {
// Default: Block if same operation called >30 times in 5 seconds
rapidEnumerationThreshold: 30,
// Per-operation overrides
rapidEnumerationOverrides: {
search: 100, // Search can be called 100 times (pagination)
'db.query': 50, // Database queries up to 50 times
'users.filter': 200, // High-volume filtering allowed
},
},
},
});
You can add custom pattern detectors:
const enclave = new Enclave({
toolHandler: async (name, args) => {
/* ... */
},
doubleVm: {
parentValidation: {
suspiciousPatterns: [
{
id: 'CUSTOM_PATTERN',
description: 'Detect sensitive + external call',
detect: (operationName, args, history) => {
const hasSensitiveAccess = history.some((h) => h.operationName.includes('sensitive'));
const isExternalCall = operationName.includes('external');
return hasSensitiveAccess && isExternalCall;
},
},
],
},
},
});
The AI Scoring Gate provides semantic security analysis to detect attack patterns beyond what static AST validation can catch. It analyzes tool call patterns, sensitive data access, and exfiltration attempts.
const enclave = new Enclave({
toolHandler: async (name, args) => {
/* ... */
},
scoring: {
scorer: 'rule-based', // 'disabled' | 'rule-based' | 'local-llm' | 'external-api'
blockThreshold: 70, // Block if score >= 70
warnThreshold: 40, // Warn if score >= 40
},
});
| Type | Description | Latency |
|---|---|---|
disabled | No scoring (pass-through) | 0ms |
rule-based | Pure TypeScript rules, zero dependencies | ~1ms |
local-llm | On-device ML with HuggingFace transformers | ~5-10ms |
external-api | External API-based scoring | ~100ms |
For advanced use cases, you can provide a custom analyzer to integrate external LLMs or static code analyzers:
import { Enclave, CustomAnalyzer } from '@enclave-vm/core';
const myAnalyzer: CustomAnalyzer = {
async analyze(prompt, features) {
// Call your external LLM or static analyzer
const response = await fetch('https://my-analyzer-api.com/score', {
method: 'POST',
body: JSON.stringify({ prompt, features }),
});
const result = await response.json();
return {
score: result.riskScore,
signals: result.signals,
};
},
// Optional lifecycle methods
async initialize() {
/* connect to service */
},
dispose() {
/* cleanup */
},
};
const enclave = new Enclave({
toolHandler: async (name, args) => {
/* ... */
},
scoringGate: {
scorer: 'local-llm',
localLlm: {
modelId: 'Xenova/all-MiniLM-L6-v2',
customAnalyzer: myAnalyzer,
},
},
});
interface CustomAnalyzer {
// Analyze prompt and features, return risk score and signals
analyze(prompt: string, features: ExtractedFeatures): Promise<{ score: number; signals: RiskSignal[] }>;
// Optional: Initialize resources (e.g., connect to external service)
initialize?(): Promise<void>;
// Optional: Cleanup resources
dispose?(): void;
}
The Reference Sidecar is a powerful feature for handling large data in AgentScript without embedding it directly in the script. This is essential when:
__ref_abc123)const enclave = new Enclave({
toolHandler: async (name, args) => {
/* ... */
},
sidecar: {
enabled: true,
maxTotalSize: 10 * 1024 * 1024, // 10MB total storage
maxReferenceSize: 1 * 1024 * 1024, // 1MB per reference
extractionThreshold: 1024, // Extract strings > 1KB
maxResolvedSize: 5 * 1024 * 1024, // 5MB max resolved size
allowComposites: false, // Block string concatenation with refs
},
});
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable the sidecar feature |
maxTotalSize | number | 10MB | Maximum total size of all stored references |
maxReferenceSize | number | 1MB | Maximum size of a single reference |
extractionThreshold | number | 1024 | Minimum string size to extract to sidecar |
maxResolvedSize | number | 5MB | Maximum size when resolving references |
allowComposites | boolean | false | Allow string concatenation with reference tokens |
const enclave = new Enclave({
sidecar: {
enabled: true,
extractionThreshold: 100, // Extract strings > 100 bytes
allowComposites: false, // Block: ref + "suffix" (security measure)
},
toolHandler: async (name, args) => {
if (name === 'getLargeData') {
// Returns 1MB of data - automatically stored in sidecar
return { data: 'x'.repeat(1024 * 1024) };
}
},
});
const result = await enclave.run(`
// Large data is transparently handled via sidecar
const response = await callTool('getLargeData', {});
// Access properties - resolved on demand
return { hasData: response.data.length > 0 };
`);
The allowComposites: false setting (default) blocks string concatenation with reference tokens. This prevents attacks like:
// BLOCKED when allowComposites: false
const malicious = ref + '__proto__'; // Attempting prototype pollution
Set allowComposites: true only if you need to concatenate strings and understand the security implications.
You can provide custom globals to the VM:
const enclave = new Enclave({
globals: {
config: { apiUrl: 'https://api.example.com' },
helpers: {
formatDate: (date: Date) => date.toISOString(),
},
},
allowFunctionsInGlobals: true, // Required when globals contain functions
toolHandler: async (name, args) => {
/* ... */
},
});
const result = await enclave.run(`
const url = config.apiUrl;
const formatted = helpers.formatDate(new Date());
return { url, formatted };
`);
Security Note: Set allowFunctionsInGlobals: true only when you intentionally provide functions. Functions in globals can potentially leak host scope via closures.
interface ExecutionResult<T> {
success: boolean;
value?: T; // Result when success is true
error?: {
message: string;
name: string;
code?: string; // Error code (e.g., 'VALIDATION_ERROR', 'TIMEOUT')
stack?: string;
data?: Record<string, unknown>;
};
stats: {
duration: number; // Execution time in ms
toolCallCount: number; // Number of tool calls made
iterationCount: number; // Number of loop iterations
};
}
| Code | Description |
|---|---|
VALIDATION_ERROR | AST validation failed (dangerous code) |
TIMEOUT | Execution exceeded timeout |
MAX_TOOL_CALLS | Exceeded maximum tool calls |
MAX_ITERATIONS | Exceeded maximum loop iterations |
TOOL_ERROR | Tool handler threw an error |
RUNTIME_ERROR | General runtime error in script |
┌─────────────────────────────────────────────────────────────┐
│ User Code Input │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Layer 0: Pre-Scanner (ast-guard) │
│ ├── Blocks ReDoS patterns │
│ ├── Blocks BiDi/Trojan source attacks │
│ ├── Input size limits │
│ └── Nesting depth limits │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Layer 1: AST Validation (ast-guard) │
│ ├── NoEvalRule - Blocks eval(), Function() │
│ ├── NoGlobalAccessRule - Blocks dangerous globals │
│ ├── DisallowedIdentifierRule - Blocks reserved prefixes │
│ └── RequiredFunctionCallRule - Enforces callTool usage │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Layer 2: Code Transformation │
│ ├── Wraps in async function __ag_main() │
│ ├── callTool → __safe_callTool (tracked) │
│ ├── Loops → __safe_for, __safe_forOf (guarded) │
│ └── String literals → extracted to sidecar (if enabled) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Layer 3: AI Scoring Gate │
│ ├── Semantic security analysis via AST features │
│ ├── Exfiltration pattern detection │
│ ├── Sensitive field access tracking │
│ └── Risk scoring (0-100) with thresholds │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Layer 4: Runtime Sandbox │
│ ├── Standard: Node.js vm (prototype isolation) │
│ ├── Optional: Worker Pool (OS-level isolation) │
│ │ ├── Dual-layer: worker thread + VM context │
│ │ ├── Hard halt via worker.terminate() │
│ │ └── Per-worker memory limits │
│ ├── __safe_callTool - Enforces maxToolCalls │
│ ├── __safe_forOf - Enforces maxIterations │
│ └── Timeout enforcement │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Layer 5: Output Sanitization │
│ ├── Removes stack traces │
│ ├── Sanitizes file paths │
│ └── Truncates oversized outputs │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Safe Execution Result │
└─────────────────────────────────────────────────────────────┘
class Enclave {
constructor(options?: CreateEnclaveOptions);
// Execute AgentScript code
run<T = unknown>(code: string): Promise<ExecutionResult<T>>;
// Clean up resources
dispose(): void;
}
type ToolHandler = (toolName: string, args: Record<string, unknown>) => Promise<unknown>;
# Run tests
nx test enclave-vm
# Run tests with coverage
nx test enclave --coverage
ast-guard - AST validation rules used by enclaveApache-2.0
FAQs
Sandbox runtime for secure JavaScript code execution
We found that @enclave-vm/core demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
RubyGems and Bundler 4.0.13 introduced an opt-in cooldown feature that delays newly published gems during dependency resolution.

Security News
pnpm 11.5 now recognizes npm staged publish approvals in release metadata, preventing those releases from being mistaken for lower-trust package publishes.

Security News
Federal audit finds NIST lacked a plan to clear the NVD backlog, wasted funds on duplicate work, and delayed use of CISA data.