@cogitator-ai/self-modifying 17.0.17 → 18.0.0
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/README.md +112 -48
- package/dist/architecture-evolution/capability-analyzer.d.ts.map +1 -1
- package/dist/architecture-evolution/capability-analyzer.js +44 -40
- package/dist/architecture-evolution/capability-analyzer.js.map +1 -1
- package/dist/architecture-evolution/evolution-strategy.d.ts.map +1 -1
- package/dist/architecture-evolution/evolution-strategy.js +4 -2
- package/dist/architecture-evolution/evolution-strategy.js.map +1 -1
- package/dist/architecture-evolution/index.d.ts +2 -2
- package/dist/architecture-evolution/index.d.ts.map +1 -1
- package/dist/architecture-evolution/index.js +1 -1
- package/dist/architecture-evolution/index.js.map +1 -1
- package/dist/architecture-evolution/parameter-optimizer.d.ts +5 -4
- package/dist/architecture-evolution/parameter-optimizer.d.ts.map +1 -1
- package/dist/architecture-evolution/parameter-optimizer.js +62 -34
- package/dist/architecture-evolution/parameter-optimizer.js.map +1 -1
- package/dist/architecture-evolution/prompts.d.ts +21 -2
- package/dist/architecture-evolution/prompts.d.ts.map +1 -1
- package/dist/architecture-evolution/prompts.js +189 -37
- package/dist/architecture-evolution/prompts.js.map +1 -1
- package/dist/constraints/modification-validator.d.ts +1 -0
- package/dist/constraints/modification-validator.d.ts.map +1 -1
- package/dist/constraints/modification-validator.js +45 -18
- package/dist/constraints/modification-validator.js.map +1 -1
- package/dist/constraints/rollback-manager.d.ts +1 -0
- package/dist/constraints/rollback-manager.d.ts.map +1 -1
- package/dist/constraints/rollback-manager.js +25 -1
- package/dist/constraints/rollback-manager.js.map +1 -1
- package/dist/events/event-emitter.d.ts +0 -1
- package/dist/events/event-emitter.d.ts.map +1 -1
- package/dist/events/event-emitter.js +6 -10
- package/dist/events/event-emitter.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/meta-reasoning/meta-reasoner.d.ts +4 -1
- package/dist/meta-reasoning/meta-reasoner.d.ts.map +1 -1
- package/dist/meta-reasoning/meta-reasoner.js +120 -43
- package/dist/meta-reasoning/meta-reasoner.js.map +1 -1
- package/dist/meta-reasoning/observation-collector.d.ts.map +1 -1
- package/dist/meta-reasoning/observation-collector.js +6 -2
- package/dist/meta-reasoning/observation-collector.js.map +1 -1
- package/dist/meta-reasoning/prompts.d.ts +10 -14
- package/dist/meta-reasoning/prompts.d.ts.map +1 -1
- package/dist/meta-reasoning/prompts.js +108 -33
- package/dist/meta-reasoning/prompts.js.map +1 -1
- package/dist/meta-reasoning/strategy-selector.d.ts.map +1 -1
- package/dist/meta-reasoning/strategy-selector.js +6 -0
- package/dist/meta-reasoning/strategy-selector.js.map +1 -1
- package/dist/self-modifying-agent.d.ts +88 -15
- package/dist/self-modifying-agent.d.ts.map +1 -1
- package/dist/self-modifying-agent.js +583 -376
- package/dist/self-modifying-agent.js.map +1 -1
- package/dist/tool-generation/gap-analyzer.d.ts +0 -1
- package/dist/tool-generation/gap-analyzer.d.ts.map +1 -1
- package/dist/tool-generation/gap-analyzer.js +16 -15
- package/dist/tool-generation/gap-analyzer.js.map +1 -1
- package/dist/tool-generation/generated-tool-store.d.ts.map +1 -1
- package/dist/tool-generation/generated-tool-store.js +90 -10
- package/dist/tool-generation/generated-tool-store.js.map +1 -1
- package/dist/tool-generation/index.d.ts +3 -3
- package/dist/tool-generation/index.d.ts.map +1 -1
- package/dist/tool-generation/index.js +2 -2
- package/dist/tool-generation/index.js.map +1 -1
- package/dist/tool-generation/prompts.d.ts +4 -3
- package/dist/tool-generation/prompts.d.ts.map +1 -1
- package/dist/tool-generation/prompts.js +96 -18
- package/dist/tool-generation/prompts.js.map +1 -1
- package/dist/tool-generation/tool-generator.d.ts +6 -6
- package/dist/tool-generation/tool-generator.d.ts.map +1 -1
- package/dist/tool-generation/tool-generator.js +88 -23
- package/dist/tool-generation/tool-generator.js.map +1 -1
- package/dist/tool-generation/tool-sandbox.d.ts +10 -11
- package/dist/tool-generation/tool-sandbox.d.ts.map +1 -1
- package/dist/tool-generation/tool-sandbox.js +346 -156
- package/dist/tool-generation/tool-sandbox.js.map +1 -1
- package/dist/tool-generation/tool-validator.d.ts +4 -8
- package/dist/tool-generation/tool-validator.d.ts.map +1 -1
- package/dist/tool-generation/tool-validator.js +74 -28
- package/dist/tool-generation/tool-validator.js.map +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +30 -5
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/llm-helper.d.ts.map +1 -1
- package/dist/utils/llm-helper.js +1 -0
- package/dist/utils/llm-helper.js.map +1 -1
- package/package.json +3 -12
- package/src/__tests__/component-regressions.test.ts +599 -0
- package/src/__tests__/meta-reasoning.test.ts +5 -1
- package/src/__tests__/sandbox-security.test.ts +186 -0
- package/src/__tests__/self-modifying-agent.test.ts +405 -11
- package/src/architecture-evolution/capability-analyzer.ts +63 -44
- package/src/architecture-evolution/evolution-strategy.ts +5 -2
- package/src/architecture-evolution/index.ts +2 -0
- package/src/architecture-evolution/parameter-optimizer.ts +82 -40
- package/src/architecture-evolution/prompts.ts +224 -42
- package/src/constraints/modification-validator.ts +48 -21
- package/src/constraints/rollback-manager.ts +20 -1
- package/src/events/event-emitter.ts +6 -15
- package/src/index.ts +14 -0
- package/src/meta-reasoning/meta-reasoner.ts +124 -52
- package/src/meta-reasoning/observation-collector.ts +6 -2
- package/src/meta-reasoning/prompts.ts +131 -56
- package/src/meta-reasoning/strategy-selector.ts +6 -0
- package/src/self-modifying-agent.ts +797 -413
- package/src/tool-generation/gap-analyzer.ts +21 -19
- package/src/tool-generation/generated-tool-store.ts +97 -10
- package/src/tool-generation/index.ts +13 -3
- package/src/tool-generation/prompts.ts +120 -18
- package/src/tool-generation/tool-generator.ts +120 -26
- package/src/tool-generation/tool-sandbox.ts +408 -206
- package/src/tool-generation/tool-validator.ts +90 -37
- package/src/utils/index.ts +29 -5
- package/src/utils/llm-helper.ts +1 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Worker } from 'node:worker_threads';
|
|
1
2
|
import type { ToolSandboxConfig, ToolSandboxResult, GeneratedTool } from '@cogitator-ai/types';
|
|
2
3
|
|
|
3
4
|
export const DEFAULT_SANDBOX_CONFIG: ToolSandboxConfig = {
|
|
@@ -8,79 +9,156 @@ export const DEFAULT_SANDBOX_CONFIG: ToolSandboxConfig = {
|
|
|
8
9
|
isolationLevel: 'strict',
|
|
9
10
|
};
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
String:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
12
|
+
const WORKER_SOURCE = String.raw`
|
|
13
|
+
const { parentPort, workerData } = require('node:worker_threads');
|
|
14
|
+
const vm = require('node:vm');
|
|
15
|
+
const { code, paramsJson, timeout } = workerData;
|
|
16
|
+
|
|
17
|
+
const BOOTSTRAP = [
|
|
18
|
+
'(function () {',
|
|
19
|
+
' "use strict";',
|
|
20
|
+
' const logs = [];',
|
|
21
|
+
' const format = (args) => args.map((v) => {',
|
|
22
|
+
' if (typeof v === "string") return v;',
|
|
23
|
+
' try { const s = JSON.stringify(v); return s === undefined ? String(v) : s; } catch (e) { return String(v); }',
|
|
24
|
+
' }).join(" ");',
|
|
25
|
+
' const logger = (level) => (...args) => { if (logs.length < 1000) logs.push("[" + level + "] " + format(args)); };',
|
|
26
|
+
' Object.defineProperty(globalThis, "console", { value: Object.freeze({',
|
|
27
|
+
' log: logger("LOG"), info: logger("INFO"), debug: logger("DEBUG"), warn: logger("WARN"), error: logger("ERROR"),',
|
|
28
|
+
' }) });',
|
|
29
|
+
' Object.defineProperty(globalThis, "__sandboxState", { value: { status: "pending", payload: "", hasValue: false, logs } });',
|
|
30
|
+
'})();',
|
|
31
|
+
].join('\n');
|
|
32
|
+
|
|
33
|
+
const runner = [
|
|
34
|
+
'(async function sandboxedExecution(params) {',
|
|
35
|
+
code,
|
|
36
|
+
' if (typeof execute !== "function") { throw new Error("Implementation must define an execute function"); }',
|
|
37
|
+
' return await execute(params);',
|
|
38
|
+
'})(JSON.parse(' + JSON.stringify(paramsJson) + ')).then(',
|
|
39
|
+
' (value) => {',
|
|
40
|
+
' const state = globalThis.__sandboxState;',
|
|
41
|
+
' try {',
|
|
42
|
+
' const json = JSON.stringify(value);',
|
|
43
|
+
' state.hasValue = json !== undefined;',
|
|
44
|
+
' state.payload = json === undefined ? "" : json;',
|
|
45
|
+
' state.status = "fulfilled";',
|
|
46
|
+
' } catch (e) {',
|
|
47
|
+
' state.payload = "Tool returned a non-serializable value: " + (e && e.message);',
|
|
48
|
+
' state.status = "failed";',
|
|
49
|
+
' }',
|
|
50
|
+
' },',
|
|
51
|
+
' (err) => {',
|
|
52
|
+
' const state = globalThis.__sandboxState;',
|
|
53
|
+
' state.payload = err && typeof err.message === "string" ? err.message : String(err);',
|
|
54
|
+
' state.status = "rejected";',
|
|
55
|
+
' }',
|
|
56
|
+
');',
|
|
57
|
+
].join('\n');
|
|
58
|
+
|
|
59
|
+
const asString = (value) => (typeof value === 'string' ? value : '');
|
|
60
|
+
const readState = (context, expression) => {
|
|
61
|
+
try {
|
|
62
|
+
return vm.runInContext(expression, context);
|
|
63
|
+
} catch {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const readLogs = (context) => {
|
|
68
|
+
const raw = readState(context, 'JSON.stringify(__sandboxState.logs)');
|
|
69
|
+
if (typeof raw !== 'string') return [];
|
|
70
|
+
try {
|
|
71
|
+
const parsed = JSON.parse(raw);
|
|
72
|
+
return Array.isArray(parsed) ? parsed.filter((l) => typeof l === 'string') : [];
|
|
73
|
+
} catch {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const context = vm.createContext(Object.create(null), {
|
|
79
|
+
codeGeneration: { strings: false, wasm: false },
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
let started = false;
|
|
83
|
+
try {
|
|
84
|
+
vm.runInContext(BOOTSTRAP, context);
|
|
85
|
+
vm.runInContext(runner, context, { timeout });
|
|
86
|
+
started = true;
|
|
87
|
+
} catch (err) {
|
|
88
|
+
parentPort.postMessage({
|
|
89
|
+
success: false,
|
|
90
|
+
thrown: true,
|
|
91
|
+
error: err && typeof err.message === 'string' ? err.message : String(err),
|
|
92
|
+
logs: readLogs(context),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (started) setImmediate(() => {
|
|
97
|
+
const status = asString(readState(context, '__sandboxState.status'));
|
|
98
|
+
const payload = asString(readState(context, '__sandboxState.payload'));
|
|
99
|
+
const logs = readLogs(context);
|
|
100
|
+
|
|
101
|
+
if (status === 'fulfilled') {
|
|
102
|
+
let result;
|
|
103
|
+
if (readState(context, '__sandboxState.hasValue') === true) {
|
|
104
|
+
try {
|
|
105
|
+
result = JSON.parse(payload);
|
|
106
|
+
} catch {
|
|
107
|
+
parentPort.postMessage({ success: false, thrown: false, error: 'Tool returned an unreadable value', logs });
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
parentPort.postMessage({ success: true, result, logs });
|
|
112
|
+
} else if (status === 'rejected') {
|
|
113
|
+
parentPort.postMessage({ success: false, thrown: true, error: payload, logs });
|
|
114
|
+
} else if (status === 'failed') {
|
|
115
|
+
parentPort.postMessage({ success: false, thrown: false, error: payload, logs });
|
|
116
|
+
} else {
|
|
117
|
+
parentPort.postMessage({
|
|
118
|
+
success: false,
|
|
119
|
+
thrown: false,
|
|
120
|
+
error: 'Tool execution never settled: execute() returned a promise that cannot resolve inside the sandbox',
|
|
121
|
+
logs,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
`;
|
|
126
|
+
|
|
127
|
+
interface WorkerMessage {
|
|
128
|
+
success: boolean;
|
|
129
|
+
result?: unknown;
|
|
130
|
+
error?: string;
|
|
131
|
+
thrown?: boolean;
|
|
132
|
+
logs?: string[];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
interface SandboxRun {
|
|
136
|
+
result: ToolSandboxResult;
|
|
137
|
+
thrownByTool: boolean;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface SandboxTestCase {
|
|
141
|
+
input: unknown;
|
|
142
|
+
expectedOutput?: unknown;
|
|
143
|
+
shouldThrow?: boolean;
|
|
144
|
+
allowThrow?: boolean;
|
|
32
145
|
}
|
|
33
146
|
|
|
34
147
|
export class ToolSandbox {
|
|
35
148
|
private readonly config: ToolSandboxConfig;
|
|
36
|
-
private readonly logs: string[] = [];
|
|
37
149
|
|
|
38
150
|
constructor(config: Partial<ToolSandboxConfig> = {}) {
|
|
39
151
|
this.config = { ...DEFAULT_SANDBOX_CONFIG, ...config };
|
|
40
152
|
}
|
|
41
153
|
|
|
42
154
|
async execute(tool: GeneratedTool, params: unknown): Promise<ToolSandboxResult> {
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (!this.config.enabled) {
|
|
47
|
-
return this.executeUnsandboxed(tool, params, startTime);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
this.validateImplementation(tool.implementation);
|
|
52
|
-
|
|
53
|
-
const context = this.createContext();
|
|
54
|
-
const wrappedCode = this.wrapImplementation(tool.implementation);
|
|
55
|
-
|
|
56
|
-
const result = await this.executeWithTimeout(
|
|
57
|
-
wrappedCode,
|
|
58
|
-
context,
|
|
59
|
-
params,
|
|
60
|
-
this.config.maxExecutionTime
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
success: true,
|
|
65
|
-
result,
|
|
66
|
-
executionTime: Date.now() - startTime,
|
|
67
|
-
memoryUsed: this.estimateMemoryUsage(result),
|
|
68
|
-
logs: [...this.logs],
|
|
69
|
-
};
|
|
70
|
-
} catch (error) {
|
|
71
|
-
return {
|
|
72
|
-
success: false,
|
|
73
|
-
error: error instanceof Error ? error.message : String(error),
|
|
74
|
-
executionTime: Date.now() - startTime,
|
|
75
|
-
memoryUsed: 0,
|
|
76
|
-
logs: [...this.logs],
|
|
77
|
-
};
|
|
78
|
-
}
|
|
155
|
+
const run = await this.run(tool, params);
|
|
156
|
+
return run.result;
|
|
79
157
|
}
|
|
80
158
|
|
|
81
159
|
async testWithCases(
|
|
82
160
|
tool: GeneratedTool,
|
|
83
|
-
testCases:
|
|
161
|
+
testCases: SandboxTestCase[]
|
|
84
162
|
): Promise<{
|
|
85
163
|
passed: number;
|
|
86
164
|
failed: number;
|
|
@@ -101,13 +179,15 @@ export class ToolSandbox {
|
|
|
101
179
|
}> = [];
|
|
102
180
|
|
|
103
181
|
for (const testCase of testCases) {
|
|
104
|
-
const execResult = await this.
|
|
182
|
+
const { result: execResult, thrownByTool } = await this.run(tool, testCase.input);
|
|
105
183
|
|
|
106
|
-
let passed
|
|
184
|
+
let passed: boolean;
|
|
107
185
|
if (testCase.shouldThrow) {
|
|
108
|
-
passed = !execResult.success;
|
|
186
|
+
passed = !execResult.success && thrownByTool;
|
|
109
187
|
} else if (testCase.expectedOutput !== undefined) {
|
|
110
|
-
passed = execResult.success &&
|
|
188
|
+
passed = execResult.success && deepEqual(execResult.result, testCase.expectedOutput);
|
|
189
|
+
} else if (testCase.allowThrow) {
|
|
190
|
+
passed = execResult.success || thrownByTool;
|
|
111
191
|
} else {
|
|
112
192
|
passed = execResult.success;
|
|
113
193
|
}
|
|
@@ -128,45 +208,101 @@ export class ToolSandbox {
|
|
|
128
208
|
};
|
|
129
209
|
}
|
|
130
210
|
|
|
211
|
+
private async run(tool: GeneratedTool, params: unknown): Promise<SandboxRun> {
|
|
212
|
+
const startTime = Date.now();
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
this.validateImplementation(tool.implementation);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
return {
|
|
218
|
+
result: failure(error instanceof Error ? error.message : String(error), startTime),
|
|
219
|
+
thrownByTool: false,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
let paramsJson: string;
|
|
224
|
+
try {
|
|
225
|
+
paramsJson = JSON.stringify(params ?? null);
|
|
226
|
+
} catch (error) {
|
|
227
|
+
return {
|
|
228
|
+
result: failure(
|
|
229
|
+
`Parameters must be JSON-serializable: ${error instanceof Error ? error.message : String(error)}`,
|
|
230
|
+
startTime
|
|
231
|
+
),
|
|
232
|
+
thrownByTool: false,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (!this.config.enabled) {
|
|
237
|
+
return this.executeUnsandboxed(tool, JSON.parse(paramsJson) as unknown, startTime);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return this.executeInWorker(tool.implementation, paramsJson, startTime);
|
|
241
|
+
}
|
|
242
|
+
|
|
131
243
|
private validateImplementation(code: string): void {
|
|
132
|
-
const forbidden = [
|
|
133
|
-
/\beval\s*\(/,
|
|
134
|
-
/\
|
|
135
|
-
/\
|
|
136
|
-
/\bimport\s*\(/,
|
|
137
|
-
/\brequire\s*\(/,
|
|
138
|
-
/\bprocess
|
|
139
|
-
/\bglobal
|
|
140
|
-
/\bglobalThis
|
|
141
|
-
/\bwindow
|
|
142
|
-
/\bdocument
|
|
143
|
-
/\bchild_process/,
|
|
144
|
-
/\
|
|
145
|
-
/\
|
|
146
|
-
/\
|
|
147
|
-
/\
|
|
148
|
-
/\
|
|
149
|
-
/\
|
|
150
|
-
/\
|
|
151
|
-
/\
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
244
|
+
const forbidden: Array<{ pattern: RegExp; label: string }> = [
|
|
245
|
+
{ pattern: /\beval\s*\(/, label: 'eval()' },
|
|
246
|
+
{ pattern: /\bnew\s+Function\s*\(/, label: 'new Function()' },
|
|
247
|
+
{ pattern: /\bFunction\s*\.\s*prototype/, label: 'Function.prototype' },
|
|
248
|
+
{ pattern: /\bimport\s*\(/, label: 'dynamic import()' },
|
|
249
|
+
{ pattern: /\brequire\s*\(/, label: 'require()' },
|
|
250
|
+
{ pattern: /\bprocess\s*[[.]/, label: 'process access' },
|
|
251
|
+
{ pattern: /\bglobal\s*[[.]/, label: 'global access' },
|
|
252
|
+
{ pattern: /\bglobalThis\s*[[.]/, label: 'globalThis access' },
|
|
253
|
+
{ pattern: /\bwindow\s*[[.]/, label: 'window access' },
|
|
254
|
+
{ pattern: /\bdocument\s*[[.]/, label: 'document access' },
|
|
255
|
+
{ pattern: /\bchild_process\b/, label: 'child_process' },
|
|
256
|
+
{ pattern: /\brequire\s*\(\s*['"]fs['"]/, label: 'fs module' },
|
|
257
|
+
{ pattern: /\brequire\s*\(\s*['"]net['"]/, label: 'net module' },
|
|
258
|
+
{ pattern: /\brequire\s*\(\s*['"]dns['"]/, label: 'dns module' },
|
|
259
|
+
{ pattern: /\brequire\s*\(\s*['"]http['"]/, label: 'http module' },
|
|
260
|
+
{ pattern: /\brequire\s*\(\s*['"]https['"]/, label: 'https module' },
|
|
261
|
+
{ pattern: /\brequire\s*\(\s*['"]os['"]/, label: 'os module' },
|
|
262
|
+
{ pattern: /\bspawn\s*\(/, label: 'spawn()' },
|
|
263
|
+
{ pattern: /\bexecSync\s*\(/, label: 'execSync()' },
|
|
264
|
+
{ pattern: /\bexecFile\s*\(/, label: 'execFile()' },
|
|
265
|
+
{ pattern: /__proto__/, label: '__proto__' },
|
|
266
|
+
{ pattern: /\.\s*constructor\s*[[.(]/, label: 'constructor access' },
|
|
267
|
+
{ pattern: /\bgetPrototypeOf\s*\(/, label: 'getPrototypeOf()' },
|
|
268
|
+
{ pattern: /\bsetPrototypeOf\s*\(/, label: 'setPrototypeOf()' },
|
|
269
|
+
{ pattern: /\bReflect\s*[[.]/, label: 'Reflect access' },
|
|
270
|
+
{ pattern: /\bProxy\s*[[.(]/, label: 'Proxy access' },
|
|
271
|
+
{ pattern: /\bSymbol\s*\.\s*for\s*\(/, label: 'Symbol.for()' },
|
|
272
|
+
{ pattern: /\bSharedArrayBuffer\b/, label: 'SharedArrayBuffer' },
|
|
273
|
+
{ pattern: /\bAtomics\b/, label: 'Atomics' },
|
|
274
|
+
{ pattern: /\bWeakRef\b/, label: 'WeakRef' },
|
|
275
|
+
{ pattern: /\bFinalizationRegistry\b/, label: 'FinalizationRegistry' },
|
|
155
276
|
];
|
|
156
277
|
|
|
157
278
|
if (this.config.isolationLevel === 'strict') {
|
|
158
279
|
forbidden.push(
|
|
159
|
-
/\bfetch\s*\(/,
|
|
160
|
-
/\bXMLHttpRequest/,
|
|
161
|
-
/\bWebSocket/,
|
|
162
|
-
/\bsetTimeout\s*\(/,
|
|
163
|
-
/\bsetInterval\s*\(
|
|
280
|
+
{ pattern: /\bfetch\s*\(/, label: 'fetch()' },
|
|
281
|
+
{ pattern: /\bXMLHttpRequest\b/, label: 'XMLHttpRequest' },
|
|
282
|
+
{ pattern: /\bWebSocket\b/, label: 'WebSocket' },
|
|
283
|
+
{ pattern: /\bsetTimeout\s*\(/, label: 'setTimeout()' },
|
|
284
|
+
{ pattern: /\bsetInterval\s*\(/, label: 'setInterval()' }
|
|
164
285
|
);
|
|
165
286
|
}
|
|
166
287
|
|
|
167
|
-
for (const pattern of forbidden) {
|
|
288
|
+
for (const { pattern, label } of forbidden) {
|
|
168
289
|
if (pattern.test(code)) {
|
|
169
|
-
throw new Error(`Security violation: forbidden pattern detected - ${
|
|
290
|
+
throw new Error(`Security violation: forbidden pattern detected - ${label}`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const bracketGlobalAccess = /\b(?:global|globalThis|process|window|document)\s*\[\s*['"`]/;
|
|
295
|
+
if (bracketGlobalAccess.test(code)) {
|
|
296
|
+
throw new Error('Security violation: bracket notation access to global objects');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const stringConcatEscape = /['"`]\s*\+\s*['"`]/;
|
|
300
|
+
if (stringConcatEscape.test(code)) {
|
|
301
|
+
const concatenated = code.replace(/['"`]\s*\+\s*['"`]/g, '');
|
|
302
|
+
for (const { pattern, label } of forbidden) {
|
|
303
|
+
if (pattern.test(concatenated)) {
|
|
304
|
+
throw new Error(`Security violation: obfuscated forbidden pattern detected - ${label}`);
|
|
305
|
+
}
|
|
170
306
|
}
|
|
171
307
|
}
|
|
172
308
|
|
|
@@ -176,82 +312,85 @@ export class ToolSandbox {
|
|
|
176
312
|
}
|
|
177
313
|
}
|
|
178
314
|
|
|
179
|
-
private
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
Array,
|
|
196
|
-
Object,
|
|
197
|
-
String,
|
|
198
|
-
Number,
|
|
199
|
-
Boolean,
|
|
200
|
-
RegExp,
|
|
201
|
-
Map,
|
|
202
|
-
Set,
|
|
203
|
-
Promise,
|
|
204
|
-
Error,
|
|
205
|
-
TypeError,
|
|
206
|
-
RangeError,
|
|
207
|
-
};
|
|
208
|
-
}
|
|
315
|
+
private executeInWorker(
|
|
316
|
+
code: string,
|
|
317
|
+
paramsJson: string,
|
|
318
|
+
startTime: number
|
|
319
|
+
): Promise<SandboxRun> {
|
|
320
|
+
return new Promise<SandboxRun>((resolve) => {
|
|
321
|
+
let settled = false;
|
|
322
|
+
let worker: Worker | undefined;
|
|
323
|
+
|
|
324
|
+
const settle = (result: ToolSandboxResult, thrownByTool = false) => {
|
|
325
|
+
if (settled) return;
|
|
326
|
+
settled = true;
|
|
327
|
+
clearTimeout(timer);
|
|
328
|
+
if (worker) void worker.terminate();
|
|
329
|
+
resolve({ result, thrownByTool });
|
|
330
|
+
};
|
|
209
331
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
return (async function sandboxedExecution(params, context) {
|
|
214
|
-
const { console, Math, JSON, Date, Array, Object, String, Number, Boolean, RegExp, Map, Set, Promise, Error, TypeError, RangeError } = context;
|
|
332
|
+
const timer = setTimeout(() => {
|
|
333
|
+
settle(failure(`Execution timeout: exceeded ${this.config.maxExecutionTime}ms`, startTime));
|
|
334
|
+
}, this.config.maxExecutionTime);
|
|
215
335
|
|
|
216
|
-
|
|
336
|
+
const maxMemoryMb = Math.max(4, Math.ceil(this.config.maxMemory / (1024 * 1024)));
|
|
217
337
|
|
|
218
|
-
|
|
219
|
-
|
|
338
|
+
try {
|
|
339
|
+
worker = new Worker(WORKER_SOURCE, {
|
|
340
|
+
eval: true,
|
|
341
|
+
env: {},
|
|
342
|
+
workerData: { code, paramsJson, timeout: this.config.maxExecutionTime },
|
|
343
|
+
resourceLimits: {
|
|
344
|
+
maxOldGenerationSizeMb: maxMemoryMb,
|
|
345
|
+
maxYoungGenerationSizeMb: Math.max(2, Math.ceil(maxMemoryMb / 4)),
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
} catch (error) {
|
|
349
|
+
settle(
|
|
350
|
+
failure(
|
|
351
|
+
`Failed to create sandbox worker: ${error instanceof Error ? error.message : String(error)}`,
|
|
352
|
+
startTime
|
|
353
|
+
)
|
|
354
|
+
);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
worker.on('message', (msg: WorkerMessage) => {
|
|
359
|
+
const logs = msg.logs ?? [];
|
|
360
|
+
if (msg.success) {
|
|
361
|
+
settle({
|
|
362
|
+
success: true,
|
|
363
|
+
result: msg.result,
|
|
364
|
+
executionTime: Date.now() - startTime,
|
|
365
|
+
memoryUsed: estimateMemoryUsage(msg.result),
|
|
366
|
+
logs,
|
|
367
|
+
});
|
|
368
|
+
} else {
|
|
369
|
+
settle({ ...failure(msg.error ?? 'Tool execution failed', startTime), logs }, msg.thrown);
|
|
220
370
|
}
|
|
221
|
-
throw new Error('Implementation must define an execute function');
|
|
222
371
|
});
|
|
223
|
-
`;
|
|
224
|
-
}
|
|
225
372
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
373
|
+
worker.on('error', (error: Error & { code?: string }) => {
|
|
374
|
+
settle(
|
|
375
|
+
failure(
|
|
376
|
+
error.code === 'ERR_WORKER_OUT_OF_MEMORY'
|
|
377
|
+
? `Execution exceeded memory limit (${this.config.maxMemory} bytes)`
|
|
378
|
+
: `Sandbox worker error: ${error.message}`,
|
|
379
|
+
startTime
|
|
380
|
+
)
|
|
381
|
+
);
|
|
382
|
+
});
|
|
236
383
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
.catch((error: unknown) => {
|
|
248
|
-
clearTimeout(timer);
|
|
249
|
-
reject(error instanceof Error ? error : new Error(String(error)));
|
|
250
|
-
});
|
|
251
|
-
} catch (error) {
|
|
252
|
-
clearTimeout(timer);
|
|
253
|
-
reject(error instanceof Error ? error : new Error(String(error)));
|
|
254
|
-
}
|
|
384
|
+
worker.on('exit', (exitCode: number) => {
|
|
385
|
+
settle(
|
|
386
|
+
failure(
|
|
387
|
+
exitCode === 134 || exitCode === 137
|
|
388
|
+
? `Execution exceeded memory limit (${this.config.maxMemory} bytes)`
|
|
389
|
+
: `Sandbox worker exited with code ${exitCode} before reporting a result`,
|
|
390
|
+
startTime
|
|
391
|
+
)
|
|
392
|
+
);
|
|
393
|
+
});
|
|
255
394
|
});
|
|
256
395
|
}
|
|
257
396
|
|
|
@@ -259,74 +398,137 @@ export class ToolSandbox {
|
|
|
259
398
|
tool: GeneratedTool,
|
|
260
399
|
params: unknown,
|
|
261
400
|
startTime: number
|
|
262
|
-
): Promise<
|
|
401
|
+
): Promise<SandboxRun> {
|
|
402
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
263
403
|
try {
|
|
264
404
|
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
265
405
|
const factory = new Function(`
|
|
266
406
|
"use strict";
|
|
267
407
|
${tool.implementation}
|
|
268
408
|
return execute;
|
|
269
|
-
`);
|
|
409
|
+
`) as () => unknown;
|
|
270
410
|
const execute = factory();
|
|
411
|
+
if (typeof execute !== 'function') {
|
|
412
|
+
return {
|
|
413
|
+
result: failure('Implementation must define an execute function', startTime),
|
|
414
|
+
thrownByTool: false,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
271
417
|
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
418
|
+
const timeout = new Promise<never>((_, reject) => {
|
|
419
|
+
timer = setTimeout(
|
|
420
|
+
() => reject(new SandboxTimeoutError(this.config.maxExecutionTime)),
|
|
421
|
+
this.config.maxExecutionTime
|
|
422
|
+
);
|
|
423
|
+
});
|
|
424
|
+
const result: unknown = await Promise.race([
|
|
425
|
+
(execute as (p: unknown) => unknown)(params),
|
|
426
|
+
timeout,
|
|
281
427
|
]);
|
|
282
428
|
|
|
283
429
|
return {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
430
|
+
result: {
|
|
431
|
+
success: true,
|
|
432
|
+
result,
|
|
433
|
+
executionTime: Date.now() - startTime,
|
|
434
|
+
memoryUsed: estimateMemoryUsage(result),
|
|
435
|
+
logs: [],
|
|
436
|
+
},
|
|
437
|
+
thrownByTool: false,
|
|
289
438
|
};
|
|
290
439
|
} catch (error) {
|
|
291
440
|
return {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
executionTime: Date.now() - startTime,
|
|
295
|
-
memoryUsed: 0,
|
|
296
|
-
logs: [],
|
|
441
|
+
result: failure(error instanceof Error ? error.message : String(error), startTime),
|
|
442
|
+
thrownByTool: !(error instanceof SandboxTimeoutError),
|
|
297
443
|
};
|
|
444
|
+
} finally {
|
|
445
|
+
clearTimeout(timer);
|
|
298
446
|
}
|
|
299
447
|
}
|
|
448
|
+
}
|
|
300
449
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
} catch {
|
|
306
|
-
return 0;
|
|
307
|
-
}
|
|
450
|
+
class SandboxTimeoutError extends Error {
|
|
451
|
+
constructor(limit: number) {
|
|
452
|
+
super(`Execution timeout: exceeded ${limit}ms`);
|
|
453
|
+
this.name = 'SandboxTimeoutError';
|
|
308
454
|
}
|
|
455
|
+
}
|
|
309
456
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
457
|
+
function failure(error: string, startTime: number): ToolSandboxResult {
|
|
458
|
+
return {
|
|
459
|
+
success: false,
|
|
460
|
+
error,
|
|
461
|
+
executionTime: Date.now() - startTime,
|
|
462
|
+
memoryUsed: 0,
|
|
463
|
+
logs: [],
|
|
464
|
+
};
|
|
465
|
+
}
|
|
314
466
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
467
|
+
function estimateMemoryUsage(value: unknown): number {
|
|
468
|
+
try {
|
|
469
|
+
const str = JSON.stringify(value);
|
|
470
|
+
return str ? str.length * 2 : 0;
|
|
471
|
+
} catch {
|
|
472
|
+
return 0;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export function deepEqual(a: unknown, b: unknown): boolean {
|
|
477
|
+
return deepEqualInner(a, b, new Map<object, Set<object>>());
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function deepEqualInner(a: unknown, b: unknown, visited: Map<object, Set<object>>): boolean {
|
|
481
|
+
if (Object.is(a, b)) return true;
|
|
482
|
+
if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) return false;
|
|
483
|
+
|
|
484
|
+
const pairs = visited.get(a);
|
|
485
|
+
if (pairs?.has(b)) return true;
|
|
486
|
+
if (pairs) pairs.add(b);
|
|
487
|
+
else visited.set(a, new Set([b]));
|
|
488
|
+
|
|
489
|
+
if (a instanceof Date || b instanceof Date) {
|
|
490
|
+
return a instanceof Date && b instanceof Date && a.getTime() === b.getTime();
|
|
491
|
+
}
|
|
319
492
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
493
|
+
if (a instanceof RegExp || b instanceof RegExp) {
|
|
494
|
+
return (
|
|
495
|
+
a instanceof RegExp && b instanceof RegExp && a.source === b.source && a.flags === b.flags
|
|
496
|
+
);
|
|
497
|
+
}
|
|
325
498
|
|
|
326
|
-
|
|
327
|
-
|
|
499
|
+
if (a instanceof Map || b instanceof Map) {
|
|
500
|
+
if (!(a instanceof Map && b instanceof Map) || a.size !== b.size) return false;
|
|
501
|
+
for (const [key, val] of a) {
|
|
502
|
+
if (!b.has(key) || !deepEqualInner(val, b.get(key), visited)) return false;
|
|
328
503
|
}
|
|
504
|
+
return true;
|
|
505
|
+
}
|
|
329
506
|
|
|
330
|
-
|
|
507
|
+
if (a instanceof Set || b instanceof Set) {
|
|
508
|
+
if (!(a instanceof Set && b instanceof Set) || a.size !== b.size) return false;
|
|
509
|
+
const unmatched = [...b];
|
|
510
|
+
for (const val of a) {
|
|
511
|
+
const idx = unmatched.findIndex((candidate) => deepEqualInner(val, candidate, visited));
|
|
512
|
+
if (idx === -1) return false;
|
|
513
|
+
unmatched.splice(idx, 1);
|
|
514
|
+
}
|
|
515
|
+
return true;
|
|
331
516
|
}
|
|
517
|
+
|
|
518
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
519
|
+
if (!(Array.isArray(a) && Array.isArray(b)) || a.length !== b.length) return false;
|
|
520
|
+
return a.every((val, i) => deepEqualInner(val, b[i], visited));
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const aObj = a as Record<string, unknown>;
|
|
524
|
+
const bObj = b as Record<string, unknown>;
|
|
525
|
+
const keysA = Object.keys(aObj).filter((k) => aObj[k] !== undefined);
|
|
526
|
+
const keysB = Object.keys(bObj).filter((k) => bObj[k] !== undefined);
|
|
527
|
+
|
|
528
|
+
if (keysA.length !== keysB.length) return false;
|
|
529
|
+
return keysA.every(
|
|
530
|
+
(key) =>
|
|
531
|
+
Object.prototype.hasOwnProperty.call(bObj, key) &&
|
|
532
|
+
deepEqualInner(aObj[key], bObj[key], visited)
|
|
533
|
+
);
|
|
332
534
|
}
|