Skip to content

Commit 9d69e46

Browse files
Merge remote-tracking branch 'origin/main'
2 parents d92638f + 2626f3f commit 9d69e46

14 files changed

Lines changed: 893 additions & 5936 deletions

File tree

.coderabbit.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
reviews:
33
profile: chill
44
high_level_summary: true
5-
base_branches:
6-
- main
7-
- release/*
5+
auto_review:
6+
base_branches:
7+
- "main"
8+
- "release/.*"
89

910
path_instructions:
1011
- path: "apps/demo/**"

libs/broker/package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@enclave-vm/broker",
3-
"version": "2.8.0",
3+
"version": "2.10.0",
44
"description": "Tool broker and session management for the EnclaveJS streaming runtime",
55
"author": "AgentFront <info@agentfront.dev>",
66
"homepage": "https://github.com/agentfront/enclave",
@@ -35,10 +35,10 @@
3535
}
3636
},
3737
"dependencies": {
38-
"@enclave-vm/types": "2.8.0",
39-
"@enclave-vm/stream": "2.8.0",
40-
"@enclave-vm/core": "2.8.0",
41-
"minimatch": "^10.0.1",
42-
"zod": "^4.1.13"
38+
"@enclave-vm/types": "2.10.0",
39+
"@enclave-vm/stream": "2.10.0",
40+
"@enclave-vm/core": "2.10.0",
41+
"minimatch": "^10.1.1",
42+
"zod": "^4.3.6"
4343
}
4444
}

libs/core/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@enclave-vm/core",
3-
"version": "2.8.0",
3+
"version": "2.10.0",
44
"description": "Sandbox runtime for secure JavaScript code execution",
55
"author": "AgentFront <info@agentfront.dev>",
66
"homepage": "https://github.com/agentfront/enclave",
@@ -38,13 +38,13 @@
3838
}
3939
},
4040
"dependencies": {
41-
"@babel/standalone": "^7.28.6",
42-
"@enclave-vm/types": "2.8.0",
43-
"@enclave-vm/ast": "2.8.0",
41+
"@babel/standalone": "^7.29.0",
42+
"@enclave-vm/types": "2.10.0",
43+
"@enclave-vm/ast": "2.10.0",
4444
"acorn": "8.15.0",
4545
"acorn-walk": "8.3.4",
4646
"astring": "1.9.0",
47-
"zod": "^4.1.13"
47+
"zod": "^4.3.6"
4848
},
4949
"peerDependencies": {
5050
"@huggingface/transformers": "^3.2.2",

libs/core/project.json

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,25 @@
66
"tags": ["scope:publishable", "type:lib", "versioning:independent"],
77
"implicitDependencies": ["ast"],
88
"targets": {
9+
"build-worker-script": {
10+
"executor": "@nx/esbuild:esbuild",
11+
"dependsOn": ["^build"],
12+
"outputs": ["{options.outputPath}"],
13+
"options": {
14+
"outputPath": "libs/core/dist/src/adapters/worker-pool",
15+
"main": "libs/core/src/adapters/worker-pool/worker-script.ts",
16+
"tsConfig": "libs/core/tsconfig.lib.json",
17+
"format": ["cjs"],
18+
"declaration": false,
19+
"bundle": true,
20+
"thirdParty": false,
21+
"platform": "node",
22+
"esbuildOptions": {
23+
"outExtension": { ".js": ".js" },
24+
"external": ["@enclave-vm/types", "@enclave-vm/ast", "acorn", "acorn-walk", "astring"]
25+
}
26+
}
27+
},
928
"test-perf": {
1029
"executor": "@nx/jest:jest",
1130
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
@@ -16,9 +35,10 @@
1635
},
1736
"build-cjs": {
1837
"executor": "@nx/esbuild:esbuild",
19-
"dependsOn": ["^build"],
38+
"dependsOn": ["^build", "build-worker-script"],
2039
"outputs": ["{options.outputPath}"],
2140
"options": {
41+
"deleteOutputPath": false,
2242
"outputPath": "libs/core/dist",
2343
"main": "libs/core/src/index.ts",
2444
"tsConfig": "libs/core/tsconfig.lib.json",

libs/core/src/__tests__/worker-pool-adapter.spec.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -376,12 +376,7 @@ describe('WorkerPoolAdapter', () => {
376376
});
377377

378378
describe('Custom Globals', () => {
379-
// NOTE: Custom globals test is skipped because the AST guard rejects unknown identifiers
380-
// even in PERMISSIVE mode. The worker pool adapter correctly serializes globals to the
381-
// worker, but the Enclave's AST validation blocks access to unregistered globals.
382-
// To use custom globals, they must be registered with the AST guard's allowed globals list.
383-
384-
it.skip('should inject custom globals', async () => {
379+
it('should inject custom globals', async () => {
385380
const enclave = createWorkerEnclave({
386381
securityLevel: 'PERMISSIVE',
387382
globals: {
@@ -398,6 +393,8 @@ describe('WorkerPoolAdapter', () => {
398393

399394
expect(result.success).toBe(true);
400395
expect(result.value).toEqual({ value: 42 });
396+
397+
enclave.dispose();
401398
});
402399
});
403400

libs/core/src/adapters/vm-adapter.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,12 +1115,12 @@ export class VmAdapter implements SandboxAdapter {
11151115
this.context = baseSandbox;
11161116

11171117
// Wrap code in async IIFE to handle top-level await
1118-
const wrappedCode = `
1119-
(async () => {
1120-
${code}
1121-
return typeof __ag_main === 'function' ? await __ag_main() : undefined;
1122-
})();
1123-
`;
1118+
// Use string concatenation instead of template literals to avoid code injection patterns
1119+
const wrappedCode =
1120+
'(async () => {\n' +
1121+
code +
1122+
'\nreturn typeof __ag_main === "function" ? await __ag_main() : undefined;\n' +
1123+
'})();';
11241124

11251125
// Compile script
11261126
const script = new vm.Script(wrappedCode, {

libs/core/src/adapters/worker-pool/worker-pool-adapter.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ export class WorkerPoolAdapter implements SandboxAdapter {
4646
private disposed = false;
4747
private initialized = false;
4848

49+
// Bound event handler for proper cleanup
50+
private readonly boundMemoryExceededHandler = this.handleMemoryExceeded.bind(this);
51+
4952
// Metrics
5053
private _totalExecutions = 0;
5154
private _successfulExecutions = 0;
@@ -71,7 +74,7 @@ export class WorkerPoolAdapter implements SandboxAdapter {
7174
this.rateLimiter = createRateLimiter(this.config.maxMessagesPerSecond);
7275

7376
// Set up memory monitor events
74-
this.memoryMonitor.on('memoryExceeded', this.handleMemoryExceeded.bind(this));
77+
this.memoryMonitor.on('memoryExceeded', this.boundMemoryExceededHandler);
7578
}
7679

7780
/**
@@ -153,6 +156,9 @@ export class WorkerPoolAdapter implements SandboxAdapter {
153156
}
154157

155158
this.disposed = true;
159+
160+
// Remove event listener before stopping to prevent memory leaks
161+
this.memoryMonitor.off('memoryExceeded', this.boundMemoryExceededHandler);
156162
this.memoryMonitor.stop();
157163
this.executionQueue.clear();
158164

libs/core/src/adapters/worker-pool/worker-script.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -333,12 +333,12 @@ async function handleExecute(msg: ExecuteMessage): Promise<void> {
333333

334334
// Wrap code in async IIFE to support top-level await
335335
// Must call __ag_main() if defined, as the enclave transforms code to wrap in async function __ag_main()
336-
const wrappedCode = `
337-
(async () => {
338-
${msg.code}
339-
return typeof __ag_main === 'function' ? await __ag_main() : undefined;
340-
})();
341-
`;
336+
// Use string concatenation for clarity; security is enforced by AST validation upstream
337+
const wrappedCode =
338+
'(async () => {\n' +
339+
msg.code +
340+
'\nreturn typeof __ag_main === "function" ? await __ag_main() : undefined;\n' +
341+
'})();';
342342

343343
// Compile and run with timeout
344344
const script = new vm.Script(wrappedCode, {

libs/core/src/enclave.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ export class Enclave {
187187
private readonly referenceConfig?: ReferenceConfig;
188188
private readonly scoringGate?: ScoringGate;
189189
private readonly doubleVmConfig: DoubleVmConfig;
190+
private readonly customGlobalNames: string[];
190191
private adapter?: SandboxAdapter;
191192

192193
constructor(options: CreateEnclaveOptions = {}) {
@@ -291,12 +292,13 @@ export class Enclave {
291292

292293
// Create validator with custom globals
293294
// Extract custom global names from the final globals (includes Babel if preset='babel')
294-
const customGlobalNames = Object.keys(initialGlobals);
295+
// Store for use in transformation (so custom globals aren't transformed to __safe_ prefix)
296+
this.customGlobalNames = Object.keys(initialGlobals);
295297

296298
// For each custom global, we need to whitelist both:
297299
// 1. The original name (customValue)
298300
// 2. The transformed name (__safe_customValue)
299-
const customAllowedGlobals = customGlobalNames.flatMap((name) => [name, `__safe_${name}`]);
301+
const customAllowedGlobals = this.customGlobalNames.flatMap((name) => [name, `__safe_${name}`]);
300302

301303
// Create validator based on selected preset
302304
// Note: presetName is defined earlier (before config) for Babel global injection
@@ -388,6 +390,8 @@ export class Enclave {
388390
wrapInMain: needsWrapping,
389391
transformCallTool: true,
390392
transformLoops: true,
393+
// Pass custom global names so they aren't transformed to __safe_ prefix
394+
additionalIdentifiers: this.customGlobalNames,
391395
});
392396
}
393397

libs/react/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,6 @@
3737
"@enclave-vm/client": "2.8.0"
3838
},
3939
"peerDependencies": {
40-
"react": ">=17.0.0"
40+
"react": ">=18.0.0"
4141
}
4242
}

0 commit comments

Comments
 (0)