@vinkius-core/mcp-fusion 2.12.0 → 2.13.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/dist/core/builder/FluentToolBuilder.d.ts +30 -0
- package/dist/core/builder/FluentToolBuilder.d.ts.map +1 -1
- package/dist/core/builder/FluentToolBuilder.js +43 -0
- package/dist/core/builder/FluentToolBuilder.js.map +1 -1
- package/dist/core/builder/GroupedToolBuilder.d.ts +27 -0
- package/dist/core/builder/GroupedToolBuilder.d.ts.map +1 -1
- package/dist/core/builder/GroupedToolBuilder.js +33 -0
- package/dist/core/builder/GroupedToolBuilder.js.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/initFusion.d.ts +21 -0
- package/dist/core/initFusion.d.ts.map +1 -1
- package/dist/core/initFusion.js +5 -0
- package/dist/core/initFusion.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/sandbox/SandboxEngine.d.ts +188 -0
- package/dist/sandbox/SandboxEngine.d.ts.map +1 -0
- package/dist/sandbox/SandboxEngine.js +290 -0
- package/dist/sandbox/SandboxEngine.js.map +1 -0
- package/dist/sandbox/SandboxGuard.d.ts +47 -0
- package/dist/sandbox/SandboxGuard.d.ts.map +1 -0
- package/dist/sandbox/SandboxGuard.js +90 -0
- package/dist/sandbox/SandboxGuard.js.map +1 -0
- package/dist/sandbox/index.d.ts +23 -0
- package/dist/sandbox/index.d.ts.map +1 -0
- package/dist/sandbox/index.js +26 -0
- package/dist/sandbox/index.js.map +1 -0
- package/package.json +10 -2
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* ├── observability/ ← Debug Observer, Tracing
|
|
12
12
|
* ├── presenter/ ← MVA View Layer
|
|
13
13
|
* ├── prompt/ ← Prompt Engine
|
|
14
|
+
* ├── sandbox/ ← Zero-Trust V8 Computation Delegation
|
|
14
15
|
* ├── server/ ← Server Attachment
|
|
15
16
|
* ├── exposition/ ← Flat/Grouped Topology Compiler
|
|
16
17
|
* ├── state-sync/ ← Epistemic Cache-Control
|
|
@@ -95,4 +96,7 @@ export { createProbe, buildJudgePrompt, parseJudgeResponse, evaluateProbe, evalu
|
|
|
95
96
|
export { enrichValidationError, createToolEnhancer, } from './introspection/ContractAwareSelfHealing.js';
|
|
96
97
|
/** @category Governance */
|
|
97
98
|
export { createGovernanceObserver, createNoopObserver, } from './introspection/GovernanceObserver.js';
|
|
99
|
+
// ── Sandbox (Zero-Trust V8 Compute Delegation) ──────────
|
|
100
|
+
/** @category Sandbox */
|
|
101
|
+
export { SandboxEngine, validateSandboxCode, SANDBOX_SYSTEM_INSTRUCTION } from './sandbox/index.js';
|
|
98
102
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SandboxEngine — Zero-Trust V8 Isolate for Computation Delegation
|
|
3
|
+
*
|
|
4
|
+
* Allows LLMs to send JavaScript functions as strings to be executed
|
|
5
|
+
* in a sealed V8 isolate. The data stays on the client's machine,
|
|
6
|
+
* only the result travels back to the LLM.
|
|
7
|
+
*
|
|
8
|
+
* Architecture (V8 Engineering Rules):
|
|
9
|
+
* 1. ONE Isolate per SandboxEngine (boot ~5-10ms), reused across requests
|
|
10
|
+
* 2. NEW Context per execute() call (~0.1ms), pristine and empty
|
|
11
|
+
* 3. ExternalCopy + Script + Context are ALWAYS released in `finally`
|
|
12
|
+
* 4. Execution is ALWAYS async (script.run, never runSync)
|
|
13
|
+
* 5. Context is empty — no process, require, fs, globalThis injected
|
|
14
|
+
*
|
|
15
|
+
* The `isolated-vm` package is a peerDependency (optional).
|
|
16
|
+
* If not installed, the engine throws a clear error at construction time.
|
|
17
|
+
*
|
|
18
|
+
* ┌─────────────────────────────────────────────────┐
|
|
19
|
+
* │ SandboxEngine (owns 1 Isolate) │
|
|
20
|
+
* │ │
|
|
21
|
+
* │ execute(code, data) │
|
|
22
|
+
* │ ┌──────────┐ │
|
|
23
|
+
* │ │ Guard │ fail-fast syntax check │
|
|
24
|
+
* │ ├──────────┤ │
|
|
25
|
+
* │ │ Context │ new per request (empty!) │
|
|
26
|
+
* │ ├──────────┤ │
|
|
27
|
+
* │ │ Copy In │ ExternalCopy (deep, no refs) │
|
|
28
|
+
* │ ├──────────┤ │
|
|
29
|
+
* │ │ Compile │ isolate.compileScript │
|
|
30
|
+
* │ ├──────────┤ │
|
|
31
|
+
* │ │ Run │ script.run (ASYNC, with timeout) │
|
|
32
|
+
* │ ├──────────┤ │
|
|
33
|
+
* │ │ Copy Out │ JSON.parse result │
|
|
34
|
+
* │ └──────────┘ │
|
|
35
|
+
* │ │
|
|
36
|
+
* │ finally: inputCopy.release() │
|
|
37
|
+
* │ script.release() │
|
|
38
|
+
* │ context.release() │
|
|
39
|
+
* └─────────────────────────────────────────────────┘
|
|
40
|
+
*
|
|
41
|
+
* @module
|
|
42
|
+
*/
|
|
43
|
+
/**
|
|
44
|
+
* Configuration for a SandboxEngine instance.
|
|
45
|
+
*
|
|
46
|
+
* All fields are optional — sensible defaults are applied.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* const engine = new SandboxEngine({
|
|
51
|
+
* timeout: 3000, // Kill after 3s
|
|
52
|
+
* memoryLimit: 64, // 64MB per isolate
|
|
53
|
+
* maxOutputBytes: 512_000, // 500KB max result
|
|
54
|
+
* });
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export interface SandboxConfig {
|
|
58
|
+
/**
|
|
59
|
+
* Maximum execution time in milliseconds.
|
|
60
|
+
* If the script exceeds this, the V8 isolate kills it.
|
|
61
|
+
* @default 5000
|
|
62
|
+
*/
|
|
63
|
+
timeout?: number;
|
|
64
|
+
/**
|
|
65
|
+
* Maximum memory for the V8 isolate in megabytes.
|
|
66
|
+
* If exceeded, the isolate dies and is recreated on next call.
|
|
67
|
+
* @default 128
|
|
68
|
+
*/
|
|
69
|
+
memoryLimit?: number;
|
|
70
|
+
/**
|
|
71
|
+
* Maximum size of the serialized output in bytes.
|
|
72
|
+
* Prevents a malicious script from returning gigabytes of data.
|
|
73
|
+
* @default 1_048_576 (1MB)
|
|
74
|
+
*/
|
|
75
|
+
maxOutputBytes?: number;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Error codes for sandbox execution failures.
|
|
79
|
+
*
|
|
80
|
+
* - `TIMEOUT`: Script exceeded the time limit
|
|
81
|
+
* - `MEMORY`: Isolate ran out of memory (auto-recovered)
|
|
82
|
+
* - `SYNTAX`: JavaScript syntax error in the provided code
|
|
83
|
+
* - `RUNTIME`: Script threw an error during execution
|
|
84
|
+
* - `OUTPUT_TOO_LARGE`: Result exceeds `maxOutputBytes`
|
|
85
|
+
* - `INVALID_CODE`: Failed the SandboxGuard fail-fast check
|
|
86
|
+
* - `UNAVAILABLE`: `isolated-vm` is not installed
|
|
87
|
+
*/
|
|
88
|
+
export type SandboxErrorCode = 'TIMEOUT' | 'MEMORY' | 'SYNTAX' | 'RUNTIME' | 'OUTPUT_TOO_LARGE' | 'INVALID_CODE' | 'UNAVAILABLE';
|
|
89
|
+
/**
|
|
90
|
+
* Result of a sandbox execution.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```typescript
|
|
94
|
+
* const result = await engine.execute('(data) => data.length', [1, 2, 3]);
|
|
95
|
+
* if (result.ok) {
|
|
96
|
+
* console.log(result.value); // 3
|
|
97
|
+
* console.log(result.executionMs); // 0.42
|
|
98
|
+
* } else {
|
|
99
|
+
* console.log(result.code); // 'TIMEOUT'
|
|
100
|
+
* console.log(result.error); // 'Script execution timed out (5000ms)'
|
|
101
|
+
* }
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export type SandboxResult<T = unknown> = {
|
|
105
|
+
readonly ok: true;
|
|
106
|
+
readonly value: T;
|
|
107
|
+
readonly executionMs: number;
|
|
108
|
+
} | {
|
|
109
|
+
readonly ok: false;
|
|
110
|
+
readonly error: string;
|
|
111
|
+
readonly code: SandboxErrorCode;
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Zero-trust V8 sandbox for executing LLM-provided JavaScript.
|
|
115
|
+
*
|
|
116
|
+
* Creates a single V8 `Isolate` at construction time and reuses it
|
|
117
|
+
* across all `execute()` calls. Each call gets a fresh, empty `Context`
|
|
118
|
+
* with no dangerous globals (no `process`, `require`, `fs`, etc.).
|
|
119
|
+
*
|
|
120
|
+
* If the isolate dies (e.g., OOM), it is automatically recreated
|
|
121
|
+
* on the next `execute()` call.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```typescript
|
|
125
|
+
* const sandbox = new SandboxEngine({ timeout: 3000, memoryLimit: 64 });
|
|
126
|
+
*
|
|
127
|
+
* const result = await sandbox.execute(
|
|
128
|
+
* '(data) => data.filter(d => d.risk > 90)',
|
|
129
|
+
* [{ name: 'A', risk: 95 }, { name: 'B', risk: 30 }],
|
|
130
|
+
* );
|
|
131
|
+
*
|
|
132
|
+
* if (result.ok) {
|
|
133
|
+
* console.log(result.value); // [{ name: 'A', risk: 95 }]
|
|
134
|
+
* }
|
|
135
|
+
*
|
|
136
|
+
* // IMPORTANT: dispose when no longer needed
|
|
137
|
+
* sandbox.dispose();
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
export declare class SandboxEngine {
|
|
141
|
+
private readonly _timeout;
|
|
142
|
+
private readonly _memoryLimit;
|
|
143
|
+
private readonly _maxOutputBytes;
|
|
144
|
+
private _isolate;
|
|
145
|
+
private _disposed;
|
|
146
|
+
constructor(config?: SandboxConfig);
|
|
147
|
+
/**
|
|
148
|
+
* Execute a JavaScript function string against the provided data.
|
|
149
|
+
*
|
|
150
|
+
* The function is compiled and run in a sealed V8 isolate with:
|
|
151
|
+
* - No `process`, `require`, `fs`, or network access
|
|
152
|
+
* - Strict timeout enforcement (async, non-blocking)
|
|
153
|
+
* - Memory limit enforcement
|
|
154
|
+
* - Automatic C++ pointer cleanup (ExternalCopy, Script, Context)
|
|
155
|
+
*
|
|
156
|
+
* @param code - A JavaScript function expression as a string.
|
|
157
|
+
* Must be an arrow function or function expression.
|
|
158
|
+
* Example: `(data) => data.filter(d => d.value > 10)`
|
|
159
|
+
*
|
|
160
|
+
* @param data - The data to pass into the function.
|
|
161
|
+
* Deeply copied into the isolate (no references leak).
|
|
162
|
+
*
|
|
163
|
+
* @returns A `SandboxResult` with the computed value or an error.
|
|
164
|
+
*/
|
|
165
|
+
execute<T = unknown>(code: string, data: unknown): Promise<SandboxResult<T>>;
|
|
166
|
+
/**
|
|
167
|
+
* Release all resources held by this engine.
|
|
168
|
+
*
|
|
169
|
+
* After calling `dispose()`, any subsequent `execute()` calls
|
|
170
|
+
* will return `{ ok: false, code: 'UNAVAILABLE' }`.
|
|
171
|
+
*/
|
|
172
|
+
dispose(): void;
|
|
173
|
+
/**
|
|
174
|
+
* Check if the engine has been disposed.
|
|
175
|
+
*/
|
|
176
|
+
get isDisposed(): boolean;
|
|
177
|
+
/**
|
|
178
|
+
* Ensure the isolate is alive. If it died (OOM), create a new one.
|
|
179
|
+
* @internal
|
|
180
|
+
*/
|
|
181
|
+
private _ensureIsolate;
|
|
182
|
+
/**
|
|
183
|
+
* Classify an error from V8 execution into a typed SandboxResult.
|
|
184
|
+
* @internal
|
|
185
|
+
*/
|
|
186
|
+
private _classifyError;
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=SandboxEngine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SandboxEngine.d.ts","sourceRoot":"","sources":["../../src/sandbox/SandboxEngine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAMH;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,aAAa;IAC1B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,gBAAgB,GACtB,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,SAAS,GACT,kBAAkB,GAClB,cAAc,GACd,aAAa,CAAC;AAEpB;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,GAAG,OAAO,IAC/B;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACtE;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAkCtF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,aAAa;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IAEzC,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,SAAS,CAAS;gBAEd,MAAM,CAAC,EAAE,aAAa;IAgBlC;;;;;;;;;;;;;;;;;OAiBG;IACG,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAsElF;;;;;OAKG;IACH,OAAO,IAAI,IAAI;IAUf;;OAEG;IACH,IAAI,UAAU,IAAI,OAAO,CAExB;IAID;;;OAGG;IACH,OAAO,CAAC,cAAc;IAatB;;;OAGG;IACH,OAAO,CAAC,cAAc;CA6CzB"}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SandboxEngine — Zero-Trust V8 Isolate for Computation Delegation
|
|
3
|
+
*
|
|
4
|
+
* Allows LLMs to send JavaScript functions as strings to be executed
|
|
5
|
+
* in a sealed V8 isolate. The data stays on the client's machine,
|
|
6
|
+
* only the result travels back to the LLM.
|
|
7
|
+
*
|
|
8
|
+
* Architecture (V8 Engineering Rules):
|
|
9
|
+
* 1. ONE Isolate per SandboxEngine (boot ~5-10ms), reused across requests
|
|
10
|
+
* 2. NEW Context per execute() call (~0.1ms), pristine and empty
|
|
11
|
+
* 3. ExternalCopy + Script + Context are ALWAYS released in `finally`
|
|
12
|
+
* 4. Execution is ALWAYS async (script.run, never runSync)
|
|
13
|
+
* 5. Context is empty — no process, require, fs, globalThis injected
|
|
14
|
+
*
|
|
15
|
+
* The `isolated-vm` package is a peerDependency (optional).
|
|
16
|
+
* If not installed, the engine throws a clear error at construction time.
|
|
17
|
+
*
|
|
18
|
+
* ┌─────────────────────────────────────────────────┐
|
|
19
|
+
* │ SandboxEngine (owns 1 Isolate) │
|
|
20
|
+
* │ │
|
|
21
|
+
* │ execute(code, data) │
|
|
22
|
+
* │ ┌──────────┐ │
|
|
23
|
+
* │ │ Guard │ fail-fast syntax check │
|
|
24
|
+
* │ ├──────────┤ │
|
|
25
|
+
* │ │ Context │ new per request (empty!) │
|
|
26
|
+
* │ ├──────────┤ │
|
|
27
|
+
* │ │ Copy In │ ExternalCopy (deep, no refs) │
|
|
28
|
+
* │ ├──────────┤ │
|
|
29
|
+
* │ │ Compile │ isolate.compileScript │
|
|
30
|
+
* │ ├──────────┤ │
|
|
31
|
+
* │ │ Run │ script.run (ASYNC, with timeout) │
|
|
32
|
+
* │ ├──────────┤ │
|
|
33
|
+
* │ │ Copy Out │ JSON.parse result │
|
|
34
|
+
* │ └──────────┘ │
|
|
35
|
+
* │ │
|
|
36
|
+
* │ finally: inputCopy.release() │
|
|
37
|
+
* │ script.release() │
|
|
38
|
+
* │ context.release() │
|
|
39
|
+
* └─────────────────────────────────────────────────┘
|
|
40
|
+
*
|
|
41
|
+
* @module
|
|
42
|
+
*/
|
|
43
|
+
import { validateSandboxCode } from './SandboxGuard.js';
|
|
44
|
+
// ── Constants ────────────────────────────────────────────
|
|
45
|
+
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
46
|
+
const DEFAULT_MEMORY_LIMIT_MB = 128;
|
|
47
|
+
const DEFAULT_MAX_OUTPUT_BYTES = 1_048_576; // 1MB
|
|
48
|
+
// ── Lazy Require ─────────────────────────────────────────
|
|
49
|
+
/**
|
|
50
|
+
* Lazy-load isolated-vm to avoid hard dependency.
|
|
51
|
+
* Returns `null` if the package is not installed.
|
|
52
|
+
* @internal
|
|
53
|
+
*/
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
55
|
+
let _ivm = undefined;
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
57
|
+
function getIvm() {
|
|
58
|
+
if (_ivm !== undefined)
|
|
59
|
+
return _ivm;
|
|
60
|
+
try {
|
|
61
|
+
// Dynamic import would be cleaner but isolated-vm uses
|
|
62
|
+
// native bindings that require synchronous resolution.
|
|
63
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
64
|
+
_ivm = require('isolated-vm');
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
_ivm = null;
|
|
68
|
+
}
|
|
69
|
+
return _ivm;
|
|
70
|
+
}
|
|
71
|
+
// ── Engine Implementation ────────────────────────────────
|
|
72
|
+
/**
|
|
73
|
+
* Zero-trust V8 sandbox for executing LLM-provided JavaScript.
|
|
74
|
+
*
|
|
75
|
+
* Creates a single V8 `Isolate` at construction time and reuses it
|
|
76
|
+
* across all `execute()` calls. Each call gets a fresh, empty `Context`
|
|
77
|
+
* with no dangerous globals (no `process`, `require`, `fs`, etc.).
|
|
78
|
+
*
|
|
79
|
+
* If the isolate dies (e.g., OOM), it is automatically recreated
|
|
80
|
+
* on the next `execute()` call.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* const sandbox = new SandboxEngine({ timeout: 3000, memoryLimit: 64 });
|
|
85
|
+
*
|
|
86
|
+
* const result = await sandbox.execute(
|
|
87
|
+
* '(data) => data.filter(d => d.risk > 90)',
|
|
88
|
+
* [{ name: 'A', risk: 95 }, { name: 'B', risk: 30 }],
|
|
89
|
+
* );
|
|
90
|
+
*
|
|
91
|
+
* if (result.ok) {
|
|
92
|
+
* console.log(result.value); // [{ name: 'A', risk: 95 }]
|
|
93
|
+
* }
|
|
94
|
+
*
|
|
95
|
+
* // IMPORTANT: dispose when no longer needed
|
|
96
|
+
* sandbox.dispose();
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export class SandboxEngine {
|
|
100
|
+
_timeout;
|
|
101
|
+
_memoryLimit;
|
|
102
|
+
_maxOutputBytes;
|
|
103
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
104
|
+
_isolate; // ivm.Isolate
|
|
105
|
+
_disposed = false;
|
|
106
|
+
constructor(config) {
|
|
107
|
+
this._timeout = config?.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
108
|
+
this._memoryLimit = config?.memoryLimit ?? DEFAULT_MEMORY_LIMIT_MB;
|
|
109
|
+
this._maxOutputBytes = config?.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
110
|
+
const ivm = getIvm();
|
|
111
|
+
if (!ivm) {
|
|
112
|
+
throw new Error('SandboxEngine requires the "isolated-vm" package. ' +
|
|
113
|
+
'Install it with: npm install isolated-vm');
|
|
114
|
+
}
|
|
115
|
+
this._isolate = new ivm.Isolate({ memoryLimit: this._memoryLimit });
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Execute a JavaScript function string against the provided data.
|
|
119
|
+
*
|
|
120
|
+
* The function is compiled and run in a sealed V8 isolate with:
|
|
121
|
+
* - No `process`, `require`, `fs`, or network access
|
|
122
|
+
* - Strict timeout enforcement (async, non-blocking)
|
|
123
|
+
* - Memory limit enforcement
|
|
124
|
+
* - Automatic C++ pointer cleanup (ExternalCopy, Script, Context)
|
|
125
|
+
*
|
|
126
|
+
* @param code - A JavaScript function expression as a string.
|
|
127
|
+
* Must be an arrow function or function expression.
|
|
128
|
+
* Example: `(data) => data.filter(d => d.value > 10)`
|
|
129
|
+
*
|
|
130
|
+
* @param data - The data to pass into the function.
|
|
131
|
+
* Deeply copied into the isolate (no references leak).
|
|
132
|
+
*
|
|
133
|
+
* @returns A `SandboxResult` with the computed value or an error.
|
|
134
|
+
*/
|
|
135
|
+
async execute(code, data) {
|
|
136
|
+
if (this._disposed) {
|
|
137
|
+
return { ok: false, error: 'SandboxEngine has been disposed.', code: 'UNAVAILABLE' };
|
|
138
|
+
}
|
|
139
|
+
// ── Step 1: Fail-fast guard ─────────────────────
|
|
140
|
+
const guard = validateSandboxCode(code);
|
|
141
|
+
if (!guard.ok) {
|
|
142
|
+
return { ok: false, error: guard.violation, code: 'INVALID_CODE' };
|
|
143
|
+
}
|
|
144
|
+
// ── Step 2: Ensure isolate is alive ─────────────
|
|
145
|
+
this._ensureIsolate();
|
|
146
|
+
const ivm = getIvm();
|
|
147
|
+
const isolate = this._isolate;
|
|
148
|
+
// ── Step 3: Execute in sealed context ───────────
|
|
149
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
150
|
+
let inputCopy; // ivm.ExternalCopy
|
|
151
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
152
|
+
let context; // ivm.Context
|
|
153
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
154
|
+
let script; // ivm.Script
|
|
155
|
+
const startMs = performance.now();
|
|
156
|
+
try {
|
|
157
|
+
// Create pristine context (NO globals injected — this IS the security)
|
|
158
|
+
context = await isolate.createContext();
|
|
159
|
+
// Deep-copy data into isolated heap (no references!)
|
|
160
|
+
inputCopy = new ivm.ExternalCopy(data);
|
|
161
|
+
await context.global.set('__input__', inputCopy.copyInto());
|
|
162
|
+
// Compile with wrapper: call the function and serialize result
|
|
163
|
+
const wrappedCode = `const __fn__ = ${code};\nJSON.stringify(__fn__(__input__));`;
|
|
164
|
+
script = await isolate.compileScript(wrappedCode);
|
|
165
|
+
// ASYNC execution — never blocks the Node.js event loop
|
|
166
|
+
const rawResult = await script.run(context, { timeout: this._timeout });
|
|
167
|
+
const executionMs = performance.now() - startMs;
|
|
168
|
+
// ── Step 4: Output size guard ───────────────
|
|
169
|
+
if (typeof rawResult === 'string' && rawResult.length > this._maxOutputBytes) {
|
|
170
|
+
return {
|
|
171
|
+
ok: false,
|
|
172
|
+
error: `Output size (${rawResult.length} bytes) exceeds limit (${this._maxOutputBytes} bytes). ` +
|
|
173
|
+
'Use more selective filters to reduce the result set.',
|
|
174
|
+
code: 'OUTPUT_TOO_LARGE',
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
// ── Step 5: Parse result ────────────────────
|
|
178
|
+
const parsed = typeof rawResult === 'string' ? JSON.parse(rawResult) : rawResult;
|
|
179
|
+
return { ok: true, value: parsed, executionMs };
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
const executionMs = performance.now() - startMs;
|
|
183
|
+
return this._classifyError(err, executionMs);
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
// ── MANDATORY C++ POINTER RELEASE ────────────
|
|
187
|
+
// Order matters: release inner resources first
|
|
188
|
+
try {
|
|
189
|
+
inputCopy?.release();
|
|
190
|
+
}
|
|
191
|
+
catch { /* already released or isolate dead */ }
|
|
192
|
+
try {
|
|
193
|
+
script?.release();
|
|
194
|
+
}
|
|
195
|
+
catch { /* already released or isolate dead */ }
|
|
196
|
+
try {
|
|
197
|
+
context?.release();
|
|
198
|
+
}
|
|
199
|
+
catch { /* already released or isolate dead */ }
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Release all resources held by this engine.
|
|
204
|
+
*
|
|
205
|
+
* After calling `dispose()`, any subsequent `execute()` calls
|
|
206
|
+
* will return `{ ok: false, code: 'UNAVAILABLE' }`.
|
|
207
|
+
*/
|
|
208
|
+
dispose() {
|
|
209
|
+
if (this._disposed)
|
|
210
|
+
return;
|
|
211
|
+
this._disposed = true;
|
|
212
|
+
try {
|
|
213
|
+
this._isolate?.dispose();
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
// Isolate may already be dead (OOM)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Check if the engine has been disposed.
|
|
221
|
+
*/
|
|
222
|
+
get isDisposed() {
|
|
223
|
+
return this._disposed;
|
|
224
|
+
}
|
|
225
|
+
// ── Private ──────────────────────────────────────────
|
|
226
|
+
/**
|
|
227
|
+
* Ensure the isolate is alive. If it died (OOM), create a new one.
|
|
228
|
+
* @internal
|
|
229
|
+
*/
|
|
230
|
+
_ensureIsolate() {
|
|
231
|
+
const ivm = getIvm();
|
|
232
|
+
// Check if isolate is still usable
|
|
233
|
+
try {
|
|
234
|
+
if (this._isolate?.isDisposed) {
|
|
235
|
+
this._isolate = new ivm.Isolate({ memoryLimit: this._memoryLimit });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
// isDisposed threw → isolate is dead, recreate
|
|
240
|
+
this._isolate = new ivm.Isolate({ memoryLimit: this._memoryLimit });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Classify an error from V8 execution into a typed SandboxResult.
|
|
245
|
+
* @internal
|
|
246
|
+
*/
|
|
247
|
+
_classifyError(err, executionMs) {
|
|
248
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
249
|
+
// Timeout: isolated-vm throws a specific error
|
|
250
|
+
if (message.includes('Script execution timed out')) {
|
|
251
|
+
return {
|
|
252
|
+
ok: false,
|
|
253
|
+
error: `Script execution timed out (${this._timeout}ms). ` +
|
|
254
|
+
'Simplify the computation or increase the timeout limit.',
|
|
255
|
+
code: 'TIMEOUT',
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
// Memory: V8 kills the isolate
|
|
259
|
+
if (message.includes('Isolate was disposed') ||
|
|
260
|
+
message.includes('out of memory') ||
|
|
261
|
+
message.includes('allocation failed')) {
|
|
262
|
+
// Mark isolate for recreation on next call
|
|
263
|
+
try {
|
|
264
|
+
this._isolate?.dispose();
|
|
265
|
+
}
|
|
266
|
+
catch { /* ignore */ }
|
|
267
|
+
return {
|
|
268
|
+
ok: false,
|
|
269
|
+
error: `Isolate ran out of memory (${this._memoryLimit}MB limit). ` +
|
|
270
|
+
'Reduce the data size or simplify the computation.',
|
|
271
|
+
code: 'MEMORY',
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
// Syntax: V8 compilation error
|
|
275
|
+
if (message.includes('SyntaxError')) {
|
|
276
|
+
return {
|
|
277
|
+
ok: false,
|
|
278
|
+
error: `JavaScript syntax error: ${message}`,
|
|
279
|
+
code: 'SYNTAX',
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
// Runtime: any other V8 error (ReferenceError, TypeError, etc.)
|
|
283
|
+
return {
|
|
284
|
+
ok: false,
|
|
285
|
+
error: `Runtime error: ${message}`,
|
|
286
|
+
code: 'RUNTIME',
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
//# sourceMappingURL=SandboxEngine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SandboxEngine.js","sourceRoot":"","sources":["../../src/sandbox/SandboxEngine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SandboxGuard — Fail-Fast Syntax Checker for LLM-Provided Code
|
|
3
|
+
*
|
|
4
|
+
* Provides quick feedback BEFORE sending code to the isolated-vm engine.
|
|
5
|
+
* This is NOT a security boundary — security comes from the empty V8
|
|
6
|
+
* Context (no `process`, `require`, `fs`, or `globalThis` injected).
|
|
7
|
+
*
|
|
8
|
+
* Purpose:
|
|
9
|
+
* - Validate that the code is syntactically valid JavaScript
|
|
10
|
+
* - Check that it looks like a function expression / arrow function
|
|
11
|
+
* - Provide fast, descriptive error messages to the LLM
|
|
12
|
+
*
|
|
13
|
+
* Properties:
|
|
14
|
+
* - Zero runtime dependencies (pure string analysis)
|
|
15
|
+
* - Fail-fast: rejects obviously broken code before V8 boot
|
|
16
|
+
* - NOT a security gate (LLMs can obfuscate; the Isolate is the real wall)
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
export interface GuardResult {
|
|
22
|
+
/** Whether the code passed the fail-fast check */
|
|
23
|
+
readonly ok: boolean;
|
|
24
|
+
/** Human-readable reason for rejection (present when `ok` is false) */
|
|
25
|
+
readonly violation?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Validate LLM-provided code before sending it to the sandbox.
|
|
29
|
+
*
|
|
30
|
+
* Performs two checks:
|
|
31
|
+
* 1. **Shape check**: The code must look like a function expression
|
|
32
|
+
* 2. **Suspicious pattern check**: Fail-fast for obviously unsandboxable patterns
|
|
33
|
+
*
|
|
34
|
+
* @param code - The JavaScript code string from the LLM
|
|
35
|
+
* @returns A `GuardResult` indicating whether the code passed
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* const result = validateSandboxCode('(data) => data.filter(d => d.x > 5)');
|
|
40
|
+
* // { ok: true }
|
|
41
|
+
*
|
|
42
|
+
* const bad = validateSandboxCode('require("fs").readFileSync("/etc/passwd")');
|
|
43
|
+
* // { ok: false, violation: 'Code must be a function expression...' }
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare function validateSandboxCode(code: string): GuardResult;
|
|
47
|
+
//# sourceMappingURL=SandboxGuard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SandboxGuard.d.ts","sourceRoot":"","sources":["../../src/sandbox/SandboxGuard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,MAAM,WAAW,WAAW;IACxB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,uEAAuE;IACvE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC/B;AA+BD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CA8B7D"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SandboxGuard — Fail-Fast Syntax Checker for LLM-Provided Code
|
|
3
|
+
*
|
|
4
|
+
* Provides quick feedback BEFORE sending code to the isolated-vm engine.
|
|
5
|
+
* This is NOT a security boundary — security comes from the empty V8
|
|
6
|
+
* Context (no `process`, `require`, `fs`, or `globalThis` injected).
|
|
7
|
+
*
|
|
8
|
+
* Purpose:
|
|
9
|
+
* - Validate that the code is syntactically valid JavaScript
|
|
10
|
+
* - Check that it looks like a function expression / arrow function
|
|
11
|
+
* - Provide fast, descriptive error messages to the LLM
|
|
12
|
+
*
|
|
13
|
+
* Properties:
|
|
14
|
+
* - Zero runtime dependencies (pure string analysis)
|
|
15
|
+
* - Fail-fast: rejects obviously broken code before V8 boot
|
|
16
|
+
* - NOT a security gate (LLMs can obfuscate; the Isolate is the real wall)
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
// ── Constants ────────────────────────────────────────────
|
|
22
|
+
/**
|
|
23
|
+
* Patterns that indicate the code is NOT a pure function.
|
|
24
|
+
* These are fail-fast hints, not security barriers.
|
|
25
|
+
* The V8 Isolate with an empty Context is the real security wall.
|
|
26
|
+
*/
|
|
27
|
+
const SUSPICIOUS_PATTERNS = [
|
|
28
|
+
{ pattern: /\bimport\s*\(/, reason: 'Dynamic import() is not available in the sandbox.' },
|
|
29
|
+
{ pattern: /\bimport\s+/, reason: 'ES module imports are not available in the sandbox.' },
|
|
30
|
+
{ pattern: /\brequire\s*\(/, reason: 'require() is not available in the sandbox.' },
|
|
31
|
+
];
|
|
32
|
+
/**
|
|
33
|
+
* The code must start with one of these patterns to be recognized
|
|
34
|
+
* as a function expression or arrow function.
|
|
35
|
+
*/
|
|
36
|
+
const FUNCTION_PATTERNS = [
|
|
37
|
+
/^\s*\(.*\)\s*=>/s, // (x) => ...
|
|
38
|
+
/^\s*[a-zA-Z_$]\w*\s*=>/, // x => ...
|
|
39
|
+
/^\s*function\s*\(/, // function(x) { ... }
|
|
40
|
+
/^\s*function\s+\w+\s*\(/, // function name(x) { ... }
|
|
41
|
+
/^\s*async\s+\(.*\)\s*=>/s, // async (x) => ...
|
|
42
|
+
/^\s*async\s+function\s*\(/, // async function(x) { ... }
|
|
43
|
+
/^\s*async\s+[a-zA-Z_$]\w*\s*=>/, // async x => ...
|
|
44
|
+
];
|
|
45
|
+
// ── Guard Implementation ─────────────────────────────────
|
|
46
|
+
/**
|
|
47
|
+
* Validate LLM-provided code before sending it to the sandbox.
|
|
48
|
+
*
|
|
49
|
+
* Performs two checks:
|
|
50
|
+
* 1. **Shape check**: The code must look like a function expression
|
|
51
|
+
* 2. **Suspicious pattern check**: Fail-fast for obviously unsandboxable patterns
|
|
52
|
+
*
|
|
53
|
+
* @param code - The JavaScript code string from the LLM
|
|
54
|
+
* @returns A `GuardResult` indicating whether the code passed
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* const result = validateSandboxCode('(data) => data.filter(d => d.x > 5)');
|
|
59
|
+
* // { ok: true }
|
|
60
|
+
*
|
|
61
|
+
* const bad = validateSandboxCode('require("fs").readFileSync("/etc/passwd")');
|
|
62
|
+
* // { ok: false, violation: 'Code must be a function expression...' }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export function validateSandboxCode(code) {
|
|
66
|
+
if (!code || typeof code !== 'string') {
|
|
67
|
+
return { ok: false, violation: 'Code must be a non-empty string.' };
|
|
68
|
+
}
|
|
69
|
+
const trimmed = code.trim();
|
|
70
|
+
if (trimmed.length === 0) {
|
|
71
|
+
return { ok: false, violation: 'Code must be a non-empty string.' };
|
|
72
|
+
}
|
|
73
|
+
// Shape check: must look like a function
|
|
74
|
+
const looksLikeFunction = FUNCTION_PATTERNS.some(p => p.test(trimmed));
|
|
75
|
+
if (!looksLikeFunction) {
|
|
76
|
+
return {
|
|
77
|
+
ok: false,
|
|
78
|
+
violation: 'Code must be a function expression or arrow function. ' +
|
|
79
|
+
'Example: (data) => data.filter(d => d.value > 10)',
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
// Suspicious pattern check (fail-fast hints, not security)
|
|
83
|
+
for (const { pattern, reason } of SUSPICIOUS_PATTERNS) {
|
|
84
|
+
if (pattern.test(trimmed)) {
|
|
85
|
+
return { ok: false, violation: reason };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return { ok: true };
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=SandboxGuard.js.map
|