@ruifung/codemode-bridge 1.0.3-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +378 -0
- package/dist/cli/commands.d.ts +70 -0
- package/dist/cli/commands.js +436 -0
- package/dist/cli/config-manager.d.ts +53 -0
- package/dist/cli/config-manager.js +142 -0
- package/dist/cli/index.d.ts +19 -0
- package/dist/cli/index.js +165 -0
- package/dist/executor/container-executor.d.ts +81 -0
- package/dist/executor/container-executor.js +351 -0
- package/dist/executor/executor-test-suite.d.ts +22 -0
- package/dist/executor/executor-test-suite.js +395 -0
- package/dist/executor/isolated-vm-executor.d.ts +78 -0
- package/dist/executor/isolated-vm-executor.js +368 -0
- package/dist/executor/vm2-executor.d.ts +21 -0
- package/dist/executor/vm2-executor.js +109 -0
- package/dist/executor/wrap-code.d.ts +52 -0
- package/dist/executor/wrap-code.js +80 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/mcp/config.d.ts +44 -0
- package/dist/mcp/config.js +102 -0
- package/dist/mcp/e2e-bridge-test-suite.d.ts +28 -0
- package/dist/mcp/e2e-bridge-test-suite.js +429 -0
- package/dist/mcp/executor.d.ts +31 -0
- package/dist/mcp/executor.js +121 -0
- package/dist/mcp/mcp-adapter.d.ts +12 -0
- package/dist/mcp/mcp-adapter.js +49 -0
- package/dist/mcp/mcp-client.d.ts +85 -0
- package/dist/mcp/mcp-client.js +441 -0
- package/dist/mcp/oauth-handler.d.ts +33 -0
- package/dist/mcp/oauth-handler.js +138 -0
- package/dist/mcp/server.d.ts +25 -0
- package/dist/mcp/server.js +322 -0
- package/dist/mcp/token-persistence.d.ts +57 -0
- package/dist/mcp/token-persistence.js +131 -0
- package/dist/utils/logger.d.ts +44 -0
- package/dist/utils/logger.js +123 -0
- package/package.json +56 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
|
|
2
|
+
export function createExecutorTestSuite(name, createExecutor, options) {
|
|
3
|
+
const skipSet = new Set(options?.skipTests ?? []);
|
|
4
|
+
/** Use it.skip for tests in the skip list, it otherwise */
|
|
5
|
+
const testOrSkip = (testName, fn) => {
|
|
6
|
+
if (skipSet.has(testName)) {
|
|
7
|
+
it.skip(testName, fn);
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
it(testName, fn, options?.testTimeout);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
describe(`Executor: ${name}`, () => {
|
|
14
|
+
let executor;
|
|
15
|
+
beforeAll(async () => {
|
|
16
|
+
executor = createExecutor();
|
|
17
|
+
// If executor has a lazy init (e.g. container), trigger it now so the
|
|
18
|
+
// startup cost is not counted against the first test's timeout.
|
|
19
|
+
if ('init' in executor && typeof executor.init === 'function') {
|
|
20
|
+
await executor.init();
|
|
21
|
+
}
|
|
22
|
+
}, options?.testTimeout ? options.testTimeout * 2 : undefined);
|
|
23
|
+
afterAll(async () => {
|
|
24
|
+
// Cleanup if executor has dispose method
|
|
25
|
+
if ('dispose' in executor && typeof executor.dispose === 'function') {
|
|
26
|
+
await Promise.resolve(executor.dispose());
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
describe('Basic Execution', () => {
|
|
30
|
+
testOrSkip('should execute simple arithmetic', async () => {
|
|
31
|
+
const result = await executor.execute('return 1 + 2;', {});
|
|
32
|
+
expect(result.result).toBe(3);
|
|
33
|
+
expect(result.error).toBeUndefined();
|
|
34
|
+
});
|
|
35
|
+
testOrSkip('should execute async code', async () => {
|
|
36
|
+
const result = await executor.execute(`
|
|
37
|
+
const promise = new Promise(resolve =>
|
|
38
|
+
setTimeout(() => resolve(42), 10)
|
|
39
|
+
);
|
|
40
|
+
return await promise;
|
|
41
|
+
`, {});
|
|
42
|
+
expect(result.result).toBe(42);
|
|
43
|
+
});
|
|
44
|
+
testOrSkip('should return undefined for no return statement', async () => {
|
|
45
|
+
const result = await executor.execute('const x = 5;', {});
|
|
46
|
+
expect(result.result).toBeUndefined();
|
|
47
|
+
});
|
|
48
|
+
testOrSkip('should handle string returns', async () => {
|
|
49
|
+
const result = await executor.execute('return "hello";', {});
|
|
50
|
+
expect(result.result).toBe('hello');
|
|
51
|
+
});
|
|
52
|
+
testOrSkip('should handle object returns', async () => {
|
|
53
|
+
const result = await executor.execute('return { a: 1, b: "two", c: [1, 2, 3] };', {});
|
|
54
|
+
expect(result.result).toEqual({
|
|
55
|
+
a: 1,
|
|
56
|
+
b: 'two',
|
|
57
|
+
c: [1, 2, 3],
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
describe('Console Logging', () => {
|
|
62
|
+
testOrSkip('should capture console.log output', async () => {
|
|
63
|
+
const result = await executor.execute(`
|
|
64
|
+
console.log('Hello');
|
|
65
|
+
console.log('World');
|
|
66
|
+
return 'done';
|
|
67
|
+
`, {});
|
|
68
|
+
expect(result.logs).toContain('Hello');
|
|
69
|
+
expect(result.logs).toContain('World');
|
|
70
|
+
});
|
|
71
|
+
testOrSkip('should capture multiple arguments', async () => {
|
|
72
|
+
const result = await executor.execute(`console.log('Result:', 42, { key: 'value' });`, {});
|
|
73
|
+
expect(result.logs?.[0]).toContain('Result');
|
|
74
|
+
expect(result.logs?.[0]).toContain('42');
|
|
75
|
+
});
|
|
76
|
+
testOrSkip('should not include logs key when empty', async () => {
|
|
77
|
+
const result = await executor.execute('return 5;', {});
|
|
78
|
+
expect(result.logs).toBeUndefined();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
describe('Error Handling', () => {
|
|
82
|
+
testOrSkip('should catch syntax errors', async () => {
|
|
83
|
+
const result = await executor.execute('this is not valid js', {});
|
|
84
|
+
expect(result.error).toBeDefined();
|
|
85
|
+
expect(result.result).toBeUndefined();
|
|
86
|
+
});
|
|
87
|
+
testOrSkip('should catch runtime errors', async () => {
|
|
88
|
+
const result = await executor.execute('throw new Error("Test error");', {});
|
|
89
|
+
expect(result.error).toContain('Test error');
|
|
90
|
+
expect(result.result).toBeUndefined();
|
|
91
|
+
});
|
|
92
|
+
testOrSkip('should catch reference errors', async () => {
|
|
93
|
+
const result = await executor.execute('return nonExistentVariable;', {});
|
|
94
|
+
expect(result.error).toBeDefined();
|
|
95
|
+
});
|
|
96
|
+
testOrSkip('should not throw exceptions, return them', async () => {
|
|
97
|
+
// Verify that the executor doesn't throw, only returns errors
|
|
98
|
+
const promise = executor.execute('throw new Error("Should not throw");', {});
|
|
99
|
+
await expect(promise).resolves.toHaveProperty('error');
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
describe('Tool Invocation', () => {
|
|
103
|
+
testOrSkip('should invoke tool functions', async () => {
|
|
104
|
+
const mockTool = vi.fn(async () => 42);
|
|
105
|
+
const result = await executor.execute('return await codemode.myTool();', { myTool: mockTool });
|
|
106
|
+
expect(result.result).toBe(42);
|
|
107
|
+
expect(mockTool).toHaveBeenCalled();
|
|
108
|
+
});
|
|
109
|
+
testOrSkip('should pass arguments to tool functions', async () => {
|
|
110
|
+
const mockTool = vi.fn(async (a, b) => a + b);
|
|
111
|
+
const result = await executor.execute('return await codemode.add(5, 3);', { add: mockTool });
|
|
112
|
+
expect(result.result).toBe(8);
|
|
113
|
+
expect(mockTool).toHaveBeenCalledWith(5, 3);
|
|
114
|
+
});
|
|
115
|
+
testOrSkip('should support tool with object arguments', async () => {
|
|
116
|
+
const mockTool = vi.fn(async (opts) => `${opts.name}=${opts.value}`);
|
|
117
|
+
const result = await executor.execute('return await codemode.format({ name: "test", value: 123 });', { format: mockTool });
|
|
118
|
+
expect(result.result).toBe('test=123');
|
|
119
|
+
expect(mockTool).toHaveBeenCalledWith({
|
|
120
|
+
name: 'test',
|
|
121
|
+
value: 123,
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
testOrSkip('should handle async tool errors', async () => {
|
|
125
|
+
const mockTool = vi.fn(async () => {
|
|
126
|
+
throw new Error('Tool failed');
|
|
127
|
+
});
|
|
128
|
+
const result = await executor.execute(`
|
|
129
|
+
try {
|
|
130
|
+
await codemode.failingTool();
|
|
131
|
+
return 'should not reach here';
|
|
132
|
+
} catch (e) {
|
|
133
|
+
return 'caught: ' + e.message;
|
|
134
|
+
}
|
|
135
|
+
`, { failingTool: mockTool });
|
|
136
|
+
expect(result.result).toContain('caught');
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
describe('Complex Code Patterns', () => {
|
|
140
|
+
testOrSkip('should handle for loops', async () => {
|
|
141
|
+
const result = await executor.execute(`
|
|
142
|
+
let sum = 0;
|
|
143
|
+
for (let i = 0; i < 10; i++) {
|
|
144
|
+
sum += i;
|
|
145
|
+
}
|
|
146
|
+
return sum;
|
|
147
|
+
`, {});
|
|
148
|
+
expect(result.result).toBe(45);
|
|
149
|
+
});
|
|
150
|
+
testOrSkip('should handle map/filter/reduce', async () => {
|
|
151
|
+
const result = await executor.execute(`
|
|
152
|
+
const arr = [1, 2, 3, 4, 5];
|
|
153
|
+
return arr
|
|
154
|
+
.filter(x => x > 2)
|
|
155
|
+
.map(x => x * 2)
|
|
156
|
+
.reduce((a, b) => a + b, 0);
|
|
157
|
+
`, {});
|
|
158
|
+
expect(result.result).toBe(24); // (3+4+5) * 2 = 24
|
|
159
|
+
});
|
|
160
|
+
testOrSkip('should handle try-catch', async () => {
|
|
161
|
+
const result = await executor.execute(`
|
|
162
|
+
try {
|
|
163
|
+
throw new Error('Caught!');
|
|
164
|
+
} catch (e) {
|
|
165
|
+
return e.message;
|
|
166
|
+
}
|
|
167
|
+
`, {});
|
|
168
|
+
expect(result.result).toBe('Caught!');
|
|
169
|
+
});
|
|
170
|
+
testOrSkip('should handle class definitions', async () => {
|
|
171
|
+
const result = await executor.execute(`
|
|
172
|
+
class Counter {
|
|
173
|
+
constructor(start = 0) {
|
|
174
|
+
this.count = start;
|
|
175
|
+
}
|
|
176
|
+
increment() {
|
|
177
|
+
return ++this.count;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const c = new Counter(5);
|
|
181
|
+
return c.increment();
|
|
182
|
+
`, {});
|
|
183
|
+
expect(result.result).toBe(6);
|
|
184
|
+
});
|
|
185
|
+
testOrSkip('should handle async/await chains', async () => {
|
|
186
|
+
const mockFetch = vi.fn(async (id) => ({ id, name: `Item ${id}` }));
|
|
187
|
+
const result = await executor.execute(`
|
|
188
|
+
const item1 = await codemode.fetch(1);
|
|
189
|
+
const item2 = await codemode.fetch(2);
|
|
190
|
+
return [item1, item2];
|
|
191
|
+
`, { fetch: mockFetch });
|
|
192
|
+
expect(Array.isArray(result.result)).toBe(true);
|
|
193
|
+
expect(result.result).toHaveLength(2);
|
|
194
|
+
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
describe('Isolation & Safety', () => {
|
|
198
|
+
testOrSkip('should not allow access to require', async () => {
|
|
199
|
+
const result = await executor.execute('return require("fs");', {});
|
|
200
|
+
expect(result.error).toBeDefined();
|
|
201
|
+
});
|
|
202
|
+
testOrSkip('should not allow process access', async () => {
|
|
203
|
+
const result = await executor.execute('return process.env;', {});
|
|
204
|
+
expect(result.error).toBeDefined();
|
|
205
|
+
});
|
|
206
|
+
testOrSkip('should not allow eval', async () => {
|
|
207
|
+
const result = await executor.execute('return eval("1+1");', {});
|
|
208
|
+
expect(result.error).toBeDefined();
|
|
209
|
+
});
|
|
210
|
+
testOrSkip('should not allow constructor to escape', async () => {
|
|
211
|
+
const result = await executor.execute('return (function(){}).constructor("return process")();', {});
|
|
212
|
+
expect(result.error).toBeDefined();
|
|
213
|
+
});
|
|
214
|
+
testOrSkip('should not allow network access', async () => {
|
|
215
|
+
// All executors must prevent outbound network access:
|
|
216
|
+
// - vm2/isolated-vm: no require/fetch/net globals available
|
|
217
|
+
// - container: --network=none blocks at OS level
|
|
218
|
+
const result = await executor.execute(`
|
|
219
|
+
// Try multiple network vectors
|
|
220
|
+
if (typeof fetch === 'function') {
|
|
221
|
+
await fetch('https://example.com');
|
|
222
|
+
return 'network allowed via fetch';
|
|
223
|
+
}
|
|
224
|
+
if (typeof require === 'function') {
|
|
225
|
+
const http = require('http');
|
|
226
|
+
await new Promise((resolve, reject) => {
|
|
227
|
+
http.get('http://example.com', resolve).on('error', reject);
|
|
228
|
+
});
|
|
229
|
+
return 'network allowed via http';
|
|
230
|
+
}
|
|
231
|
+
// If neither is available, that itself is blocking network access
|
|
232
|
+
throw new Error('no network APIs available');
|
|
233
|
+
`, {});
|
|
234
|
+
// Must either error or return no success indicator
|
|
235
|
+
if (result.error) {
|
|
236
|
+
// Good — network was blocked or APIs unavailable
|
|
237
|
+
expect(result.error).toBeDefined();
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
// Should never reach here with 'network allowed' messages
|
|
241
|
+
expect(result.result).not.toBe('network allowed via fetch');
|
|
242
|
+
expect(result.result).not.toBe('network allowed via http');
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
testOrSkip('should not allow low-level socket network access', async () => {
|
|
246
|
+
// Validates that raw TCP/UDP socket APIs are blocked.
|
|
247
|
+
// - vm2/isolated-vm: require/net/dgram not available
|
|
248
|
+
// - container: --network=none blocks at OS level even if APIs exist
|
|
249
|
+
const result = await executor.execute(`
|
|
250
|
+
if (typeof require === 'function') {
|
|
251
|
+
// Try net.Socket (TCP)
|
|
252
|
+
try {
|
|
253
|
+
const net = require('net');
|
|
254
|
+
await new Promise((resolve, reject) => {
|
|
255
|
+
const sock = new net.Socket();
|
|
256
|
+
sock.setTimeout(2000);
|
|
257
|
+
sock.on('error', reject);
|
|
258
|
+
sock.on('timeout', () => reject(new Error('timeout')));
|
|
259
|
+
sock.connect(80, '1.1.1.1', resolve);
|
|
260
|
+
});
|
|
261
|
+
return 'network allowed via net.Socket';
|
|
262
|
+
} catch (e) {}
|
|
263
|
+
|
|
264
|
+
// Try dgram (UDP)
|
|
265
|
+
try {
|
|
266
|
+
const dgram = require('dgram');
|
|
267
|
+
const sock = dgram.createSocket('udp4');
|
|
268
|
+
await new Promise((resolve, reject) => {
|
|
269
|
+
sock.on('error', reject);
|
|
270
|
+
sock.send('ping', 53, '1.1.1.1', (err) => {
|
|
271
|
+
sock.close();
|
|
272
|
+
err ? reject(err) : resolve();
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
return 'network allowed via dgram';
|
|
276
|
+
} catch (e) {}
|
|
277
|
+
|
|
278
|
+
// Try tls.connect
|
|
279
|
+
try {
|
|
280
|
+
const tls = require('tls');
|
|
281
|
+
await new Promise((resolve, reject) => {
|
|
282
|
+
const sock = tls.connect(443, '1.1.1.1', {}, resolve);
|
|
283
|
+
sock.on('error', reject);
|
|
284
|
+
});
|
|
285
|
+
return 'network allowed via tls';
|
|
286
|
+
} catch (e) {}
|
|
287
|
+
|
|
288
|
+
// Try dns.resolve
|
|
289
|
+
try {
|
|
290
|
+
const dns = require('dns');
|
|
291
|
+
await new Promise((resolve, reject) => {
|
|
292
|
+
dns.resolve('example.com', (err, addresses) => {
|
|
293
|
+
err ? reject(err) : resolve(addresses);
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
return 'network allowed via dns';
|
|
297
|
+
} catch (e) {}
|
|
298
|
+
|
|
299
|
+
// All low-level socket APIs failed — network is blocked
|
|
300
|
+
throw new Error('all socket APIs blocked by network isolation');
|
|
301
|
+
}
|
|
302
|
+
// No require means no access to socket APIs at all
|
|
303
|
+
throw new Error('no require available');
|
|
304
|
+
`, {});
|
|
305
|
+
if (result.error) {
|
|
306
|
+
expect(result.error).toBeDefined();
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
expect(result.result).not.toBe('network allowed via net.Socket');
|
|
310
|
+
expect(result.result).not.toBe('network allowed via dgram');
|
|
311
|
+
expect(result.result).not.toBe('network allowed via tls');
|
|
312
|
+
expect(result.result).not.toBe('network allowed via dns');
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
testOrSkip('should isolate prototype pollution to the current execution', async () => {
|
|
316
|
+
// NOTE: Prototype pollution IS possible within a single execution,
|
|
317
|
+
// but it is NOT a security risk because:
|
|
318
|
+
// 1. Each execution gets a fresh sandbox with clean prototypes
|
|
319
|
+
// 2. Pollution only affects that specific execution's globals
|
|
320
|
+
// 3. Host code is protected by serialization boundaries (JSON.stringify)
|
|
321
|
+
// 4. Next execution runs in a completely new sandbox
|
|
322
|
+
// Test 1: Pollution works within execution (expected)
|
|
323
|
+
const result1 = await executor.execute(`
|
|
324
|
+
Object.assign(Object.prototype, { polluted: true });
|
|
325
|
+
const obj = {};
|
|
326
|
+
return obj.polluted;
|
|
327
|
+
`, {});
|
|
328
|
+
expect(result1.result).toBe(true); // Pollution works within this execution
|
|
329
|
+
// Test 2: Next execution has clean prototypes (this proves isolation)
|
|
330
|
+
const result2 = await executor.execute(`
|
|
331
|
+
const obj = {};
|
|
332
|
+
return obj.polluted;
|
|
333
|
+
`, {});
|
|
334
|
+
expect(result2.result).toBeUndefined(); // Fresh sandbox, no pollution
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
describe('Concurrency', () => {
|
|
338
|
+
testOrSkip('should handle multiple concurrent executions', async () => {
|
|
339
|
+
const promises = Array.from({ length: 5 }, (_, i) => executor.execute(`return ${i * 10};`, {}));
|
|
340
|
+
const results = await Promise.all(promises);
|
|
341
|
+
results.forEach((result, i) => {
|
|
342
|
+
expect(result.result).toBe(i * 10);
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
testOrSkip('should isolate data between concurrent executions', async () => {
|
|
346
|
+
const promises = Array.from({ length: 3 }, (_, i) => executor.execute(`
|
|
347
|
+
global.sharedValue = ${i};
|
|
348
|
+
return global.sharedValue;
|
|
349
|
+
`, {}));
|
|
350
|
+
const results = await Promise.all(promises);
|
|
351
|
+
// Each should return its own value, not shared
|
|
352
|
+
results.forEach((result, i) => {
|
|
353
|
+
expect(result.result).toBe(i);
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
describe('Performance Baseline', () => {
|
|
358
|
+
testOrSkip('should execute simple code quickly', async () => {
|
|
359
|
+
const start = performance.now();
|
|
360
|
+
await executor.execute('return 1 + 1;', {});
|
|
361
|
+
const elapsed = performance.now() - start;
|
|
362
|
+
// Simple execution should be <100ms
|
|
363
|
+
expect(elapsed).toBeLessThan(100);
|
|
364
|
+
});
|
|
365
|
+
testOrSkip('should handle multiple sequential executions', async () => {
|
|
366
|
+
const start = performance.now();
|
|
367
|
+
for (let i = 0; i < 10; i++) {
|
|
368
|
+
await executor.execute(`return ${i};`, {});
|
|
369
|
+
}
|
|
370
|
+
const elapsed = performance.now() - start;
|
|
371
|
+
// 10 executions should be <500ms total
|
|
372
|
+
expect(elapsed).toBeLessThan(500);
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
// Run the test suite against both executors if available
|
|
378
|
+
if (require.main === module) {
|
|
379
|
+
// Import and run against vm2
|
|
380
|
+
try {
|
|
381
|
+
const { createVM2Executor } = require('./vm2-executor');
|
|
382
|
+
createExecutorTestSuite('vm2', () => createVM2Executor());
|
|
383
|
+
}
|
|
384
|
+
catch (e) {
|
|
385
|
+
console.log('vm2 executor not available');
|
|
386
|
+
}
|
|
387
|
+
// Import and run against isolated-vm
|
|
388
|
+
try {
|
|
389
|
+
const { createIsolatedVmExecutor } = require('./isolated-vm-executor');
|
|
390
|
+
createExecutorTestSuite('isolated-vm', () => createIsolatedVmExecutor({ memoryLimit: 256 }));
|
|
391
|
+
}
|
|
392
|
+
catch (e) {
|
|
393
|
+
console.log('isolated-vm executor not available');
|
|
394
|
+
}
|
|
395
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { Executor, ExecuteResult } from '@cloudflare/codemode';
|
|
2
|
+
export interface IsolatedVmExecutorOptions {
|
|
3
|
+
/** Memory limit in MB (default: 128) */
|
|
4
|
+
memoryLimit?: number;
|
|
5
|
+
/** Enable inspector (default: false) */
|
|
6
|
+
inspector?: boolean;
|
|
7
|
+
/** Execution timeout in ms (default: 30000) */
|
|
8
|
+
timeout?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface ExecutionMetrics {
|
|
11
|
+
cpuTime: bigint;
|
|
12
|
+
wallTime: bigint;
|
|
13
|
+
heapUsed: number;
|
|
14
|
+
heapLimit: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Executor implementation using isolated-vm
|
|
18
|
+
*
|
|
19
|
+
* Architecture: Uses Promise chain pattern
|
|
20
|
+
* - Async IIFE is executed in the isolate
|
|
21
|
+
* - Its returned Promise is chained with .then() inside the isolate
|
|
22
|
+
* - The .then handlers call sync Callbacks on the host side
|
|
23
|
+
* - Host Promise resolves when the callback is invoked
|
|
24
|
+
*
|
|
25
|
+
* Features:
|
|
26
|
+
* - True memory isolation (V8 Isolate level)
|
|
27
|
+
* - Hard memory limit enforcement
|
|
28
|
+
* - Accurate timeout handling
|
|
29
|
+
* - Promise-based result capturing
|
|
30
|
+
*
|
|
31
|
+
* Security:
|
|
32
|
+
* - Separate V8 memory heap
|
|
33
|
+
* - No shared memory access
|
|
34
|
+
* - Explicit serialization boundaries
|
|
35
|
+
*/
|
|
36
|
+
export declare class IsolatedVmExecutor implements Executor {
|
|
37
|
+
private isolate;
|
|
38
|
+
private context;
|
|
39
|
+
private metrics;
|
|
40
|
+
private readonly options;
|
|
41
|
+
constructor(options?: IsolatedVmExecutorOptions);
|
|
42
|
+
/**
|
|
43
|
+
* Execute code within a sandboxed isolate using Promise chain pattern
|
|
44
|
+
*
|
|
45
|
+
* The function `fns` are made available as `codemode.*` within the sandbox.
|
|
46
|
+
* Uses Promise chain: async IIFE → .then() in isolate → sync callback → host Promise
|
|
47
|
+
*
|
|
48
|
+
* Returns ExecuteResult with:
|
|
49
|
+
* - result: The return value of the code
|
|
50
|
+
* - error: Error message if execution failed
|
|
51
|
+
* - logs: Array of console.log outputs
|
|
52
|
+
*/
|
|
53
|
+
execute(code: string, fns: Record<string, (...args: unknown[]) => Promise<unknown>>): Promise<ExecuteResult>;
|
|
54
|
+
/**
|
|
55
|
+
* Cleanup resources
|
|
56
|
+
*/
|
|
57
|
+
dispose(): void;
|
|
58
|
+
/**
|
|
59
|
+
* Get heap statistics from the isolate
|
|
60
|
+
*/
|
|
61
|
+
getHeapStatistics(): Promise<{
|
|
62
|
+
total: number;
|
|
63
|
+
used: number;
|
|
64
|
+
limit: number;
|
|
65
|
+
}>;
|
|
66
|
+
/**
|
|
67
|
+
* Create or recreate context (resets globals for isolation)
|
|
68
|
+
*/
|
|
69
|
+
private createContext;
|
|
70
|
+
/**
|
|
71
|
+
* Capture execution metrics
|
|
72
|
+
*/
|
|
73
|
+
private captureMetrics;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Factory function to create an isolated-vm executor instance
|
|
77
|
+
*/
|
|
78
|
+
export declare function createIsolatedVmExecutor(options?: IsolatedVmExecutorOptions): Executor;
|