@secure-exec/nodejs 0.2.0-rc.1 → 0.2.0-rc.2

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.
@@ -41,6 +41,7 @@ const STDLIB_BROWSER_MODULES = new Set([
41
41
  "readline",
42
42
  "repl",
43
43
  "stream",
44
+ "stream/promises",
44
45
  "_stream_duplex",
45
46
  "_stream_passthrough",
46
47
  "_stream_readable",
@@ -111,6 +112,7 @@ const KNOWN_BUILTIN_MODULES = new Set([
111
112
  "path",
112
113
  "querystring",
113
114
  "stream",
115
+ "stream/promises",
114
116
  "stream/web",
115
117
  "string_decoder",
116
118
  "timers",
@@ -270,6 +272,10 @@ export const BUILTIN_NAMED_EXPORTS = {
270
272
  "addAbortSignal",
271
273
  "compose",
272
274
  ],
275
+ "stream/promises": [
276
+ "finished",
277
+ "pipeline",
278
+ ],
273
279
  "stream/web": [
274
280
  "ReadableStream",
275
281
  "ReadableStreamDefaultReader",
@@ -6,6 +6,7 @@
6
6
  * re-export the bridge-provided globalThis objects as proper ESM modules
7
7
  * with both default and named exports.
8
8
  */
9
+ export declare function getBuiltinBindingExpression(moduleName: string): string | null;
9
10
  /** Get a pre-built ESM wrapper for a bridge-backed built-in, or null if not bridge-handled. */
10
11
  export declare function getStaticBuiltinWrapperSource(moduleName: string): string | null;
11
12
  /** Build a custom ESM wrapper for a dynamically-resolved module (e.g. polyfills). */
@@ -41,19 +41,37 @@ const MODULE_FALLBACK_BINDING = "globalThis.bridge?.module || {" +
41
41
  "isBuiltin: () => false," +
42
42
  "builtinModules: []" +
43
43
  "}";
44
+ const STATIC_BUILTIN_BINDINGS = {
45
+ fs: "globalThis.bridge?.fs || globalThis.bridge?.default || {}",
46
+ "fs/promises": "(globalThis.bridge?.fs || globalThis.bridge?.default || {}).promises || {}",
47
+ "stream/promises": 'globalThis._requireFrom("stream/promises", "/")',
48
+ module: MODULE_FALLBACK_BINDING,
49
+ os: "globalThis.bridge?.os || {}",
50
+ http: "globalThis._httpModule || globalThis.bridge?.network?.http || {}",
51
+ https: "globalThis._httpsModule || globalThis.bridge?.network?.https || {}",
52
+ http2: "globalThis._http2Module || {}",
53
+ dns: "globalThis._dnsModule || globalThis.bridge?.network?.dns || {}",
54
+ child_process: "globalThis._childProcessModule || globalThis.bridge?.childProcess || {}",
55
+ process: "globalThis.process || {}",
56
+ v8: "globalThis._moduleCache?.v8 || {}",
57
+ };
44
58
  const STATIC_BUILTIN_WRAPPER_SOURCES = {
45
- fs: buildWrapperSource("globalThis.bridge?.fs || globalThis.bridge?.default || {}", BUILTIN_NAMED_EXPORTS.fs),
46
- "fs/promises": buildWrapperSource("(globalThis.bridge?.fs || globalThis.bridge?.default || {}).promises || {}", BUILTIN_NAMED_EXPORTS["fs/promises"]),
47
- module: buildWrapperSource(MODULE_FALLBACK_BINDING, BUILTIN_NAMED_EXPORTS.module),
48
- os: buildWrapperSource("globalThis.bridge?.os || {}", BUILTIN_NAMED_EXPORTS.os),
49
- http: buildWrapperSource("globalThis._httpModule || globalThis.bridge?.network?.http || {}", BUILTIN_NAMED_EXPORTS.http),
50
- https: buildWrapperSource("globalThis._httpsModule || globalThis.bridge?.network?.https || {}", BUILTIN_NAMED_EXPORTS.https),
51
- http2: buildWrapperSource("globalThis._http2Module || {}", []),
52
- dns: buildWrapperSource("globalThis._dnsModule || globalThis.bridge?.network?.dns || {}", BUILTIN_NAMED_EXPORTS.dns),
53
- child_process: buildWrapperSource("globalThis._childProcessModule || globalThis.bridge?.childProcess || {}", BUILTIN_NAMED_EXPORTS.child_process),
54
- process: buildWrapperSource("globalThis.process || {}", BUILTIN_NAMED_EXPORTS.process),
55
- v8: buildWrapperSource("globalThis._moduleCache?.v8 || {}", []),
59
+ fs: buildWrapperSource(STATIC_BUILTIN_BINDINGS.fs, BUILTIN_NAMED_EXPORTS.fs),
60
+ "fs/promises": buildWrapperSource(STATIC_BUILTIN_BINDINGS["fs/promises"], BUILTIN_NAMED_EXPORTS["fs/promises"]),
61
+ "stream/promises": buildWrapperSource(STATIC_BUILTIN_BINDINGS["stream/promises"], BUILTIN_NAMED_EXPORTS["stream/promises"]),
62
+ module: buildWrapperSource(STATIC_BUILTIN_BINDINGS.module, BUILTIN_NAMED_EXPORTS.module),
63
+ os: buildWrapperSource(STATIC_BUILTIN_BINDINGS.os, BUILTIN_NAMED_EXPORTS.os),
64
+ http: buildWrapperSource(STATIC_BUILTIN_BINDINGS.http, BUILTIN_NAMED_EXPORTS.http),
65
+ https: buildWrapperSource(STATIC_BUILTIN_BINDINGS.https, BUILTIN_NAMED_EXPORTS.https),
66
+ http2: buildWrapperSource(STATIC_BUILTIN_BINDINGS.http2, []),
67
+ dns: buildWrapperSource(STATIC_BUILTIN_BINDINGS.dns, BUILTIN_NAMED_EXPORTS.dns),
68
+ child_process: buildWrapperSource(STATIC_BUILTIN_BINDINGS.child_process, BUILTIN_NAMED_EXPORTS.child_process),
69
+ process: buildWrapperSource(STATIC_BUILTIN_BINDINGS.process, BUILTIN_NAMED_EXPORTS.process),
70
+ v8: buildWrapperSource(STATIC_BUILTIN_BINDINGS.v8, []),
56
71
  };
72
+ export function getBuiltinBindingExpression(moduleName) {
73
+ return STATIC_BUILTIN_BINDINGS[moduleName] ?? null;
74
+ }
57
75
  /** Get a pre-built ESM wrapper for a bridge-backed built-in, or null if not bridge-handled. */
58
76
  export function getStaticBuiltinWrapperSource(moduleName) {
59
77
  return STATIC_BUILTIN_WRAPPER_SOURCES[moduleName] ?? null;
@@ -2,14 +2,14 @@ import { createResolutionCache } from "./package-bundler.js";
2
2
  import { getConsoleSetupCode } from "@secure-exec/core/internal/shared/console-formatter";
3
3
  import { getRequireSetupCode } from "@secure-exec/core/internal/shared/require-setup";
4
4
  import { getIsolateRuntimeSource, getInitialBridgeGlobalsSetupCode } from "@secure-exec/core";
5
- import { transformDynamicImport } from "@secure-exec/core/internal/shared/esm-utils";
6
5
  import { createCommandExecutorStub, createFsStub, createNetworkStub, filterEnv, wrapCommandExecutor, wrapFileSystem, wrapNetworkAdapter, } from "@secure-exec/core/internal/shared/permissions";
7
6
  import { createV8Runtime } from "@secure-exec/v8";
8
7
  import { getRawBridgeCode, getBridgeAttachCode } from "./bridge-loader.js";
9
8
  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";
9
+ import { transformSourceForRequireSync } from "./module-source.js";
10
10
  import { shouldRunAsESM } from "./module-resolver.js";
11
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";
12
+ import { buildCryptoBridgeHandlers, buildConsoleBridgeHandlers, buildKernelHandleDispatchHandlers, buildKernelStdinDispatchHandlers, buildKernelTimerDispatchHandlers, buildModuleLoadingBridgeHandlers, buildMimeBridgeHandlers, buildTimerBridgeHandlers, buildFsBridgeHandlers, buildKernelFdBridgeHandlers, buildChildProcessBridgeHandlers, buildNetworkBridgeHandlers, buildNetworkSocketBridgeHandlers, buildModuleResolutionBridgeHandlers, buildPtyBridgeHandlers, createProcessConfigForExecution, resolveHttpServerResponse, } from "./bridge-handlers.js";
13
13
  import { flattenBindingTree, BINDING_PREFIX } from "./bindings.js";
14
14
  import { createNodeHostNetworkAdapter } from "./host-network-adapter.js";
15
15
  const MAX_ERROR_MESSAGE_CHARS = 8192;
@@ -49,7 +49,62 @@ async function getSharedV8Runtime() {
49
49
  return sharedV8RuntimePromise;
50
50
  }
51
51
  // Minimal polyfills for APIs the bridge IIFE expects but the Rust V8 runtime doesn't provide.
52
+ const REGEXP_COMPAT_POLYFILL = String.raw `
53
+ if (typeof globalThis.RegExp === 'function' && !globalThis.RegExp.__secureExecRgiEmojiCompat) {
54
+ const NativeRegExp = globalThis.RegExp;
55
+ const RGI_EMOJI_PATTERN = '^\\p{RGI_Emoji}$';
56
+ const RGI_EMOJI_BASE_CLASS = '[\\u{00A9}\\u{00AE}\\u{203C}\\u{2049}\\u{2122}\\u{2139}\\u{2194}-\\u{21AA}\\u{231A}-\\u{23FF}\\u{24C2}\\u{25AA}-\\u{27BF}\\u{2934}-\\u{2935}\\u{2B05}-\\u{2B55}\\u{3030}\\u{303D}\\u{3297}\\u{3299}\\u{1F000}-\\u{1FAFF}]';
57
+ const RGI_EMOJI_KEYCAP = '[#*0-9]\\uFE0F?\\u20E3';
58
+ const RGI_EMOJI_FALLBACK_SOURCE =
59
+ '^(?:' +
60
+ RGI_EMOJI_KEYCAP +
61
+ '|\\p{Regional_Indicator}{2}|' +
62
+ RGI_EMOJI_BASE_CLASS +
63
+ '(?:\\uFE0F|\\u200D(?:' +
64
+ RGI_EMOJI_KEYCAP +
65
+ '|' +
66
+ RGI_EMOJI_BASE_CLASS +
67
+ ')|[\\u{1F3FB}-\\u{1F3FF}])*)$';
68
+ try {
69
+ new NativeRegExp(RGI_EMOJI_PATTERN, 'v');
70
+ } catch (error) {
71
+ if (String(error && error.message || error).includes('RGI_Emoji')) {
72
+ function CompatRegExp(pattern, flags) {
73
+ const normalizedPattern =
74
+ pattern instanceof NativeRegExp && flags === undefined
75
+ ? pattern.source
76
+ : String(pattern);
77
+ const normalizedFlags =
78
+ flags === undefined
79
+ ? (pattern instanceof NativeRegExp ? pattern.flags : '')
80
+ : String(flags);
81
+ try {
82
+ return new NativeRegExp(pattern, flags);
83
+ } catch (innerError) {
84
+ if (normalizedPattern === RGI_EMOJI_PATTERN && normalizedFlags === 'v') {
85
+ return new NativeRegExp(RGI_EMOJI_FALLBACK_SOURCE, 'u');
86
+ }
87
+ throw innerError;
88
+ }
89
+ }
90
+ Object.setPrototypeOf(CompatRegExp, NativeRegExp);
91
+ CompatRegExp.prototype = NativeRegExp.prototype;
92
+ Object.defineProperty(CompatRegExp.prototype, 'constructor', {
93
+ value: CompatRegExp,
94
+ writable: true,
95
+ configurable: true,
96
+ });
97
+ CompatRegExp.__secureExecRgiEmojiCompat = true;
98
+ globalThis.RegExp = CompatRegExp;
99
+ }
100
+ }
101
+ }
102
+ `;
52
103
  const V8_POLYFILLS = `
104
+ if (typeof global === 'undefined') {
105
+ globalThis.global = globalThis;
106
+ }
107
+ ${REGEXP_COMPAT_POLYFILL}
53
108
  if (typeof SharedArrayBuffer === 'undefined') {
54
109
  globalThis.SharedArrayBuffer = class SharedArrayBuffer extends ArrayBuffer {};
55
110
  var _abBL = Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, 'byteLength');
@@ -224,9 +279,266 @@ if (
224
279
  return controller.signal;
225
280
  };
226
281
  }
282
+ if (
283
+ typeof globalThis.AbortSignal === 'function' &&
284
+ typeof globalThis.AbortController === 'function' &&
285
+ typeof globalThis.AbortSignal.timeout !== 'function'
286
+ ) {
287
+ globalThis.AbortSignal.timeout = function timeout(milliseconds) {
288
+ const delay = Number(milliseconds);
289
+ if (!Number.isFinite(delay) || delay < 0) {
290
+ throw new RangeError('The value of "milliseconds" is out of range. It must be a finite, non-negative number.');
291
+ }
292
+ const controller = new globalThis.AbortController();
293
+ const timer = setTimeout(() => {
294
+ controller.abort(
295
+ new globalThis.DOMException(
296
+ 'The operation was aborted due to timeout',
297
+ 'TimeoutError',
298
+ ),
299
+ );
300
+ }, delay);
301
+ if (typeof timer?.unref === 'function') {
302
+ timer.unref();
303
+ }
304
+ return controller.signal;
305
+ };
306
+ }
307
+ if (
308
+ typeof globalThis.AbortSignal === 'function' &&
309
+ typeof globalThis.AbortController === 'function' &&
310
+ typeof globalThis.AbortSignal.any !== 'function'
311
+ ) {
312
+ globalThis.AbortSignal.any = function any(signals) {
313
+ if (
314
+ signals === null ||
315
+ signals === undefined ||
316
+ typeof signals[Symbol.iterator] !== 'function'
317
+ ) {
318
+ throw new TypeError('The "signals" argument must be an iterable.');
319
+ }
320
+
321
+ const controller = new globalThis.AbortController();
322
+ const cleanup = [];
323
+ const abortFromSignal = (signal) => {
324
+ for (const dispose of cleanup) {
325
+ dispose();
326
+ }
327
+ cleanup.length = 0;
328
+ controller.abort(signal.reason);
329
+ };
330
+
331
+ for (const signal of signals) {
332
+ if (
333
+ !signal ||
334
+ typeof signal.aborted !== 'boolean' ||
335
+ typeof signal.addEventListener !== 'function' ||
336
+ typeof signal.removeEventListener !== 'function'
337
+ ) {
338
+ throw new TypeError('The "signals" argument must contain only AbortSignal instances.');
339
+ }
340
+ if (signal.aborted) {
341
+ abortFromSignal(signal);
342
+ break;
343
+ }
344
+ const listener = () => {
345
+ abortFromSignal(signal);
346
+ };
347
+ signal.addEventListener('abort', listener, { once: true });
348
+ cleanup.push(() => {
349
+ signal.removeEventListener('abort', listener);
350
+ });
351
+ }
352
+
353
+ return controller.signal;
354
+ };
355
+ }
227
356
  if (typeof navigator === 'undefined') {
228
357
  globalThis.navigator = { userAgent: 'secure-exec-v8' };
229
358
  }
359
+ if (typeof DOMException === 'undefined') {
360
+ const DOM_EXCEPTION_LEGACY_CODES = {
361
+ IndexSizeError: 1,
362
+ DOMStringSizeError: 2,
363
+ HierarchyRequestError: 3,
364
+ WrongDocumentError: 4,
365
+ InvalidCharacterError: 5,
366
+ NoDataAllowedError: 6,
367
+ NoModificationAllowedError: 7,
368
+ NotFoundError: 8,
369
+ NotSupportedError: 9,
370
+ InUseAttributeError: 10,
371
+ InvalidStateError: 11,
372
+ SyntaxError: 12,
373
+ InvalidModificationError: 13,
374
+ NamespaceError: 14,
375
+ InvalidAccessError: 15,
376
+ ValidationError: 16,
377
+ TypeMismatchError: 17,
378
+ SecurityError: 18,
379
+ NetworkError: 19,
380
+ AbortError: 20,
381
+ URLMismatchError: 21,
382
+ QuotaExceededError: 22,
383
+ TimeoutError: 23,
384
+ InvalidNodeTypeError: 24,
385
+ DataCloneError: 25,
386
+ };
387
+ class DOMException extends Error {
388
+ constructor(message = '', name = 'Error') {
389
+ super(String(message));
390
+ this.name = String(name);
391
+ this.code = DOM_EXCEPTION_LEGACY_CODES[this.name] ?? 0;
392
+ }
393
+ get [Symbol.toStringTag]() { return 'DOMException'; }
394
+ }
395
+ for (const [name, code] of Object.entries(DOM_EXCEPTION_LEGACY_CODES)) {
396
+ const constantName = name.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toUpperCase();
397
+ Object.defineProperty(DOMException, constantName, {
398
+ value: code,
399
+ writable: false,
400
+ configurable: false,
401
+ enumerable: true,
402
+ });
403
+ Object.defineProperty(DOMException.prototype, constantName, {
404
+ value: code,
405
+ writable: false,
406
+ configurable: false,
407
+ enumerable: true,
408
+ });
409
+ }
410
+ Object.defineProperty(globalThis, 'DOMException', {
411
+ value: DOMException,
412
+ writable: false,
413
+ configurable: false,
414
+ enumerable: true,
415
+ });
416
+ }
417
+ if (typeof Blob === 'undefined') {
418
+ globalThis.Blob = class Blob {
419
+ constructor(parts = [], options = {}) {
420
+ this._parts = Array.isArray(parts) ? parts.slice() : [];
421
+ this.type = options && options.type ? String(options.type).toLowerCase() : '';
422
+ this.size = this._parts.reduce((total, part) => {
423
+ if (typeof part === 'string') return total + part.length;
424
+ if (part && typeof part.byteLength === 'number') return total + part.byteLength;
425
+ return total;
426
+ }, 0);
427
+ }
428
+ arrayBuffer() { return Promise.resolve(new ArrayBuffer(0)); }
429
+ text() { return Promise.resolve(''); }
430
+ slice() { return new globalThis.Blob(); }
431
+ stream() { throw new Error('Blob.stream is not supported in sandbox'); }
432
+ get [Symbol.toStringTag]() { return 'Blob'; }
433
+ };
434
+ Object.defineProperty(globalThis, 'Blob', {
435
+ value: globalThis.Blob,
436
+ writable: false,
437
+ configurable: false,
438
+ enumerable: true,
439
+ });
440
+ }
441
+ if (typeof File === 'undefined') {
442
+ globalThis.File = class File extends globalThis.Blob {
443
+ constructor(parts = [], name = '', options = {}) {
444
+ super(parts, options);
445
+ this.name = String(name);
446
+ this.lastModified =
447
+ options && typeof options.lastModified === 'number'
448
+ ? options.lastModified
449
+ : Date.now();
450
+ this.webkitRelativePath = '';
451
+ }
452
+ get [Symbol.toStringTag]() { return 'File'; }
453
+ };
454
+ Object.defineProperty(globalThis, 'File', {
455
+ value: globalThis.File,
456
+ writable: false,
457
+ configurable: false,
458
+ enumerable: true,
459
+ });
460
+ }
461
+ if (typeof FormData === 'undefined') {
462
+ class FormData {
463
+ constructor() {
464
+ this._entries = [];
465
+ }
466
+ append(name, value) {
467
+ this._entries.push([String(name), value]);
468
+ }
469
+ get(name) {
470
+ const key = String(name);
471
+ for (const entry of this._entries) {
472
+ if (entry[0] === key) return entry[1];
473
+ }
474
+ return null;
475
+ }
476
+ getAll(name) {
477
+ const key = String(name);
478
+ return this._entries.filter((entry) => entry[0] === key).map((entry) => entry[1]);
479
+ }
480
+ has(name) {
481
+ return this.get(name) !== null;
482
+ }
483
+ delete(name) {
484
+ const key = String(name);
485
+ this._entries = this._entries.filter((entry) => entry[0] !== key);
486
+ }
487
+ entries() {
488
+ return this._entries[Symbol.iterator]();
489
+ }
490
+ [Symbol.iterator]() {
491
+ return this.entries();
492
+ }
493
+ get [Symbol.toStringTag]() { return 'FormData'; }
494
+ }
495
+ Object.defineProperty(globalThis, 'FormData', {
496
+ value: FormData,
497
+ writable: false,
498
+ configurable: false,
499
+ enumerable: true,
500
+ });
501
+ }
502
+ if (typeof MessageEvent === 'undefined') {
503
+ globalThis.MessageEvent = class MessageEvent {
504
+ constructor(type, options = {}) {
505
+ this.type = String(type);
506
+ this.data = Object.prototype.hasOwnProperty.call(options, 'data')
507
+ ? options.data
508
+ : undefined;
509
+ }
510
+ };
511
+ }
512
+ if (typeof MessagePort === 'undefined') {
513
+ globalThis.MessagePort = class MessagePort {
514
+ constructor() {
515
+ this.onmessage = null;
516
+ this._pairedPort = null;
517
+ }
518
+ postMessage(data) {
519
+ const target = this._pairedPort;
520
+ if (!target) return;
521
+ const event = new globalThis.MessageEvent('message', { data });
522
+ if (typeof target.onmessage === 'function') {
523
+ target.onmessage.call(target, event);
524
+ }
525
+ }
526
+ start() {}
527
+ close() {
528
+ this._pairedPort = null;
529
+ }
530
+ };
531
+ }
532
+ if (typeof MessageChannel === 'undefined') {
533
+ globalThis.MessageChannel = class MessageChannel {
534
+ constructor() {
535
+ this.port1 = new globalThis.MessagePort();
536
+ this.port2 = new globalThis.MessagePort();
537
+ this.port1._pairedPort = this.port2;
538
+ this.port2._pairedPort = this.port1;
539
+ }
540
+ };
541
+ }
230
542
  `;
231
543
  // Shim for ivm.Reference methods used by bridge code.
232
544
  // Bridge globals in the V8 runtime are plain functions, but the bridge code
@@ -353,7 +665,7 @@ export class NodeExecutionDriver {
353
665
  const permissions = system.permissions;
354
666
  if (!this.socketTable) {
355
667
  this.socketTable = new SocketTable({
356
- hostAdapter: createNodeHostNetworkAdapter(),
668
+ hostAdapter: system.network ? createNodeHostNetworkAdapter() : undefined,
357
669
  networkCheck: permissions?.network,
358
670
  });
359
671
  }
@@ -402,12 +714,14 @@ export class NodeExecutionDriver {
402
714
  activeHttpServerIds: new Set(),
403
715
  activeHttpServerClosers: new Map(),
404
716
  pendingHttpServerStarts: { count: 0 },
717
+ activeHttpClientRequests: { count: 0 },
405
718
  activeChildProcesses: new Map(),
406
719
  activeHostTimers: new Set(),
407
720
  moduleFormatCache: new Map(),
408
721
  packageTypeCache: new Map(),
409
722
  resolutionCache: createResolutionCache(),
410
723
  onPtySetRawMode: options.onPtySetRawMode,
724
+ liveStdinSource: options.liveStdinSource,
411
725
  };
412
726
  // Validate and flatten bindings once at construction time
413
727
  if (options.bindings) {
@@ -424,7 +738,19 @@ export class NodeExecutionDriver {
424
738
  }
425
739
  get unsafeIsolate() { return null; }
426
740
  hasManagedResources() {
427
- return (this.state.pendingHttpServerStarts.count > 0 ||
741
+ const hasBridgeHandles = this.pid !== undefined &&
742
+ this.processTable !== undefined &&
743
+ (() => {
744
+ try {
745
+ return this.processTable.getHandles(this.pid).size > 0;
746
+ }
747
+ catch {
748
+ return false;
749
+ }
750
+ })();
751
+ return (hasBridgeHandles ||
752
+ this.state.pendingHttpServerStarts.count > 0 ||
753
+ this.state.activeHttpClientRequests.count > 0 ||
428
754
  this.state.activeHttpServerIds.size > 0 ||
429
755
  this.state.activeChildProcesses.size > 0 ||
430
756
  (!this.ownsProcessTable && this.state.activeHostTimers.size > 0));
@@ -526,7 +852,13 @@ export class NodeExecutionDriver {
526
852
  const sessionMode = options.mode === "run" || entryIsEsm ? "run" : "exec";
527
853
  const userCode = entryIsEsm
528
854
  ? options.code
529
- : transformDynamicImport(options.code);
855
+ : (() => {
856
+ const transformed = transformSourceForRequireSync(options.code, options.filePath ?? "/entry.js");
857
+ if (options.mode !== "exec") {
858
+ return transformed;
859
+ }
860
+ return `${transformed}\n;typeof _waitForActiveHandles === "function" ? _waitForActiveHandles() : undefined;`;
861
+ })();
530
862
  // Get or create V8 runtime
531
863
  const v8Runtime = await getSharedV8Runtime();
532
864
  const cpuTimeLimitMs = getExecutionTimeoutMs(options.cpuTimeLimitMs, s.cpuTimeLimitMs);
@@ -571,6 +903,7 @@ export class NodeExecutionDriver {
571
903
  activeHttpServerIds: s.activeHttpServerIds,
572
904
  activeHttpServerClosers: s.activeHttpServerClosers,
573
905
  pendingHttpServerStarts: s.pendingHttpServerStarts,
906
+ activeHttpClientRequests: s.activeHttpClientRequests,
574
907
  sendStreamEvent,
575
908
  socketTable: this.socketTable,
576
909
  pid: this.pid,
@@ -594,6 +927,11 @@ export class NodeExecutionDriver {
594
927
  budgetState: s.budgetState,
595
928
  maxBridgeCalls: s.maxBridgeCalls,
596
929
  });
930
+ const kernelStdinDispatchHandlers = buildKernelStdinDispatchHandlers({
931
+ liveStdinSource: s.liveStdinSource,
932
+ budgetState: s.budgetState,
933
+ maxBridgeCalls: s.maxBridgeCalls,
934
+ });
597
935
  const bridgeHandlers = {
598
936
  ...cryptoResult.handlers,
599
937
  ...buildConsoleBridgeHandlers({
@@ -601,6 +939,7 @@ export class NodeExecutionDriver {
601
939
  budgetState: s.budgetState,
602
940
  maxOutputBytes: s.maxOutputBytes,
603
941
  }),
942
+ ...kernelStdinDispatchHandlers,
604
943
  ...buildModuleLoadingBridgeHandlers({
605
944
  filesystem: s.filesystem,
606
945
  resolutionCache: s.resolutionCache,
@@ -628,6 +967,7 @@ export class NodeExecutionDriver {
628
967
  onPtySetRawMode: s.onPtySetRawMode,
629
968
  stdinIsTTY: s.processConfig.stdinIsTTY,
630
969
  }),
970
+ ...buildMimeBridgeHandlers(),
631
971
  // Kernel FD table handlers
632
972
  ...kernelFdResult.handlers,
633
973
  ...kernelTimerDispatchHandlers,
@@ -900,6 +1240,21 @@ function buildPostRestoreScript(processConfig, osConfig, bridgeConfig, timingMit
900
1240
  }
901
1241
  // Apply execution overrides (env, cwd, stdin) for exec mode
902
1242
  if (mode === "exec") {
1243
+ const commonJsFileConfig = (() => {
1244
+ if (filePath) {
1245
+ const dirname = filePath.includes("/")
1246
+ ? filePath.substring(0, filePath.lastIndexOf("/")) || "/"
1247
+ : "/";
1248
+ return { filePath, dirname };
1249
+ }
1250
+ if (processConfig.cwd) {
1251
+ return {
1252
+ filePath: `${processConfig.cwd.replace(/\/$/, "") || "/"}/[eval].js`,
1253
+ dirname: processConfig.cwd,
1254
+ };
1255
+ }
1256
+ return null;
1257
+ })();
903
1258
  if (processConfig.env) {
904
1259
  parts.push(`globalThis.__runtimeProcessEnvOverride = ${JSON.stringify(processConfig.env)};`);
905
1260
  parts.push(getIsolateRuntimeSource("overrideProcessEnv"));
@@ -914,11 +1269,8 @@ function buildPostRestoreScript(processConfig, osConfig, bridgeConfig, timingMit
914
1269
  }
915
1270
  // Set CommonJS globals
916
1271
  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 })};`);
1272
+ if (commonJsFileConfig) {
1273
+ parts.push(`globalThis.__runtimeCommonJsFileConfig = ${JSON.stringify(commonJsFileConfig)};`);
922
1274
  parts.push(getIsolateRuntimeSource("setCommonjsFileGlobals"));
923
1275
  }
924
1276
  }
@@ -2,9 +2,10 @@
2
2
  * Concrete HostNetworkAdapter for Node.js, delegating to node:net,
3
3
  * node:dgram, and node:dns for real external I/O.
4
4
  */
5
- import * as net from "node:net";
6
5
  import * as dgram from "node:dgram";
7
6
  import * as dns from "node:dns";
7
+ import * as net from "node:net";
8
+ import { IPPROTO_TCP, SOL_SOCKET, SO_KEEPALIVE, TCP_NODELAY, } from "@secure-exec/core/internal/kernel";
8
9
  /**
9
10
  * Queued-read adapter: incoming data/EOF/errors are buffered so that
10
11
  * each read() call returns the next chunk or null for EOF.
@@ -14,7 +15,6 @@ class NodeHostSocket {
14
15
  readQueue = [];
15
16
  waiters = [];
16
17
  ended = false;
17
- errored = null;
18
18
  constructor(socket) {
19
19
  this.socket = socket;
20
20
  socket.on("data", (chunk) => {
@@ -38,7 +38,6 @@ class NodeHostSocket {
38
38
  }
39
39
  });
40
40
  socket.on("error", (err) => {
41
- this.errored = err;
42
41
  // Wake all pending readers with EOF
43
42
  for (const waiter of this.waiters.splice(0)) {
44
43
  waiter(null);
@@ -80,8 +79,13 @@ class NodeHostSocket {
80
79
  });
81
80
  }
82
81
  setOption(level, optname, optval) {
83
- // Forward common options to the real socket
84
- this.socket.setNoDelay(optval !== 0);
82
+ if (level === IPPROTO_TCP && optname === TCP_NODELAY) {
83
+ this.socket.setNoDelay(optval !== 0);
84
+ return;
85
+ }
86
+ if (level === SOL_SOCKET && optname === SO_KEEPALIVE) {
87
+ this.socket.setKeepAlive(optval !== 0);
88
+ }
85
89
  }
86
90
  shutdown(how) {
87
91
  if (how === "write" || how === "both") {
@@ -146,7 +150,7 @@ class NodeHostListener {
146
150
  async close() {
147
151
  this.closed = true;
148
152
  // Reject pending accept waiters
149
- for (const waiter of this.waiters.splice(0)) {
153
+ for (const _waiter of this.waiters.splice(0)) {
150
154
  // Resolve with a destroyed socket to signal closure — caller handles
151
155
  // the error via the socket's error/close events
152
156
  }
@@ -8,6 +8,8 @@ export interface NodeExecutionDriverOptions extends RuntimeDriverOptions {
8
8
  bindings?: BindingTree;
9
9
  /** Callback to toggle PTY raw mode — wired by kernel runtime when PTY is attached. */
10
10
  onPtySetRawMode?: (mode: boolean) => void;
11
+ /** Optional live stdin source for PTY-backed interactive processes. */
12
+ liveStdinSource?: LiveStdinSource;
11
13
  /** Kernel socket table — routes net.connect through kernel instead of host TCP. */
12
14
  socketTable?: import("@secure-exec/core").SocketTable;
13
15
  /** Kernel process table — registers child processes for cross-runtime visibility. */
@@ -23,6 +25,9 @@ export interface BudgetState {
23
25
  activeTimers: number;
24
26
  childProcesses: number;
25
27
  }
28
+ export interface LiveStdinSource {
29
+ read(): Promise<Uint8Array | null>;
30
+ }
26
31
  /** Shared mutable state owned by NodeExecutionDriver, passed to extracted modules. */
27
32
  export interface DriverDeps {
28
33
  filesystem: VirtualFileSystem;
@@ -51,6 +56,7 @@ export interface DriverDeps {
51
56
  resolutionCache: ResolutionCache;
52
57
  /** Optional callback for PTY setRawMode — wired by kernel when PTY is attached. */
53
58
  onPtySetRawMode?: (mode: boolean) => void;
59
+ liveStdinSource?: LiveStdinSource;
54
60
  }
55
61
  export declare const DEFAULT_BRIDGE_BASE64_TRANSFER_BYTES: number;
56
62
  export declare const DEFAULT_ISOLATE_JSON_PAYLOAD_BYTES: number;
@@ -21,7 +21,9 @@ export interface NodeRuntimeOptions {
21
21
  moduleAccessPaths?: string[];
22
22
  /**
23
23
  * Bridge permissions for isolate processes. Defaults to allowAllChildProcess
24
- * (fs/network/env deny-by-default). Use allowAll for full sandbox access.
24
+ * plus read-only `/proc/self` metadata access in kernel-mounted mode
25
+ * (other fs/network/env access deny-by-default). Use allowAll for full
26
+ * sandbox access.
25
27
  */
26
28
  permissions?: Partial<Permissions>;
27
29
  /**