@secure-exec/nodejs 0.2.0-rc.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 (68) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +7 -0
  3. package/dist/bindings.d.ts +31 -0
  4. package/dist/bindings.js +67 -0
  5. package/dist/bridge/active-handles.d.ts +22 -0
  6. package/dist/bridge/active-handles.js +112 -0
  7. package/dist/bridge/child-process.d.ts +99 -0
  8. package/dist/bridge/child-process.js +672 -0
  9. package/dist/bridge/dispatch.d.ts +2 -0
  10. package/dist/bridge/dispatch.js +40 -0
  11. package/dist/bridge/fs.d.ts +502 -0
  12. package/dist/bridge/fs.js +3307 -0
  13. package/dist/bridge/index.d.ts +10 -0
  14. package/dist/bridge/index.js +41 -0
  15. package/dist/bridge/module.d.ts +75 -0
  16. package/dist/bridge/module.js +325 -0
  17. package/dist/bridge/network.d.ts +1093 -0
  18. package/dist/bridge/network.js +8651 -0
  19. package/dist/bridge/os.d.ts +13 -0
  20. package/dist/bridge/os.js +256 -0
  21. package/dist/bridge/polyfills.d.ts +9 -0
  22. package/dist/bridge/polyfills.js +67 -0
  23. package/dist/bridge/process.d.ts +121 -0
  24. package/dist/bridge/process.js +1382 -0
  25. package/dist/bridge/whatwg-url.d.ts +67 -0
  26. package/dist/bridge/whatwg-url.js +712 -0
  27. package/dist/bridge-contract.d.ts +774 -0
  28. package/dist/bridge-contract.js +172 -0
  29. package/dist/bridge-handlers.d.ts +199 -0
  30. package/dist/bridge-handlers.js +4263 -0
  31. package/dist/bridge-loader.d.ts +9 -0
  32. package/dist/bridge-loader.js +87 -0
  33. package/dist/bridge-setup.d.ts +1 -0
  34. package/dist/bridge-setup.js +3 -0
  35. package/dist/bridge.js +21652 -0
  36. package/dist/builtin-modules.d.ts +25 -0
  37. package/dist/builtin-modules.js +312 -0
  38. package/dist/default-network-adapter.d.ts +13 -0
  39. package/dist/default-network-adapter.js +351 -0
  40. package/dist/driver.d.ts +87 -0
  41. package/dist/driver.js +191 -0
  42. package/dist/esm-compiler.d.ts +14 -0
  43. package/dist/esm-compiler.js +68 -0
  44. package/dist/execution-driver.d.ts +37 -0
  45. package/dist/execution-driver.js +977 -0
  46. package/dist/host-network-adapter.d.ts +7 -0
  47. package/dist/host-network-adapter.js +279 -0
  48. package/dist/index.d.ts +20 -0
  49. package/dist/index.js +23 -0
  50. package/dist/isolate-bootstrap.d.ts +86 -0
  51. package/dist/isolate-bootstrap.js +125 -0
  52. package/dist/ivm-compat.d.ts +7 -0
  53. package/dist/ivm-compat.js +31 -0
  54. package/dist/kernel-runtime.d.ts +58 -0
  55. package/dist/kernel-runtime.js +535 -0
  56. package/dist/module-access.d.ts +75 -0
  57. package/dist/module-access.js +606 -0
  58. package/dist/module-resolver.d.ts +8 -0
  59. package/dist/module-resolver.js +150 -0
  60. package/dist/os-filesystem.d.ts +42 -0
  61. package/dist/os-filesystem.js +161 -0
  62. package/dist/package-bundler.d.ts +36 -0
  63. package/dist/package-bundler.js +497 -0
  64. package/dist/polyfills.d.ts +17 -0
  65. package/dist/polyfills.js +97 -0
  66. package/dist/worker-adapter.d.ts +21 -0
  67. package/dist/worker-adapter.js +34 -0
  68. package/package.json +123 -0
@@ -0,0 +1,977 @@
1
+ import { createResolutionCache } from "./package-bundler.js";
2
+ import { getConsoleSetupCode } from "@secure-exec/core/internal/shared/console-formatter";
3
+ import { getRequireSetupCode } from "@secure-exec/core/internal/shared/require-setup";
4
+ import { getIsolateRuntimeSource, getInitialBridgeGlobalsSetupCode } from "@secure-exec/core";
5
+ import { transformDynamicImport } from "@secure-exec/core/internal/shared/esm-utils";
6
+ import { createCommandExecutorStub, createFsStub, createNetworkStub, filterEnv, wrapCommandExecutor, wrapFileSystem, wrapNetworkAdapter, } from "@secure-exec/core/internal/shared/permissions";
7
+ import { createV8Runtime } from "@secure-exec/v8";
8
+ import { getRawBridgeCode, getBridgeAttachCode } from "./bridge-loader.js";
9
+ import { createBudgetState, clearActiveHostTimers, killActiveChildProcesses, normalizePayloadLimit, getExecutionTimeoutMs, getTimingMitigation, PAYLOAD_LIMIT_ERROR_CODE, DEFAULT_BRIDGE_BASE64_TRANSFER_BYTES, DEFAULT_ISOLATE_JSON_PAYLOAD_BYTES, DEFAULT_MAX_TIMERS, DEFAULT_MAX_HANDLES, DEFAULT_SANDBOX_CWD, DEFAULT_SANDBOX_HOME, DEFAULT_SANDBOX_TMPDIR, } from "./isolate-bootstrap.js";
10
+ import { shouldRunAsESM } from "./module-resolver.js";
11
+ import { TIMEOUT_ERROR_MESSAGE, TIMEOUT_EXIT_CODE, ProcessTable, SocketTable, TimerTable, } from "@secure-exec/core";
12
+ import { buildCryptoBridgeHandlers, buildConsoleBridgeHandlers, buildKernelHandleDispatchHandlers, buildKernelTimerDispatchHandlers, buildModuleLoadingBridgeHandlers, buildTimerBridgeHandlers, buildFsBridgeHandlers, buildKernelFdBridgeHandlers, buildChildProcessBridgeHandlers, buildNetworkBridgeHandlers, buildNetworkSocketBridgeHandlers, buildModuleResolutionBridgeHandlers, buildPtyBridgeHandlers, createProcessConfigForExecution, resolveHttpServerResponse, } from "./bridge-handlers.js";
13
+ import { flattenBindingTree, BINDING_PREFIX } from "./bindings.js";
14
+ import { createNodeHostNetworkAdapter } from "./host-network-adapter.js";
15
+ const MAX_ERROR_MESSAGE_CHARS = 8192;
16
+ function boundErrorMessage(message) {
17
+ if (message.length <= MAX_ERROR_MESSAGE_CHARS)
18
+ return message;
19
+ return `${message.slice(0, MAX_ERROR_MESSAGE_CHARS)}...[Truncated]`;
20
+ }
21
+ function createBridgeDriverProcess() {
22
+ return {
23
+ writeStdin() { },
24
+ closeStdin() { },
25
+ kill() { },
26
+ wait: async () => 0,
27
+ onStdout: null,
28
+ onStderr: null,
29
+ onExit: null,
30
+ };
31
+ }
32
+ // Shared V8 runtime process — one per Node.js process, lazy-initialized
33
+ let sharedV8Runtime = null;
34
+ let sharedV8RuntimePromise = null;
35
+ async function getSharedV8Runtime() {
36
+ if (sharedV8Runtime?.isAlive)
37
+ return sharedV8Runtime;
38
+ if (sharedV8RuntimePromise)
39
+ return sharedV8RuntimePromise;
40
+ // Build bridge code for snapshot warmup
41
+ const bridgeCode = buildFullBridgeCode();
42
+ sharedV8RuntimePromise = createV8Runtime({
43
+ warmupBridgeCode: bridgeCode,
44
+ }).then((rt) => {
45
+ sharedV8Runtime = rt;
46
+ sharedV8RuntimePromise = null;
47
+ return rt;
48
+ });
49
+ return sharedV8RuntimePromise;
50
+ }
51
+ // Minimal polyfills for APIs the bridge IIFE expects but the Rust V8 runtime doesn't provide.
52
+ const V8_POLYFILLS = `
53
+ if (typeof SharedArrayBuffer === 'undefined') {
54
+ globalThis.SharedArrayBuffer = class SharedArrayBuffer extends ArrayBuffer {};
55
+ var _abBL = Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, 'byteLength');
56
+ if (_abBL) Object.defineProperty(SharedArrayBuffer.prototype, 'byteLength', _abBL);
57
+ Object.defineProperty(SharedArrayBuffer.prototype, 'growable', { get() { return false; } });
58
+ }
59
+ if (!Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, 'resizable')) {
60
+ Object.defineProperty(ArrayBuffer.prototype, 'resizable', { get() { return false; } });
61
+ }
62
+ if (typeof queueMicrotask === 'undefined') globalThis.queueMicrotask = (fn) => Promise.resolve().then(fn);
63
+ if (typeof atob === 'undefined') {
64
+ globalThis.atob = (s) => {
65
+ const b = typeof Buffer !== 'undefined' ? Buffer : null;
66
+ if (b) return b.from(s, 'base64').toString('binary');
67
+ // Fallback: manual base64 decode
68
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
69
+ let out = ''; for (let i = 0; i < s.length;) {
70
+ const a = chars.indexOf(s[i++]), b2 = chars.indexOf(s[i++]), c = chars.indexOf(s[i++]), d = chars.indexOf(s[i++]);
71
+ out += String.fromCharCode((a<<2)|(b2>>4)); if (c!==64) out += String.fromCharCode(((b2&15)<<4)|(c>>2)); if (d!==64) out += String.fromCharCode(((c&3)<<6)|d);
72
+ } return out;
73
+ };
74
+ globalThis.btoa = (s) => {
75
+ const b = typeof Buffer !== 'undefined' ? Buffer : null;
76
+ if (b) return b.from(s, 'binary').toString('base64');
77
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
78
+ let out = ''; for (let i = 0; i < s.length;) {
79
+ const a = s.charCodeAt(i++), b2 = s.charCodeAt(i++), c = s.charCodeAt(i++);
80
+ out += chars[a>>2] + chars[((a&3)<<4)|(b2>>4)] + (isNaN(b2) ? '=' : chars[((b2&15)<<2)|(c>>4)]) + (isNaN(c) ? '=' : chars[c&63]);
81
+ } return out;
82
+ };
83
+ }
84
+ if (typeof TextEncoder === 'undefined') {
85
+ const _encodeUtf8 = (str = '') => {
86
+ const bytes = [];
87
+ for (let i = 0; i < str.length; i++) {
88
+ const codeUnit = str.charCodeAt(i);
89
+ let codePoint = codeUnit;
90
+ if (codeUnit >= 0xD800 && codeUnit <= 0xDBFF) {
91
+ const next = i + 1 < str.length ? str.charCodeAt(i + 1) : 0;
92
+ if (next >= 0xDC00 && next <= 0xDFFF) {
93
+ codePoint = 0x10000 + ((codeUnit - 0xD800) << 10) + (next - 0xDC00);
94
+ i++;
95
+ } else {
96
+ codePoint = 0xFFFD;
97
+ }
98
+ } else if (codeUnit >= 0xDC00 && codeUnit <= 0xDFFF) {
99
+ codePoint = 0xFFFD;
100
+ }
101
+ if (codePoint < 0x80) bytes.push(codePoint);
102
+ else if (codePoint < 0x800) bytes.push(0xC0 | (codePoint >> 6), 0x80 | (codePoint & 63));
103
+ else if (codePoint < 0x10000) bytes.push(0xE0 | (codePoint >> 12), 0x80 | ((codePoint >> 6) & 63), 0x80 | (codePoint & 63));
104
+ else bytes.push(0xF0 | (codePoint >> 18), 0x80 | ((codePoint >> 12) & 63), 0x80 | ((codePoint >> 6) & 63), 0x80 | (codePoint & 63));
105
+ }
106
+ return new Uint8Array(bytes);
107
+ };
108
+ globalThis.TextEncoder = class TextEncoder {
109
+ encode(str = '') { return _encodeUtf8(String(str)); }
110
+ get encoding() { return 'utf-8'; }
111
+ };
112
+ }
113
+ if (typeof TextDecoder === 'undefined') {
114
+ globalThis.TextDecoder = class TextDecoder {
115
+ constructor() {}
116
+ decode(buf) { if (!buf) return ''; const u8 = new Uint8Array(buf.buffer || buf); let s = ''; for (let i = 0; i < u8.length;) { const b = u8[i++]; if (b < 128) s += String.fromCharCode(b); else if (b < 224) s += String.fromCharCode(((b&31)<<6)|(u8[i++]&63)); else if (b < 240) { const b2 = u8[i++]; s += String.fromCharCode(((b&15)<<12)|((b2&63)<<6)|(u8[i++]&63)); } else { const b2 = u8[i++], b3 = u8[i++], cp = ((b&7)<<18)|((b2&63)<<12)|((b3&63)<<6)|(u8[i++]&63); if (cp>0xFFFF) { const s2 = cp-0x10000; s += String.fromCharCode(0xD800+(s2>>10), 0xDC00+(s2&0x3FF)); } else s += String.fromCharCode(cp); } } return s; }
117
+ get encoding() { return 'utf-8'; }
118
+ };
119
+ }
120
+ if (typeof URL === 'undefined') {
121
+ globalThis.URL = class URL {
122
+ constructor(url, base) { const m = String(base ? new URL(base).href : ''); const full = url.startsWith('http') ? url : m.replace(/\\/[^\\/]*$/, '/') + url; const pm = full.match(/^(\\w+:)\\/\\/([^/:]+)(:\\d+)?(.*)$/); this.protocol = pm?.[1]||''; this.hostname = pm?.[2]||''; this.port = (pm?.[3]||'').slice(1); this.pathname = (pm?.[4]||'/').split('?')[0].split('#')[0]; this.search = full.includes('?') ? '?'+full.split('?')[1].split('#')[0] : ''; this.hash = full.includes('#') ? '#'+full.split('#')[1] : ''; this.host = this.hostname + (this.port ? ':'+this.port : ''); this.href = this.protocol+'//'+this.host+this.pathname+this.search+this.hash; this.origin = this.protocol+'//'+this.host; this.searchParams = typeof URLSearchParams !== 'undefined' ? new URLSearchParams(this.search) : { get:()=>null }; }
123
+ toString() { return this.href; }
124
+ };
125
+ }
126
+ if (typeof URLSearchParams === 'undefined') {
127
+ globalThis.URLSearchParams = class URLSearchParams {
128
+ constructor(init) { this._map = new Map(); if (typeof init === 'string') { for (const p of init.replace(/^\\?/,'').split('&')) { const [k,...v] = p.split('='); if (k) this._map.set(decodeURIComponent(k), decodeURIComponent(v.join('='))); } } }
129
+ get(k) { return this._map.get(k) ?? null; }
130
+ has(k) { return this._map.has(k); }
131
+ toString() { return [...this._map].map(([k,v])=>encodeURIComponent(k)+'='+encodeURIComponent(v)).join('&'); }
132
+ };
133
+ }
134
+ if (typeof structuredClone === 'undefined') {
135
+ globalThis.structuredClone = (obj) => JSON.parse(JSON.stringify(obj));
136
+ }
137
+ if (typeof performance === 'undefined') {
138
+ globalThis.performance = { now: () => Date.now(), timeOrigin: Date.now() };
139
+ }
140
+ if (
141
+ typeof AbortController === 'undefined' ||
142
+ typeof AbortSignal === 'undefined' ||
143
+ typeof AbortSignal.prototype?.addEventListener !== 'function' ||
144
+ typeof AbortSignal.prototype?.removeEventListener !== 'function'
145
+ ) {
146
+ const abortSignalState = new WeakMap();
147
+ function getAbortSignalState(signal) {
148
+ const state = abortSignalState.get(signal);
149
+ if (!state) throw new Error('Invalid AbortSignal');
150
+ return state;
151
+ }
152
+ class AbortSignal {
153
+ constructor() {
154
+ this.onabort = null;
155
+ abortSignalState.set(this, {
156
+ aborted: false,
157
+ reason: undefined,
158
+ listeners: [],
159
+ });
160
+ }
161
+ get aborted() {
162
+ return getAbortSignalState(this).aborted;
163
+ }
164
+ get reason() {
165
+ return getAbortSignalState(this).reason;
166
+ }
167
+ get _listeners() {
168
+ return getAbortSignalState(this).listeners.slice();
169
+ }
170
+ getEventListeners(type) {
171
+ if (type !== 'abort') return [];
172
+ return getAbortSignalState(this).listeners.slice();
173
+ }
174
+ addEventListener(type, listener) {
175
+ if (type !== 'abort' || typeof listener !== 'function') return;
176
+ getAbortSignalState(this).listeners.push(listener);
177
+ }
178
+ removeEventListener(type, listener) {
179
+ if (type !== 'abort' || typeof listener !== 'function') return;
180
+ const listeners = getAbortSignalState(this).listeners;
181
+ const index = listeners.indexOf(listener);
182
+ if (index !== -1) {
183
+ listeners.splice(index, 1);
184
+ }
185
+ }
186
+ dispatchEvent(event) {
187
+ if (!event || event.type !== 'abort') return false;
188
+ if (typeof this.onabort === 'function') {
189
+ try {
190
+ this.onabort.call(this, event);
191
+ } catch {}
192
+ }
193
+ const listeners = getAbortSignalState(this).listeners.slice();
194
+ for (const listener of listeners) {
195
+ try {
196
+ listener.call(this, event);
197
+ } catch {}
198
+ }
199
+ return true;
200
+ }
201
+ }
202
+ globalThis.AbortSignal = AbortSignal;
203
+ globalThis.AbortController = class AbortController {
204
+ constructor() {
205
+ this.signal = new AbortSignal();
206
+ }
207
+ abort(reason) {
208
+ const state = getAbortSignalState(this.signal);
209
+ if (state.aborted) return;
210
+ state.aborted = true;
211
+ state.reason = reason;
212
+ this.signal.dispatchEvent({ type: 'abort' });
213
+ }
214
+ };
215
+ }
216
+ if (
217
+ typeof globalThis.AbortSignal === 'function' &&
218
+ typeof globalThis.AbortController === 'function' &&
219
+ typeof globalThis.AbortSignal.abort !== 'function'
220
+ ) {
221
+ globalThis.AbortSignal.abort = function abort(reason) {
222
+ const controller = new globalThis.AbortController();
223
+ controller.abort(reason);
224
+ return controller.signal;
225
+ };
226
+ }
227
+ if (typeof navigator === 'undefined') {
228
+ globalThis.navigator = { userAgent: 'secure-exec-v8' };
229
+ }
230
+ `;
231
+ // Shim for ivm.Reference methods used by bridge code.
232
+ // Bridge globals in the V8 runtime are plain functions, but the bridge code
233
+ // (compiled from @secure-exec/core) calls them via .applySync(), .apply(), and
234
+ // .applySyncPromise() which are ivm Reference calling patterns.
235
+ // Shim for native bridge functions (runs early in postRestoreScript)
236
+ const BRIDGE_NATIVE_SHIM = `
237
+ (function() {
238
+ var _origApply = Function.prototype.apply;
239
+ function shimBridgeGlobal(name) {
240
+ var fn = globalThis[name];
241
+ if (typeof fn !== 'function' || fn.applySync) return;
242
+ fn.applySync = function(_, args) { return _origApply.call(fn, null, args || []); };
243
+ fn.applySyncPromise = function(_, args) { return _origApply.call(fn, null, args || []); };
244
+ fn.derefInto = function() { return fn; };
245
+ }
246
+ var keys = Object.getOwnPropertyNames(globalThis).filter(function(k) { return k.startsWith('_') && typeof globalThis[k] === 'function'; });
247
+ keys.forEach(shimBridgeGlobal);
248
+ })();
249
+ `;
250
+ // Dispatch shim for bridge globals not natively supported by the V8 binary.
251
+ // Installs dispatch wrappers for ALL known bridge globals that aren't already
252
+ // functions. This runs BEFORE require-setup so the crypto/net module code
253
+ // detects the dispatch-wrapped globals and installs the corresponding APIs.
254
+ function buildBridgeDispatchShim() {
255
+ const K = HOST_BRIDGE_GLOBAL_KEYS;
256
+ // Collect all bridge global names from the contract
257
+ const allGlobals = Object.values(K).filter(v => typeof v === "string");
258
+ return `
259
+ (function() {
260
+ var _origApply = Function.prototype.apply;
261
+ function encodeDispatchArgs(args) {
262
+ return JSON.stringify(args, function(_key, value) {
263
+ if (value === undefined) {
264
+ return { __secureExecDispatchType: 'undefined' };
265
+ }
266
+ return value;
267
+ });
268
+ }
269
+ var names = ${JSON.stringify(allGlobals)};
270
+ for (var i = 0; i < names.length; i++) {
271
+ var name = names[i];
272
+ if (typeof globalThis[name] === 'function') continue;
273
+ (function(n) {
274
+ function reviveDispatchError(payload) {
275
+ var error = new Error(payload && payload.message ? payload.message : String(payload));
276
+ if (payload && payload.name) error.name = payload.name;
277
+ if (payload && payload.code !== undefined) error.code = payload.code;
278
+ if (payload && payload.stack) error.stack = payload.stack;
279
+ return error;
280
+ }
281
+ var fn = function() {
282
+ var args = Array.prototype.slice.call(arguments);
283
+ var encoded = "__bd:" + n + ":" + encodeDispatchArgs(args);
284
+ var resultJson = _loadPolyfill.applySyncPromise(undefined, [encoded]);
285
+ if (resultJson === null) return undefined;
286
+ try {
287
+ var parsed = JSON.parse(resultJson);
288
+ if (parsed.__bd_error) throw reviveDispatchError(parsed.__bd_error);
289
+ return parsed.__bd_result;
290
+ } catch (e) {
291
+ if (e.message && e.message.startsWith('No handler:')) return undefined;
292
+ throw e;
293
+ }
294
+ };
295
+ fn.applySync = function(_, args) { return _origApply.call(fn, null, args || []); };
296
+ fn.applySyncPromise = function(_, args) { return _origApply.call(fn, null, args || []); };
297
+ fn.derefInto = function() { return fn; };
298
+ globalThis[n] = fn;
299
+ })(name);
300
+ }
301
+ })();
302
+ `;
303
+ }
304
+ const BRIDGE_DISPATCH_SHIM = buildBridgeDispatchShim();
305
+ // Cache assembled bridge code (same across all executions)
306
+ let bridgeCodeCache = null;
307
+ function buildFullBridgeCode() {
308
+ if (bridgeCodeCache)
309
+ return bridgeCodeCache;
310
+ // Assemble the full bridge code IIFE from component scripts.
311
+ // Only include code that can run without bridge calls (snapshot phase).
312
+ // Console/require/fsFacade setup goes in postRestoreScript where bridge calls work.
313
+ const parts = [
314
+ // Polyfill missing Web APIs for the Rust V8 runtime
315
+ V8_POLYFILLS,
316
+ getIsolateRuntimeSource("globalExposureHelpers"),
317
+ getInitialBridgeGlobalsSetupCode(),
318
+ getRawBridgeCode(),
319
+ getBridgeAttachCode(),
320
+ ];
321
+ bridgeCodeCache = parts.join("\n");
322
+ return bridgeCodeCache;
323
+ }
324
+ export class NodeExecutionDriver {
325
+ state;
326
+ memoryLimit;
327
+ disposed = false;
328
+ flattenedBindings = null;
329
+ // Unwrapped filesystem for path translation (toHostPath/toSandboxPath)
330
+ rawFilesystem;
331
+ // Kernel socket table for routing net.connect through kernel
332
+ socketTable;
333
+ // Kernel process table for child process registration
334
+ processTable;
335
+ timerTable;
336
+ ownsProcessTable;
337
+ ownsTimerTable;
338
+ configuredMaxTimers;
339
+ configuredMaxHandles;
340
+ pid;
341
+ constructor(options) {
342
+ this.memoryLimit = options.memoryLimit ?? 128;
343
+ const budgets = options.resourceBudgets;
344
+ this.socketTable = options.socketTable;
345
+ this.processTable = options.processTable ?? new ProcessTable();
346
+ this.timerTable = options.timerTable ?? new TimerTable();
347
+ this.ownsProcessTable = options.processTable === undefined;
348
+ this.ownsTimerTable = options.timerTable === undefined;
349
+ this.configuredMaxTimers = budgets?.maxTimers;
350
+ this.configuredMaxHandles = budgets?.maxHandles;
351
+ this.pid = options.pid ?? 1;
352
+ const system = options.system;
353
+ const permissions = system.permissions;
354
+ if (!this.socketTable) {
355
+ this.socketTable = new SocketTable({
356
+ hostAdapter: createNodeHostNetworkAdapter(),
357
+ networkCheck: permissions?.network,
358
+ });
359
+ }
360
+ // Keep unwrapped filesystem for path translation (toHostPath/toSandboxPath)
361
+ this.rawFilesystem = system.filesystem;
362
+ const filesystem = this.rawFilesystem
363
+ ? wrapFileSystem(this.rawFilesystem, permissions)
364
+ : createFsStub();
365
+ const commandExecutor = system.commandExecutor
366
+ ? wrapCommandExecutor(system.commandExecutor, permissions)
367
+ : createCommandExecutorStub();
368
+ const rawNetworkAdapter = system.network;
369
+ const networkAdapter = rawNetworkAdapter
370
+ ? wrapNetworkAdapter(rawNetworkAdapter, permissions)
371
+ : createNetworkStub();
372
+ const loopbackAwareAdapter = networkAdapter;
373
+ if (loopbackAwareAdapter.__setLoopbackPortChecker && this.socketTable) {
374
+ loopbackAwareAdapter.__setLoopbackPortChecker((_hostname, port) => this.socketTable?.findListener({ host: "127.0.0.1", port }) !== null);
375
+ }
376
+ const processConfig = { ...(options.runtime.process ?? {}) };
377
+ processConfig.cwd ??= DEFAULT_SANDBOX_CWD;
378
+ processConfig.env = filterEnv(processConfig.env, permissions);
379
+ const osConfig = { ...(options.runtime.os ?? {}) };
380
+ osConfig.homedir ??= DEFAULT_SANDBOX_HOME;
381
+ osConfig.tmpdir ??= DEFAULT_SANDBOX_TMPDIR;
382
+ const bridgeBase64TransferLimitBytes = normalizePayloadLimit(options.payloadLimits?.base64TransferBytes, DEFAULT_BRIDGE_BASE64_TRANSFER_BYTES, "payloadLimits.base64TransferBytes");
383
+ const isolateJsonPayloadLimitBytes = normalizePayloadLimit(options.payloadLimits?.jsonPayloadBytes, DEFAULT_ISOLATE_JSON_PAYLOAD_BYTES, "payloadLimits.jsonPayloadBytes");
384
+ this.state = {
385
+ filesystem,
386
+ commandExecutor,
387
+ networkAdapter,
388
+ permissions,
389
+ processConfig,
390
+ osConfig,
391
+ onStdio: options.onStdio,
392
+ cpuTimeLimitMs: options.cpuTimeLimitMs,
393
+ timingMitigation: options.timingMitigation ?? "freeze",
394
+ bridgeBase64TransferLimitBytes,
395
+ isolateJsonPayloadLimitBytes,
396
+ maxOutputBytes: budgets?.maxOutputBytes,
397
+ maxBridgeCalls: budgets?.maxBridgeCalls,
398
+ maxChildProcesses: budgets?.maxChildProcesses,
399
+ maxTimers: budgets?.maxTimers,
400
+ maxHandles: budgets?.maxHandles,
401
+ budgetState: createBudgetState(),
402
+ activeHttpServerIds: new Set(),
403
+ activeHttpServerClosers: new Map(),
404
+ pendingHttpServerStarts: { count: 0 },
405
+ activeChildProcesses: new Map(),
406
+ activeHostTimers: new Set(),
407
+ moduleFormatCache: new Map(),
408
+ packageTypeCache: new Map(),
409
+ resolutionCache: createResolutionCache(),
410
+ onPtySetRawMode: options.onPtySetRawMode,
411
+ };
412
+ // Validate and flatten bindings once at construction time
413
+ if (options.bindings) {
414
+ this.flattenedBindings = flattenBindingTree(options.bindings);
415
+ }
416
+ }
417
+ get network() {
418
+ const adapter = this.state.networkAdapter ?? createNetworkStub();
419
+ return {
420
+ fetch: (url, options) => adapter.fetch(url, options),
421
+ dnsLookup: (hostname) => adapter.dnsLookup(hostname),
422
+ httpRequest: (url, options) => adapter.httpRequest(url, options),
423
+ };
424
+ }
425
+ get unsafeIsolate() { return null; }
426
+ hasManagedResources() {
427
+ return (this.state.pendingHttpServerStarts.count > 0 ||
428
+ this.state.activeHttpServerIds.size > 0 ||
429
+ this.state.activeChildProcesses.size > 0 ||
430
+ (!this.ownsProcessTable && this.state.activeHostTimers.size > 0));
431
+ }
432
+ async waitForManagedResources() {
433
+ const graceDeadline = Date.now() + 100;
434
+ // Give async bridge callbacks a moment to register their host-side handles.
435
+ while (!this.disposed && !this.hasManagedResources() && Date.now() < graceDeadline) {
436
+ await new Promise((resolve) => setTimeout(resolve, 10));
437
+ }
438
+ // Keep the session alive while host-managed resources are still active.
439
+ while (!this.disposed && this.hasManagedResources()) {
440
+ await new Promise((resolve) => setTimeout(resolve, 10));
441
+ }
442
+ }
443
+ ensureBridgeProcessEntry(processConfig) {
444
+ if (this.pid === undefined || !this.processTable)
445
+ return;
446
+ const entry = this.processTable.get(this.pid);
447
+ if (!entry || entry.status === "exited") {
448
+ this.processTable.register(this.pid, "node", "node", [], {
449
+ pid: this.pid,
450
+ ppid: 0,
451
+ env: processConfig.env ?? {},
452
+ cwd: processConfig.cwd ?? DEFAULT_SANDBOX_CWD,
453
+ fds: { stdin: 0, stdout: 1, stderr: 2 },
454
+ stdinIsTTY: processConfig.stdinIsTTY,
455
+ stdoutIsTTY: processConfig.stdoutIsTTY,
456
+ stderrIsTTY: processConfig.stderrIsTTY,
457
+ }, createBridgeDriverProcess());
458
+ }
459
+ if (this.ownsProcessTable || this.configuredMaxHandles !== undefined) {
460
+ this.processTable.setHandleLimit(this.pid, this.configuredMaxHandles ?? DEFAULT_MAX_HANDLES);
461
+ }
462
+ if (this.ownsTimerTable || this.configuredMaxTimers !== undefined) {
463
+ this.timerTable.setLimit(this.pid, this.configuredMaxTimers ?? DEFAULT_MAX_TIMERS);
464
+ }
465
+ }
466
+ clearKernelTimersForProcess(pid) {
467
+ for (const timer of this.timerTable.getActiveTimers(pid)) {
468
+ if (timer.hostHandle !== undefined) {
469
+ clearTimeout(timer.hostHandle);
470
+ this.state.activeHostTimers.delete(timer.hostHandle);
471
+ timer.hostHandle = undefined;
472
+ }
473
+ this.timerTable.clearTimer(timer.id);
474
+ }
475
+ }
476
+ finalizeExecutionState(exitCode) {
477
+ if (this.pid === undefined)
478
+ return;
479
+ this.clearKernelTimersForProcess(this.pid);
480
+ if (this.ownsProcessTable && this.processTable) {
481
+ this.processTable.markExited(this.pid, exitCode);
482
+ }
483
+ }
484
+ async createUnsafeContext(_options = {}) {
485
+ return null;
486
+ }
487
+ async run(code, filePath) {
488
+ return this.executeInternal({ mode: "run", code, filePath });
489
+ }
490
+ async exec(code, options) {
491
+ const result = await this.executeInternal({
492
+ mode: "exec",
493
+ code,
494
+ filePath: options?.filePath,
495
+ env: options?.env,
496
+ cwd: options?.cwd,
497
+ stdin: options?.stdin,
498
+ cpuTimeLimitMs: options?.cpuTimeLimitMs,
499
+ timingMitigation: options?.timingMitigation,
500
+ onStdio: options?.onStdio,
501
+ });
502
+ return { code: result.code, errorMessage: result.errorMessage };
503
+ }
504
+ async executeInternal(options) {
505
+ if (this.disposed)
506
+ throw new Error("NodeExecutionDriver has been disposed");
507
+ // Reset per-execution state
508
+ this.state.budgetState = createBudgetState();
509
+ this.state.moduleFormatCache.clear();
510
+ this.state.packageTypeCache.clear();
511
+ this.state.resolutionCache.resolveResults.clear();
512
+ this.state.resolutionCache.packageJsonResults.clear();
513
+ this.state.resolutionCache.existsResults.clear();
514
+ this.state.resolutionCache.statResults.clear();
515
+ const s = this.state;
516
+ const timingMitigation = getTimingMitigation(options.timingMitigation, s.timingMitigation);
517
+ const frozenTimeMs = Date.now();
518
+ const onStdio = options.onStdio ?? s.onStdio;
519
+ const entryIsEsm = await shouldRunAsESM({
520
+ filesystem: s.filesystem,
521
+ packageTypeCache: s.packageTypeCache,
522
+ moduleFormatCache: s.moduleFormatCache,
523
+ isolateJsonPayloadLimitBytes: s.isolateJsonPayloadLimitBytes,
524
+ resolutionCache: s.resolutionCache,
525
+ }, options.code, options.filePath);
526
+ const sessionMode = options.mode === "run" || entryIsEsm ? "run" : "exec";
527
+ const userCode = entryIsEsm
528
+ ? options.code
529
+ : transformDynamicImport(options.code);
530
+ // Get or create V8 runtime
531
+ const v8Runtime = await getSharedV8Runtime();
532
+ const cpuTimeLimitMs = getExecutionTimeoutMs(options.cpuTimeLimitMs, s.cpuTimeLimitMs);
533
+ const sessionOpts = {
534
+ heapLimitMb: this.memoryLimit,
535
+ cpuTimeLimitMs,
536
+ };
537
+ const session = await v8Runtime.createSession(sessionOpts);
538
+ let finalExitCode = 0;
539
+ try {
540
+ const execProcessConfig = createProcessConfigForExecution(options.env || options.cwd
541
+ ? {
542
+ ...s.processConfig,
543
+ ...(options.env ? { env: filterEnv(options.env, s.permissions) } : {}),
544
+ ...(options.cwd ? { cwd: options.cwd } : {}),
545
+ }
546
+ : s.processConfig, timingMitigation, frozenTimeMs);
547
+ this.ensureBridgeProcessEntry(execProcessConfig);
548
+ // Build bridge handlers for this execution
549
+ const cryptoResult = buildCryptoBridgeHandlers();
550
+ const sendStreamEvent = (eventType, payload) => {
551
+ try {
552
+ session.sendStreamEvent(eventType, payload);
553
+ }
554
+ catch {
555
+ // Session may be destroyed
556
+ }
557
+ };
558
+ const netSocketResult = buildNetworkSocketBridgeHandlers({
559
+ dispatch: (socketId, event, data) => {
560
+ const payload = JSON.stringify({ socketId, event, data });
561
+ sendStreamEvent("netSocket", Buffer.from(payload));
562
+ },
563
+ socketTable: this.socketTable,
564
+ pid: this.pid,
565
+ });
566
+ const networkBridgeResult = buildNetworkBridgeHandlers({
567
+ networkAdapter: s.networkAdapter,
568
+ budgetState: s.budgetState,
569
+ maxBridgeCalls: s.maxBridgeCalls,
570
+ isolateJsonPayloadLimitBytes: s.isolateJsonPayloadLimitBytes,
571
+ activeHttpServerIds: s.activeHttpServerIds,
572
+ activeHttpServerClosers: s.activeHttpServerClosers,
573
+ pendingHttpServerStarts: s.pendingHttpServerStarts,
574
+ sendStreamEvent,
575
+ socketTable: this.socketTable,
576
+ pid: this.pid,
577
+ });
578
+ const kernelFdResult = buildKernelFdBridgeHandlers({
579
+ filesystem: s.filesystem,
580
+ budgetState: s.budgetState,
581
+ maxBridgeCalls: s.maxBridgeCalls,
582
+ });
583
+ const kernelTimerDispatchHandlers = buildKernelTimerDispatchHandlers({
584
+ timerTable: this.timerTable,
585
+ pid: this.pid ?? 1,
586
+ budgetState: s.budgetState,
587
+ maxBridgeCalls: s.maxBridgeCalls,
588
+ activeHostTimers: s.activeHostTimers,
589
+ sendStreamEvent,
590
+ });
591
+ const kernelHandleDispatchHandlers = buildKernelHandleDispatchHandlers({
592
+ processTable: this.processTable,
593
+ pid: this.pid ?? 1,
594
+ budgetState: s.budgetState,
595
+ maxBridgeCalls: s.maxBridgeCalls,
596
+ });
597
+ const bridgeHandlers = {
598
+ ...cryptoResult.handlers,
599
+ ...buildConsoleBridgeHandlers({
600
+ onStdio,
601
+ budgetState: s.budgetState,
602
+ maxOutputBytes: s.maxOutputBytes,
603
+ }),
604
+ ...buildModuleLoadingBridgeHandlers({
605
+ filesystem: s.filesystem,
606
+ resolutionCache: s.resolutionCache,
607
+ resolveMode: entryIsEsm ? "import" : "require",
608
+ sandboxToHostPath: (p) => {
609
+ const rfs = this.rawFilesystem;
610
+ return typeof rfs?.toHostPath === "function" ? rfs.toHostPath(p) : null;
611
+ },
612
+ }, {
613
+ // Dispatch handlers routed through _loadPolyfill for V8 runtime compat
614
+ ...cryptoResult.handlers,
615
+ ...networkBridgeResult.handlers,
616
+ ...netSocketResult.handlers,
617
+ ...buildModuleResolutionBridgeHandlers({
618
+ sandboxToHostPath: (p) => {
619
+ const fs = s.filesystem;
620
+ return typeof fs.toHostPath === "function" ? fs.toHostPath(p) : null;
621
+ },
622
+ hostToSandboxPath: (p) => {
623
+ const fs = s.filesystem;
624
+ return typeof fs.toSandboxPath === "function" ? fs.toSandboxPath(p) : p;
625
+ },
626
+ }),
627
+ ...buildPtyBridgeHandlers({
628
+ onPtySetRawMode: s.onPtySetRawMode,
629
+ stdinIsTTY: s.processConfig.stdinIsTTY,
630
+ }),
631
+ // Kernel FD table handlers
632
+ ...kernelFdResult.handlers,
633
+ ...kernelTimerDispatchHandlers,
634
+ ...kernelHandleDispatchHandlers,
635
+ // Custom bindings dispatched through _loadPolyfill
636
+ ...(this.flattenedBindings ? Object.fromEntries(this.flattenedBindings.map(b => [b.key, b.handler])) : {}),
637
+ }),
638
+ ...buildTimerBridgeHandlers({
639
+ budgetState: s.budgetState,
640
+ maxBridgeCalls: s.maxBridgeCalls,
641
+ activeHostTimers: s.activeHostTimers,
642
+ }),
643
+ ...buildFsBridgeHandlers({
644
+ filesystem: s.filesystem,
645
+ budgetState: s.budgetState,
646
+ maxBridgeCalls: s.maxBridgeCalls,
647
+ bridgeBase64TransferLimitBytes: s.bridgeBase64TransferLimitBytes,
648
+ isolateJsonPayloadLimitBytes: s.isolateJsonPayloadLimitBytes,
649
+ }),
650
+ ...buildChildProcessBridgeHandlers({
651
+ commandExecutor: s.commandExecutor,
652
+ processConfig: s.processConfig,
653
+ budgetState: s.budgetState,
654
+ maxBridgeCalls: s.maxBridgeCalls,
655
+ maxChildProcesses: s.maxChildProcesses,
656
+ isolateJsonPayloadLimitBytes: s.isolateJsonPayloadLimitBytes,
657
+ activeChildProcesses: s.activeChildProcesses,
658
+ sendStreamEvent,
659
+ processTable: this.processTable,
660
+ parentPid: this.pid,
661
+ }),
662
+ ...networkBridgeResult.handlers,
663
+ ...netSocketResult.handlers,
664
+ ...buildModuleResolutionBridgeHandlers({
665
+ sandboxToHostPath: (p) => {
666
+ const rfs = this.rawFilesystem;
667
+ return typeof rfs?.toHostPath === "function" ? rfs.toHostPath(p) : null;
668
+ },
669
+ hostToSandboxPath: (p) => {
670
+ const rfs = this.rawFilesystem;
671
+ return typeof rfs?.toSandboxPath === "function" ? rfs.toSandboxPath(p) : p;
672
+ },
673
+ }),
674
+ ...buildPtyBridgeHandlers({
675
+ onPtySetRawMode: s.onPtySetRawMode,
676
+ stdinIsTTY: s.processConfig.stdinIsTTY,
677
+ }),
678
+ };
679
+ // Merge custom bindings into bridge handlers
680
+ if (this.flattenedBindings) {
681
+ for (const binding of this.flattenedBindings) {
682
+ bridgeHandlers[binding.key] = binding.handler;
683
+ }
684
+ }
685
+ // Build bridge code with embedded config
686
+ const bridgeCode = buildFullBridgeCode();
687
+ // Build post-restore script with per-execution config
688
+ const bindingKeys = this.flattenedBindings
689
+ ? this.flattenedBindings.map((b) => b.key.slice(BINDING_PREFIX.length))
690
+ : [];
691
+ const postRestoreScript = buildPostRestoreScript(execProcessConfig, s.osConfig, {
692
+ initialCwd: execProcessConfig.cwd ?? "/",
693
+ jsonPayloadLimitBytes: s.isolateJsonPayloadLimitBytes,
694
+ payloadLimitErrorCode: PAYLOAD_LIMIT_ERROR_CODE,
695
+ maxTimers: s.maxTimers,
696
+ maxHandles: s.maxHandles,
697
+ stdin: options.stdin,
698
+ }, timingMitigation, frozenTimeMs, options.mode, options.filePath, bindingKeys);
699
+ // Execute in V8 session
700
+ const result = await session.execute({
701
+ bridgeCode,
702
+ postRestoreScript,
703
+ userCode,
704
+ mode: sessionMode,
705
+ filePath: options.filePath,
706
+ processConfig: {
707
+ cwd: execProcessConfig.cwd ?? "/",
708
+ env: execProcessConfig.env ?? {},
709
+ timing_mitigation: timingMitigation,
710
+ frozen_time_ms: timingMitigation === "freeze" ? frozenTimeMs : null,
711
+ },
712
+ osConfig: {
713
+ homedir: s.osConfig.homedir ?? DEFAULT_SANDBOX_HOME,
714
+ tmpdir: s.osConfig.tmpdir ?? DEFAULT_SANDBOX_TMPDIR,
715
+ platform: s.osConfig.platform ?? "linux",
716
+ arch: s.osConfig.arch ?? "x64",
717
+ },
718
+ bridgeHandlers,
719
+ onStreamCallback: (callbackType, payload) => {
720
+ // Handle stream callbacks from V8 isolate
721
+ if (callbackType === "httpServerResponse") {
722
+ try {
723
+ const data = JSON.parse(Buffer.from(payload).toString());
724
+ resolveHttpServerResponse({
725
+ requestId: data.requestId !== undefined
726
+ ? Number(data.requestId)
727
+ : undefined,
728
+ serverId: data.serverId !== undefined
729
+ ? Number(data.serverId)
730
+ : undefined,
731
+ responseJson: data.responseJson,
732
+ });
733
+ }
734
+ catch {
735
+ // Invalid payload
736
+ }
737
+ }
738
+ },
739
+ });
740
+ if (options.mode === "exec" && !result.error) {
741
+ await this.waitForManagedResources();
742
+ }
743
+ // Clean up per-execution resources
744
+ cryptoResult.dispose();
745
+ netSocketResult.dispose();
746
+ kernelFdResult.dispose();
747
+ await networkBridgeResult.dispose();
748
+ // Map V8 execution result to RunResult
749
+ if (result.error) {
750
+ const errMessage = result.error.type && result.error.type !== "Error"
751
+ ? `${result.error.type}: ${result.error.message}`
752
+ : result.error.message;
753
+ // Check for timeout
754
+ if (/timed out|time limit exceeded/i.test(errMessage)) {
755
+ finalExitCode = TIMEOUT_EXIT_CODE;
756
+ return {
757
+ code: TIMEOUT_EXIT_CODE,
758
+ errorMessage: TIMEOUT_ERROR_MESSAGE,
759
+ exports: undefined,
760
+ };
761
+ }
762
+ // Check for process.exit()
763
+ const exitMatch = errMessage.match(/process\.exit\((\d+)\)/);
764
+ if (exitMatch) {
765
+ finalExitCode = parseInt(exitMatch[1], 10);
766
+ return {
767
+ code: finalExitCode,
768
+ exports: undefined,
769
+ };
770
+ }
771
+ finalExitCode = result.code || 1;
772
+ return {
773
+ code: finalExitCode,
774
+ errorMessage: boundErrorMessage(errMessage),
775
+ exports: undefined,
776
+ };
777
+ }
778
+ // Parse exports for run() mode
779
+ let exports;
780
+ if (options.mode === "run" && result.exports) {
781
+ try {
782
+ const { deserialize } = await import("node:v8");
783
+ exports = deserialize(result.exports);
784
+ }
785
+ catch {
786
+ exports = undefined;
787
+ }
788
+ }
789
+ finalExitCode = result.code;
790
+ return {
791
+ code: finalExitCode,
792
+ exports,
793
+ };
794
+ }
795
+ catch (err) {
796
+ const errMessage = err instanceof Error
797
+ ? (err.name && err.name !== "Error" ? `${err.name}: ${err.message}` : err.message)
798
+ : String(err);
799
+ if (/timed out|time limit exceeded/i.test(errMessage)) {
800
+ finalExitCode = TIMEOUT_EXIT_CODE;
801
+ return {
802
+ code: TIMEOUT_EXIT_CODE,
803
+ errorMessage: TIMEOUT_ERROR_MESSAGE,
804
+ exports: undefined,
805
+ };
806
+ }
807
+ const exitMatch = errMessage.match(/process\.exit\((\d+)\)/);
808
+ if (exitMatch) {
809
+ finalExitCode = parseInt(exitMatch[1], 10);
810
+ return {
811
+ code: finalExitCode,
812
+ exports: undefined,
813
+ };
814
+ }
815
+ finalExitCode = 1;
816
+ return {
817
+ code: finalExitCode,
818
+ errorMessage: boundErrorMessage(errMessage),
819
+ exports: undefined,
820
+ };
821
+ }
822
+ finally {
823
+ await session.destroy().catch(() => { });
824
+ this.finalizeExecutionState(finalExitCode);
825
+ }
826
+ }
827
+ dispose() {
828
+ if (this.disposed)
829
+ return;
830
+ this.disposed = true;
831
+ killActiveChildProcesses(this.state);
832
+ clearActiveHostTimers(this.state);
833
+ if (this.pid !== undefined) {
834
+ this.clearKernelTimersForProcess(this.pid);
835
+ }
836
+ }
837
+ async terminate() {
838
+ if (this.disposed)
839
+ return;
840
+ killActiveChildProcesses(this.state);
841
+ const closers = Array.from(this.state.activeHttpServerClosers.values());
842
+ await Promise.allSettled(closers.map((close) => close()));
843
+ this.state.activeHttpServerIds.clear();
844
+ this.state.activeHttpServerClosers.clear();
845
+ clearActiveHostTimers(this.state);
846
+ if (this.pid !== undefined) {
847
+ this.clearKernelTimersForProcess(this.pid);
848
+ }
849
+ this.disposed = true;
850
+ }
851
+ }
852
+ /** Build the post-restore script that configures the V8 session per-execution. */
853
+ function buildPostRestoreScript(processConfig, osConfig, bridgeConfig, timingMitigation, frozenTimeMs, mode, filePath, bindingKeys) {
854
+ const parts = [];
855
+ // Shim existing native bridge functions for ivm.Reference compat,
856
+ // then install dispatch wrappers for bridge globals not in the V8 binary
857
+ parts.push(BRIDGE_NATIVE_SHIM);
858
+ parts.push(BRIDGE_DISPATCH_SHIM);
859
+ // Console and require setup (must run in postRestoreScript, not bridgeCode,
860
+ // because bridge calls are muted during the bridgeCode snapshot phase)
861
+ parts.push(getConsoleSetupCode());
862
+ parts.push(getRequireSetupCode());
863
+ parts.push(getIsolateRuntimeSource("setupFsFacade"));
864
+ parts.push(`globalThis.__runtimeDynamicImportConfig = ${JSON.stringify({
865
+ referrerPath: filePath ?? processConfig.cwd ?? bridgeConfig.initialCwd,
866
+ })};`);
867
+ parts.push(getIsolateRuntimeSource("setupDynamicImport"));
868
+ // Inject bridge setup config
869
+ parts.push(`globalThis.__runtimeBridgeSetupConfig = ${JSON.stringify({
870
+ initialCwd: bridgeConfig.initialCwd,
871
+ jsonPayloadLimitBytes: bridgeConfig.jsonPayloadLimitBytes,
872
+ payloadLimitErrorCode: bridgeConfig.payloadLimitErrorCode,
873
+ })};`);
874
+ // Inject process and OS config
875
+ parts.push(`globalThis.${getProcessConfigGlobalKey()} = ${JSON.stringify(processConfig)};`);
876
+ parts.push(`globalThis.${getOsConfigGlobalKey()} = ${JSON.stringify(osConfig)};`);
877
+ // Inject TTY config separately — InjectGlobals overwrites _processConfig,
878
+ // so TTY flags need their own global that persists
879
+ if (processConfig.stdinIsTTY || processConfig.stdoutIsTTY || processConfig.stderrIsTTY) {
880
+ parts.push(`globalThis.__runtimeTtyConfig = ${JSON.stringify({
881
+ stdinIsTTY: processConfig.stdinIsTTY,
882
+ stdoutIsTTY: processConfig.stdoutIsTTY,
883
+ stderrIsTTY: processConfig.stderrIsTTY,
884
+ })};`);
885
+ }
886
+ // Inject timer/handle limits
887
+ if (bridgeConfig.maxTimers !== undefined) {
888
+ parts.push(`globalThis._maxTimers = ${bridgeConfig.maxTimers};`);
889
+ }
890
+ if (bridgeConfig.maxHandles !== undefined) {
891
+ parts.push(`globalThis._maxHandles = ${bridgeConfig.maxHandles};`);
892
+ }
893
+ // Apply timing mitigation
894
+ if (timingMitigation === "freeze") {
895
+ parts.push(`globalThis.__runtimeTimingMitigationConfig = ${JSON.stringify({ frozenTimeMs })};`);
896
+ parts.push(getIsolateRuntimeSource("applyTimingMitigationFreeze"));
897
+ }
898
+ else {
899
+ parts.push(getIsolateRuntimeSource("applyTimingMitigationOff"));
900
+ }
901
+ // Apply execution overrides (env, cwd, stdin) for exec mode
902
+ if (mode === "exec") {
903
+ if (processConfig.env) {
904
+ parts.push(`globalThis.__runtimeProcessEnvOverride = ${JSON.stringify(processConfig.env)};`);
905
+ parts.push(getIsolateRuntimeSource("overrideProcessEnv"));
906
+ }
907
+ if (processConfig.cwd) {
908
+ parts.push(`globalThis.__runtimeProcessCwdOverride = ${JSON.stringify(processConfig.cwd)};`);
909
+ parts.push(getIsolateRuntimeSource("overrideProcessCwd"));
910
+ }
911
+ if (bridgeConfig.stdin !== undefined) {
912
+ parts.push(`globalThis.__runtimeStdinData = ${JSON.stringify(bridgeConfig.stdin)};`);
913
+ parts.push(getIsolateRuntimeSource("setStdinData"));
914
+ }
915
+ // Set CommonJS globals
916
+ parts.push(getIsolateRuntimeSource("initCommonjsModuleGlobals"));
917
+ if (filePath) {
918
+ const dirname = filePath.includes("/")
919
+ ? filePath.substring(0, filePath.lastIndexOf("/")) || "/"
920
+ : "/";
921
+ parts.push(`globalThis.__runtimeCommonJsFileConfig = ${JSON.stringify({ filePath, dirname })};`);
922
+ parts.push(getIsolateRuntimeSource("setCommonjsFileGlobals"));
923
+ }
924
+ }
925
+ else {
926
+ // run mode — still need CommonJS module globals
927
+ parts.push(getIsolateRuntimeSource("initCommonjsModuleGlobals"));
928
+ if (filePath) {
929
+ const dirname = filePath.includes("/")
930
+ ? filePath.substring(0, filePath.lastIndexOf("/")) || "/"
931
+ : "/";
932
+ parts.push(`globalThis.__runtimeCommonJsFileConfig = ${JSON.stringify({ filePath, dirname })};`);
933
+ parts.push(getIsolateRuntimeSource("setCommonjsFileGlobals"));
934
+ }
935
+ }
936
+ // Apply custom global exposure policy
937
+ parts.push(`globalThis.__runtimeCustomGlobalPolicy = ${JSON.stringify({
938
+ hardenedGlobals: getHardenedGlobals(),
939
+ mutableGlobals: getMutableGlobals(),
940
+ })};`);
941
+ parts.push(getIsolateRuntimeSource("applyCustomGlobalPolicy"));
942
+ // Inflate SecureExec.bindings from flattened __bind.* globals
943
+ parts.push(buildBindingsInflationSnippet(bindingKeys ?? []));
944
+ return parts.join("\n");
945
+ }
946
+ // Import global exposure policy constants
947
+ import { HARDENED_NODE_CUSTOM_GLOBALS, MUTABLE_NODE_CUSTOM_GLOBALS, } from "@secure-exec/core/internal/shared/global-exposure";
948
+ import { HOST_BRIDGE_GLOBAL_KEYS, } from "./bridge-contract.js";
949
+ function getHardenedGlobals() { return HARDENED_NODE_CUSTOM_GLOBALS; }
950
+ function getMutableGlobals() { return MUTABLE_NODE_CUSTOM_GLOBALS; }
951
+ function getProcessConfigGlobalKey() { return HOST_BRIDGE_GLOBAL_KEYS.processConfig; }
952
+ function getOsConfigGlobalKey() { return HOST_BRIDGE_GLOBAL_KEYS.osConfig; }
953
+ /** Build the JS snippet that inflates __bind.* globals into a frozen SecureExec.bindings tree. */
954
+ function buildBindingsInflationSnippet(bindingKeys) {
955
+ // Build dispatch wrappers for each binding key and assign directly to the
956
+ // tree nodes. Uses _loadPolyfill as the dispatch multiplexer (same as the
957
+ // static dispatch shim for internal bridge globals).
958
+ return `(function(){
959
+ var __bindingKeys__=${JSON.stringify(bindingKeys)};
960
+ var tree={};
961
+ function makeBindFn(bk){
962
+ return function(){var args=Array.prototype.slice.call(arguments);var encoded="__bd:"+bk+":"+JSON.stringify(args);var r=_loadPolyfill.applySyncPromise(undefined,[encoded]);if(r===null)return undefined;try{var p=JSON.parse(r);if(p.__bd_error)throw new Error(p.__bd_error);return p.__bd_result;}catch(e){if(e.message&&e.message.startsWith("No handler:"))return undefined;throw e;}};
963
+ }
964
+ for(var i=0;i<__bindingKeys__.length;i++){
965
+ var parts=__bindingKeys__[i].split(".");
966
+ var node=tree;
967
+ for(var j=0;j<parts.length-1;j++){node[parts[j]]=node[parts[j]]||{};node=node[parts[j]];}
968
+ node[parts[parts.length-1]]=makeBindFn("__bind."+__bindingKeys__[i]);
969
+ }
970
+ function deepFreeze(obj){
971
+ var vals=Object.values(obj);
972
+ for(var k=0;k<vals.length;k++){if(typeof vals[k]==="object"&&vals[k]!==null)deepFreeze(vals[k]);}
973
+ return Object.freeze(obj);
974
+ }
975
+ Object.defineProperty(globalThis,"SecureExec",{value:Object.freeze({bindings:deepFreeze(tree)}),writable:false,enumerable:true,configurable:false});
976
+ })();`;
977
+ }