@vinkius-core/mcp-fusion 2.12.0 → 2.13.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.
Files changed (52) hide show
  1. package/dist/core/builder/FluentToolBuilder.d.ts +30 -0
  2. package/dist/core/builder/FluentToolBuilder.d.ts.map +1 -1
  3. package/dist/core/builder/FluentToolBuilder.js +43 -0
  4. package/dist/core/builder/FluentToolBuilder.js.map +1 -1
  5. package/dist/core/builder/GroupedToolBuilder.d.ts +27 -0
  6. package/dist/core/builder/GroupedToolBuilder.d.ts.map +1 -1
  7. package/dist/core/builder/GroupedToolBuilder.js +33 -0
  8. package/dist/core/builder/GroupedToolBuilder.js.map +1 -1
  9. package/dist/core/index.d.ts +1 -0
  10. package/dist/core/index.d.ts.map +1 -1
  11. package/dist/core/index.js.map +1 -1
  12. package/dist/core/initFusion.d.ts +37 -0
  13. package/dist/core/initFusion.d.ts.map +1 -1
  14. package/dist/core/initFusion.js +8 -0
  15. package/dist/core/initFusion.js.map +1 -1
  16. package/dist/core/response.d.ts +2 -1
  17. package/dist/core/response.d.ts.map +1 -1
  18. package/dist/core/response.js +3 -2
  19. package/dist/core/response.js.map +1 -1
  20. package/dist/core/serialization/JsonSerializer.d.ts +71 -0
  21. package/dist/core/serialization/JsonSerializer.d.ts.map +1 -0
  22. package/dist/core/serialization/JsonSerializer.js +192 -0
  23. package/dist/core/serialization/JsonSerializer.js.map +1 -0
  24. package/dist/core/serialization/index.d.ts +7 -0
  25. package/dist/core/serialization/index.d.ts.map +1 -0
  26. package/dist/core/serialization/index.js +7 -0
  27. package/dist/core/serialization/index.js.map +1 -0
  28. package/dist/index.d.ts +10 -1
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +7 -0
  31. package/dist/index.js.map +1 -1
  32. package/dist/presenter/Presenter.d.ts +1 -0
  33. package/dist/presenter/Presenter.d.ts.map +1 -1
  34. package/dist/presenter/Presenter.js +6 -1
  35. package/dist/presenter/Presenter.js.map +1 -1
  36. package/dist/presenter/ResponseBuilder.d.ts +2 -1
  37. package/dist/presenter/ResponseBuilder.d.ts.map +1 -1
  38. package/dist/presenter/ResponseBuilder.js +3 -2
  39. package/dist/presenter/ResponseBuilder.js.map +1 -1
  40. package/dist/sandbox/SandboxEngine.d.ts +202 -0
  41. package/dist/sandbox/SandboxEngine.d.ts.map +1 -0
  42. package/dist/sandbox/SandboxEngine.js +343 -0
  43. package/dist/sandbox/SandboxEngine.js.map +1 -0
  44. package/dist/sandbox/SandboxGuard.d.ts +47 -0
  45. package/dist/sandbox/SandboxGuard.d.ts.map +1 -0
  46. package/dist/sandbox/SandboxGuard.js +90 -0
  47. package/dist/sandbox/SandboxGuard.js.map +1 -0
  48. package/dist/sandbox/index.d.ts +23 -0
  49. package/dist/sandbox/index.d.ts.map +1 -0
  50. package/dist/sandbox/index.js +26 -0
  51. package/dist/sandbox/index.js.map +1 -0
  52. package/package.json +13 -1
@@ -0,0 +1,202 @@
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
+ * 6. AbortSignal kills isolate.dispose() instantly (Connection Watchdog)
15
+ *
16
+ * The `isolated-vm` package is a peerDependency (optional).
17
+ * If not installed, the engine throws a clear error at construction time.
18
+ *
19
+ * ┌─────────────────────────────────────────────────────────┐
20
+ * │ SandboxEngine (owns 1 Isolate) │
21
+ * │ │
22
+ * │ execute(code, data, { signal? }) │
23
+ * │ ┌──────────┐ │
24
+ * │ │ Abort? │ pre-flight signal check │
25
+ * │ ├──────────┤ │
26
+ * │ │ Guard │ fail-fast syntax check │
27
+ * │ ├──────────┤ │
28
+ * │ │ Context │ new per request (empty!) │
29
+ * │ ├──────────┤ │
30
+ * │ │ Copy In │ ExternalCopy (deep, no refs) │
31
+ * │ ├──────────┤ │
32
+ * │ │ Compile │ isolate.compileScript │
33
+ * │ ├──────────┤ │
34
+ * │ │ Run │ script.run (ASYNC, with timeout + abort) │
35
+ * │ ├──────────┤ │
36
+ * │ │ Copy Out │ JSON.parse result │
37
+ * │ └──────────┘ │
38
+ * │ │
39
+ * │ finally: signal.removeEventListener() │
40
+ * │ inputCopy.release() │
41
+ * │ script.release() │
42
+ * │ context.release() │
43
+ * └─────────────────────────────────────────────────────────┘
44
+ *
45
+ * @module
46
+ */
47
+ /**
48
+ * Configuration for a SandboxEngine instance.
49
+ *
50
+ * All fields are optional — sensible defaults are applied.
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * const engine = new SandboxEngine({
55
+ * timeout: 3000, // Kill after 3s
56
+ * memoryLimit: 64, // 64MB per isolate
57
+ * maxOutputBytes: 512_000, // 500KB max result
58
+ * });
59
+ * ```
60
+ */
61
+ export interface SandboxConfig {
62
+ /**
63
+ * Maximum execution time in milliseconds.
64
+ * If the script exceeds this, the V8 isolate kills it.
65
+ * @default 5000
66
+ */
67
+ timeout?: number;
68
+ /**
69
+ * Maximum memory for the V8 isolate in megabytes.
70
+ * If exceeded, the isolate dies and is recreated on next call.
71
+ * @default 128
72
+ */
73
+ memoryLimit?: number;
74
+ /**
75
+ * Maximum size of the serialized output in bytes.
76
+ * Prevents a malicious script from returning gigabytes of data.
77
+ * @default 1_048_576 (1MB)
78
+ */
79
+ maxOutputBytes?: number;
80
+ }
81
+ /**
82
+ * Error codes for sandbox execution failures.
83
+ *
84
+ * - `TIMEOUT`: Script exceeded the time limit
85
+ * - `MEMORY`: Isolate ran out of memory (auto-recovered)
86
+ * - `SYNTAX`: JavaScript syntax error in the provided code
87
+ * - `RUNTIME`: Script threw an error during execution
88
+ * - `OUTPUT_TOO_LARGE`: Result exceeds `maxOutputBytes`
89
+ * - `INVALID_CODE`: Failed the SandboxGuard fail-fast check
90
+ * - `UNAVAILABLE`: `isolated-vm` is not installed
91
+ * - `ABORTED`: Execution was cancelled via AbortSignal (client disconnect)
92
+ */
93
+ export type SandboxErrorCode = 'TIMEOUT' | 'MEMORY' | 'SYNTAX' | 'RUNTIME' | 'OUTPUT_TOO_LARGE' | 'INVALID_CODE' | 'UNAVAILABLE' | 'ABORTED';
94
+ /**
95
+ * Result of a sandbox execution.
96
+ *
97
+ * @example
98
+ * ```typescript
99
+ * const result = await engine.execute('(data) => data.length', [1, 2, 3]);
100
+ * if (result.ok) {
101
+ * console.log(result.value); // 3
102
+ * console.log(result.executionMs); // 0.42
103
+ * } else {
104
+ * console.log(result.code); // 'TIMEOUT'
105
+ * console.log(result.error); // 'Script execution timed out (5000ms)'
106
+ * }
107
+ * ```
108
+ */
109
+ export type SandboxResult<T = unknown> = {
110
+ readonly ok: true;
111
+ readonly value: T;
112
+ readonly executionMs: number;
113
+ } | {
114
+ readonly ok: false;
115
+ readonly error: string;
116
+ readonly code: SandboxErrorCode;
117
+ };
118
+ /**
119
+ * Zero-trust V8 sandbox for executing LLM-provided JavaScript.
120
+ *
121
+ * Creates a single V8 `Isolate` at construction time and reuses it
122
+ * across all `execute()` calls. Each call gets a fresh, empty `Context`
123
+ * with no dangerous globals (no `process`, `require`, `fs`, etc.).
124
+ *
125
+ * If the isolate dies (e.g., OOM), it is automatically recreated
126
+ * on the next `execute()` call.
127
+ *
128
+ * @example
129
+ * ```typescript
130
+ * const sandbox = new SandboxEngine({ timeout: 3000, memoryLimit: 64 });
131
+ *
132
+ * const result = await sandbox.execute(
133
+ * '(data) => data.filter(d => d.risk > 90)',
134
+ * [{ name: 'A', risk: 95 }, { name: 'B', risk: 30 }],
135
+ * );
136
+ *
137
+ * if (result.ok) {
138
+ * console.log(result.value); // [{ name: 'A', risk: 95 }]
139
+ * }
140
+ *
141
+ * // IMPORTANT: dispose when no longer needed
142
+ * sandbox.dispose();
143
+ * ```
144
+ */
145
+ export declare class SandboxEngine {
146
+ private readonly _timeout;
147
+ private readonly _memoryLimit;
148
+ private readonly _maxOutputBytes;
149
+ private _isolate;
150
+ private _disposed;
151
+ constructor(config?: SandboxConfig);
152
+ /**
153
+ * Execute a JavaScript function string against the provided data.
154
+ *
155
+ * The function is compiled and run in a sealed V8 isolate with:
156
+ * - No `process`, `require`, `fs`, or network access
157
+ * - Strict timeout enforcement (async, non-blocking)
158
+ * - Memory limit enforcement
159
+ * - Automatic C++ pointer cleanup (ExternalCopy, Script, Context)
160
+ * - Cooperative cancellation via AbortSignal (Connection Watchdog)
161
+ *
162
+ * @param code - A JavaScript function expression as a string.
163
+ * Must be an arrow function or function expression.
164
+ * Example: `(data) => data.filter(d => d.value > 10)`
165
+ *
166
+ * @param data - The data to pass into the function.
167
+ * Deeply copied into the isolate (no references leak).
168
+ *
169
+ * @param options - Optional execution options.
170
+ * @param options.signal - AbortSignal for cooperative cancellation.
171
+ * When the signal fires (e.g., MCP client disconnects), the engine
172
+ * calls `isolate.dispose()` to kill the V8 C++ threads instantly.
173
+ * The isolate is auto-recovered on the next `.execute()` call.
174
+ *
175
+ * @returns A `SandboxResult` with the computed value or an error.
176
+ */
177
+ execute<T = unknown>(code: string, data: unknown, options?: {
178
+ signal?: AbortSignal;
179
+ }): Promise<SandboxResult<T>>;
180
+ /**
181
+ * Release all resources held by this engine.
182
+ *
183
+ * After calling `dispose()`, any subsequent `execute()` calls
184
+ * will return `{ ok: false, code: 'UNAVAILABLE' }`.
185
+ */
186
+ dispose(): void;
187
+ /**
188
+ * Check if the engine has been disposed.
189
+ */
190
+ get isDisposed(): boolean;
191
+ /**
192
+ * Ensure the isolate is alive. If it died (OOM), create a new one.
193
+ * @internal
194
+ */
195
+ private _ensureIsolate;
196
+ /**
197
+ * Classify an error from V8 execution into a typed SandboxResult.
198
+ * @internal
199
+ */
200
+ private _classifyError;
201
+ }
202
+ //# sourceMappingURL=SandboxEngine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SandboxEngine.d.ts","sourceRoot":"","sources":["../../src/sandbox/SandboxEngine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;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;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,gBAAgB,GACtB,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,SAAS,GACT,kBAAkB,GAClB,cAAc,GACd,aAAa,GACb,SAAS,CAAC;AAEhB;;;;;;;;;;;;;;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;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACG,OAAO,CAAC,CAAC,GAAG,OAAO,EACrB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,EACb,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GACnC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAkH5B;;;;;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,343 @@
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
+ * 6. AbortSignal kills isolate.dispose() instantly (Connection Watchdog)
15
+ *
16
+ * The `isolated-vm` package is a peerDependency (optional).
17
+ * If not installed, the engine throws a clear error at construction time.
18
+ *
19
+ * ┌─────────────────────────────────────────────────────────┐
20
+ * │ SandboxEngine (owns 1 Isolate) │
21
+ * │ │
22
+ * │ execute(code, data, { signal? }) │
23
+ * │ ┌──────────┐ │
24
+ * │ │ Abort? │ pre-flight signal check │
25
+ * │ ├──────────┤ │
26
+ * │ │ Guard │ fail-fast syntax check │
27
+ * │ ├──────────┤ │
28
+ * │ │ Context │ new per request (empty!) │
29
+ * │ ├──────────┤ │
30
+ * │ │ Copy In │ ExternalCopy (deep, no refs) │
31
+ * │ ├──────────┤ │
32
+ * │ │ Compile │ isolate.compileScript │
33
+ * │ ├──────────┤ │
34
+ * │ │ Run │ script.run (ASYNC, with timeout + abort) │
35
+ * │ ├──────────┤ │
36
+ * │ │ Copy Out │ JSON.parse result │
37
+ * │ └──────────┘ │
38
+ * │ │
39
+ * │ finally: signal.removeEventListener() │
40
+ * │ inputCopy.release() │
41
+ * │ script.release() │
42
+ * │ context.release() │
43
+ * └─────────────────────────────────────────────────────────┘
44
+ *
45
+ * @module
46
+ */
47
+ import { validateSandboxCode } from './SandboxGuard.js';
48
+ // ── Constants ────────────────────────────────────────────
49
+ const DEFAULT_TIMEOUT_MS = 5_000;
50
+ const DEFAULT_MEMORY_LIMIT_MB = 128;
51
+ const DEFAULT_MAX_OUTPUT_BYTES = 1_048_576; // 1MB
52
+ // ── Lazy Require ─────────────────────────────────────────
53
+ /**
54
+ * Lazy-load isolated-vm to avoid hard dependency.
55
+ * Returns `null` if the package is not installed.
56
+ * @internal
57
+ */
58
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
59
+ let _ivm = undefined;
60
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
+ function getIvm() {
62
+ if (_ivm !== undefined)
63
+ return _ivm;
64
+ try {
65
+ // Dynamic import would be cleaner but isolated-vm uses
66
+ // native bindings that require synchronous resolution.
67
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
68
+ _ivm = require('isolated-vm');
69
+ }
70
+ catch {
71
+ _ivm = null;
72
+ }
73
+ return _ivm;
74
+ }
75
+ // ── Engine Implementation ────────────────────────────────
76
+ /**
77
+ * Zero-trust V8 sandbox for executing LLM-provided JavaScript.
78
+ *
79
+ * Creates a single V8 `Isolate` at construction time and reuses it
80
+ * across all `execute()` calls. Each call gets a fresh, empty `Context`
81
+ * with no dangerous globals (no `process`, `require`, `fs`, etc.).
82
+ *
83
+ * If the isolate dies (e.g., OOM), it is automatically recreated
84
+ * on the next `execute()` call.
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * const sandbox = new SandboxEngine({ timeout: 3000, memoryLimit: 64 });
89
+ *
90
+ * const result = await sandbox.execute(
91
+ * '(data) => data.filter(d => d.risk > 90)',
92
+ * [{ name: 'A', risk: 95 }, { name: 'B', risk: 30 }],
93
+ * );
94
+ *
95
+ * if (result.ok) {
96
+ * console.log(result.value); // [{ name: 'A', risk: 95 }]
97
+ * }
98
+ *
99
+ * // IMPORTANT: dispose when no longer needed
100
+ * sandbox.dispose();
101
+ * ```
102
+ */
103
+ export class SandboxEngine {
104
+ _timeout;
105
+ _memoryLimit;
106
+ _maxOutputBytes;
107
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
108
+ _isolate; // ivm.Isolate
109
+ _disposed = false;
110
+ constructor(config) {
111
+ this._timeout = config?.timeout ?? DEFAULT_TIMEOUT_MS;
112
+ this._memoryLimit = config?.memoryLimit ?? DEFAULT_MEMORY_LIMIT_MB;
113
+ this._maxOutputBytes = config?.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
114
+ const ivm = getIvm();
115
+ if (!ivm) {
116
+ throw new Error('SandboxEngine requires the "isolated-vm" package. ' +
117
+ 'Install it with: npm install isolated-vm');
118
+ }
119
+ this._isolate = new ivm.Isolate({ memoryLimit: this._memoryLimit });
120
+ }
121
+ /**
122
+ * Execute a JavaScript function string against the provided data.
123
+ *
124
+ * The function is compiled and run in a sealed V8 isolate with:
125
+ * - No `process`, `require`, `fs`, or network access
126
+ * - Strict timeout enforcement (async, non-blocking)
127
+ * - Memory limit enforcement
128
+ * - Automatic C++ pointer cleanup (ExternalCopy, Script, Context)
129
+ * - Cooperative cancellation via AbortSignal (Connection Watchdog)
130
+ *
131
+ * @param code - A JavaScript function expression as a string.
132
+ * Must be an arrow function or function expression.
133
+ * Example: `(data) => data.filter(d => d.value > 10)`
134
+ *
135
+ * @param data - The data to pass into the function.
136
+ * Deeply copied into the isolate (no references leak).
137
+ *
138
+ * @param options - Optional execution options.
139
+ * @param options.signal - AbortSignal for cooperative cancellation.
140
+ * When the signal fires (e.g., MCP client disconnects), the engine
141
+ * calls `isolate.dispose()` to kill the V8 C++ threads instantly.
142
+ * The isolate is auto-recovered on the next `.execute()` call.
143
+ *
144
+ * @returns A `SandboxResult` with the computed value or an error.
145
+ */
146
+ async execute(code, data, options) {
147
+ if (this._disposed) {
148
+ return { ok: false, error: 'SandboxEngine has been disposed.', code: 'UNAVAILABLE' };
149
+ }
150
+ const signal = options?.signal;
151
+ // ── Step 0: Pre-flight abort check ───────────────
152
+ // If the signal is already aborted (client disconnected before
153
+ // we even started), skip all V8 allocation entirely.
154
+ if (signal?.aborted) {
155
+ return {
156
+ ok: false,
157
+ error: 'Execution aborted: client disconnected before sandbox started.',
158
+ code: 'ABORTED',
159
+ };
160
+ }
161
+ // ── Step 1: Fail-fast guard ─────────────────────
162
+ const guard = validateSandboxCode(code);
163
+ if (!guard.ok) {
164
+ return { ok: false, error: guard.violation, code: 'INVALID_CODE' };
165
+ }
166
+ // ── Step 2: Ensure isolate is alive ─────────────
167
+ this._ensureIsolate();
168
+ const ivm = getIvm();
169
+ const isolate = this._isolate;
170
+ // ── Step 3: Wire abort kill-switch ──────────────
171
+ // When the signal fires, we call isolate.dispose() which
172
+ // immediately kills the V8 C++ threads. The _ensureIsolate()
173
+ // method will recreate a fresh isolate on the next call.
174
+ let aborted = false;
175
+ const onAbort = signal ? () => {
176
+ aborted = true;
177
+ try {
178
+ isolate.dispose();
179
+ }
180
+ catch { /* may already be dead */ }
181
+ } : undefined;
182
+ if (signal && onAbort) {
183
+ signal.addEventListener('abort', onAbort, { once: true });
184
+ }
185
+ // ── Step 4: Execute in sealed context ───────────
186
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
187
+ let inputCopy; // ivm.ExternalCopy
188
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
189
+ let context; // ivm.Context
190
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
191
+ let script; // ivm.Script
192
+ const startMs = performance.now();
193
+ try {
194
+ // Create pristine context (NO globals injected — this IS the security)
195
+ context = await isolate.createContext();
196
+ // Deep-copy data into isolated heap (no references!)
197
+ inputCopy = new ivm.ExternalCopy(data);
198
+ await context.global.set('__input__', inputCopy.copyInto());
199
+ // Compile with wrapper: call the function and serialize result
200
+ const wrappedCode = `const __fn__ = ${code};\nJSON.stringify(__fn__(__input__));`;
201
+ script = await isolate.compileScript(wrappedCode);
202
+ // ASYNC execution — never blocks the Node.js event loop
203
+ const rawResult = await script.run(context, { timeout: this._timeout });
204
+ const executionMs = performance.now() - startMs;
205
+ // ── Step 5: Output size guard ───────────────
206
+ if (typeof rawResult === 'string' && rawResult.length > this._maxOutputBytes) {
207
+ return {
208
+ ok: false,
209
+ error: `Output size (${rawResult.length} bytes) exceeds limit (${this._maxOutputBytes} bytes). ` +
210
+ 'Use more selective filters to reduce the result set.',
211
+ code: 'OUTPUT_TOO_LARGE',
212
+ };
213
+ }
214
+ // ── Step 6: Parse result ────────────────────
215
+ const parsed = typeof rawResult === 'string' ? JSON.parse(rawResult) : rawResult;
216
+ return { ok: true, value: parsed, executionMs };
217
+ }
218
+ catch (err) {
219
+ // If the abort listener disposed the isolate, classify as ABORTED
220
+ // (not MEMORY — the user disconnected, not an OOM condition)
221
+ if (aborted) {
222
+ return {
223
+ ok: false,
224
+ error: 'Execution aborted: client disconnected during sandbox execution.',
225
+ code: 'ABORTED',
226
+ };
227
+ }
228
+ return this._classifyError(err);
229
+ }
230
+ finally {
231
+ // ── MANDATORY ABORT LISTENER CLEANUP ─────────
232
+ // Remove the listener to prevent memory leaks when
233
+ // execution completes before the signal fires.
234
+ if (signal && onAbort) {
235
+ signal.removeEventListener('abort', onAbort);
236
+ }
237
+ // ── MANDATORY C++ POINTER RELEASE ────────────
238
+ // Order matters: release inner resources first.
239
+ // After abort-triggered dispose, these will throw
240
+ // but the catch blocks handle dead-isolate gracefully.
241
+ try {
242
+ inputCopy?.release();
243
+ }
244
+ catch { /* already released or isolate dead */ }
245
+ try {
246
+ script?.release();
247
+ }
248
+ catch { /* already released or isolate dead */ }
249
+ try {
250
+ context?.release();
251
+ }
252
+ catch { /* already released or isolate dead */ }
253
+ }
254
+ }
255
+ /**
256
+ * Release all resources held by this engine.
257
+ *
258
+ * After calling `dispose()`, any subsequent `execute()` calls
259
+ * will return `{ ok: false, code: 'UNAVAILABLE' }`.
260
+ */
261
+ dispose() {
262
+ if (this._disposed)
263
+ return;
264
+ this._disposed = true;
265
+ try {
266
+ this._isolate?.dispose();
267
+ }
268
+ catch {
269
+ // Isolate may already be dead (OOM)
270
+ }
271
+ }
272
+ /**
273
+ * Check if the engine has been disposed.
274
+ */
275
+ get isDisposed() {
276
+ return this._disposed;
277
+ }
278
+ // ── Private ──────────────────────────────────────────
279
+ /**
280
+ * Ensure the isolate is alive. If it died (OOM), create a new one.
281
+ * @internal
282
+ */
283
+ _ensureIsolate() {
284
+ const ivm = getIvm();
285
+ // Check if isolate is still usable
286
+ try {
287
+ if (this._isolate?.isDisposed) {
288
+ this._isolate = new ivm.Isolate({ memoryLimit: this._memoryLimit });
289
+ }
290
+ }
291
+ catch {
292
+ // isDisposed threw → isolate is dead, recreate
293
+ this._isolate = new ivm.Isolate({ memoryLimit: this._memoryLimit });
294
+ }
295
+ }
296
+ /**
297
+ * Classify an error from V8 execution into a typed SandboxResult.
298
+ * @internal
299
+ */
300
+ _classifyError(err) {
301
+ const message = err instanceof Error ? err.message : String(err);
302
+ // Timeout: isolated-vm throws a specific error
303
+ if (message.includes('Script execution timed out')) {
304
+ return {
305
+ ok: false,
306
+ error: `Script execution timed out (${this._timeout}ms). ` +
307
+ 'Simplify the computation or increase the timeout limit.',
308
+ code: 'TIMEOUT',
309
+ };
310
+ }
311
+ // Memory: V8 kills the isolate
312
+ if (message.includes('Isolate was disposed') ||
313
+ message.includes('out of memory') ||
314
+ message.includes('allocation failed')) {
315
+ // Mark isolate for recreation on next call
316
+ try {
317
+ this._isolate?.dispose();
318
+ }
319
+ catch { /* ignore */ }
320
+ return {
321
+ ok: false,
322
+ error: `Isolate ran out of memory (${this._memoryLimit}MB limit). ` +
323
+ 'Reduce the data size or simplify the computation.',
324
+ code: 'MEMORY',
325
+ };
326
+ }
327
+ // Syntax: V8 compilation error
328
+ if (message.includes('SyntaxError')) {
329
+ return {
330
+ ok: false,
331
+ error: `JavaScript syntax error: ${message}`,
332
+ code: 'SYNTAX',
333
+ };
334
+ }
335
+ // Runtime: any other V8 error (ReferenceError, TypeError, etc.)
336
+ return {
337
+ ok: false,
338
+ error: `Runtime error: ${message}`,
339
+ code: 'RUNTIME',
340
+ };
341
+ }
342
+ }
343
+ //# 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"}