@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.
- package/dist/bridge/child-process.js +47 -1
- package/dist/bridge/fs.d.ts +6 -1
- package/dist/bridge/fs.js +12 -3
- package/dist/bridge/index.d.ts +2 -2
- package/dist/bridge/index.js +2 -2
- package/dist/bridge/network.d.ts +35 -1
- package/dist/bridge/network.js +471 -153
- package/dist/bridge/os.js +9 -3
- package/dist/bridge/polyfills.d.ts +6 -9
- package/dist/bridge/polyfills.js +616 -14
- package/dist/bridge/process.d.ts +3 -2
- package/dist/bridge/process.js +288 -61
- package/dist/bridge-contract.d.ts +21 -3
- package/dist/bridge-contract.js +2 -0
- package/dist/bridge-handlers.d.ts +10 -0
- package/dist/bridge-handlers.js +515 -179
- package/dist/bridge.js +1531 -855
- package/dist/builtin-modules.js +6 -0
- package/dist/esm-compiler.d.ts +1 -0
- package/dist/esm-compiler.js +29 -11
- package/dist/execution-driver.js +362 -10
- package/dist/host-network-adapter.js +10 -6
- package/dist/isolate-bootstrap.d.ts +6 -0
- package/dist/kernel-runtime.d.ts +3 -1
- package/dist/kernel-runtime.js +109 -11
- package/dist/module-access.d.ts +3 -0
- package/dist/module-access.js +52 -2
- package/dist/module-resolver.js +3 -3
- package/dist/module-source.d.ts +5 -0
- package/dist/module-source.js +224 -0
- package/dist/polyfills.js +27 -1
- package/package.json +5 -3
package/dist/builtin-modules.js
CHANGED
|
@@ -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",
|
package/dist/esm-compiler.d.ts
CHANGED
|
@@ -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). */
|
package/dist/esm-compiler.js
CHANGED
|
@@ -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(
|
|
46
|
-
"fs/promises": buildWrapperSource("
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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;
|
package/dist/execution-driver.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
:
|
|
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 (
|
|
918
|
-
|
|
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
|
-
|
|
84
|
-
|
|
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
|
|
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;
|
package/dist/kernel-runtime.d.ts
CHANGED
|
@@ -21,7 +21,9 @@ export interface NodeRuntimeOptions {
|
|
|
21
21
|
moduleAccessPaths?: string[];
|
|
22
22
|
/**
|
|
23
23
|
* Bridge permissions for isolate processes. Defaults to allowAllChildProcess
|
|
24
|
-
*
|
|
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
|
/**
|