@secure-exec/core 0.1.0-rc.4 → 0.1.1-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.
- package/dist/bridge/child-process.js +8 -22
- package/dist/bridge/fs.js +25 -29
- package/dist/bridge/module.js +3 -12
- package/dist/bridge/network.d.ts +1 -0
- package/dist/bridge/network.js +47 -30
- package/dist/bridge/process.d.ts +3 -0
- package/dist/bridge/process.js +40 -22
- package/dist/bridge.js +113 -96
- package/dist/generated/isolate-runtime.d.ts +5 -5
- package/dist/generated/isolate-runtime.js +5 -5
- package/dist/index.d.ts +1 -1
- package/dist/isolate-runtime/apply-custom-global-policy.js +2 -3
- package/dist/isolate-runtime/bridge-initial-globals.js +143 -4
- package/dist/isolate-runtime/require-setup.js +3 -3
- package/dist/isolate-runtime/setup-dynamic-import.js +1 -5
- package/dist/isolate-runtime/setup-fs-facade.js +60 -21
- package/dist/module-resolver.js +49 -3
- package/dist/shared/api-types.d.ts +6 -0
- package/dist/shared/bridge-contract.d.ts +97 -80
- package/dist/shared/bridge-contract.js +4 -2
- package/dist/shared/console-formatter.js +4 -4
- package/dist/shared/global-exposure.js +5 -0
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
|
@@ -376,13 +376,8 @@ function execSync(command, options) {
|
|
|
376
376
|
}
|
|
377
377
|
// Default maxBuffer 1MB (Node.js convention)
|
|
378
378
|
const maxBuffer = opts.maxBuffer ?? 1024 * 1024;
|
|
379
|
-
// Use synchronous bridge call
|
|
380
|
-
const
|
|
381
|
-
"bash",
|
|
382
|
-
JSON.stringify(["-c", command]),
|
|
383
|
-
JSON.stringify({ cwd: opts.cwd, env: opts.env, maxBuffer }),
|
|
384
|
-
]);
|
|
385
|
-
const result = JSON.parse(jsonResult);
|
|
379
|
+
// Use synchronous bridge call
|
|
380
|
+
const result = _childProcessSpawnSync("bash", JSON.stringify(["-c", command]), JSON.stringify({ cwd: opts.cwd, env: opts.env, maxBuffer }));
|
|
386
381
|
if (result.maxBufferExceeded) {
|
|
387
382
|
const err = new Error("stdout maxBuffer length exceeded");
|
|
388
383
|
err.code = "ERR_CHILD_PROCESS_STDIO_MAXBUFFER";
|
|
@@ -423,11 +418,7 @@ function spawn(command, args, options) {
|
|
|
423
418
|
// This ensures process.chdir() changes are reflected in child processes
|
|
424
419
|
const effectiveCwd = opts.cwd ?? (typeof process !== "undefined" ? process.cwd() : "/");
|
|
425
420
|
// Streaming mode - spawn immediately
|
|
426
|
-
const sessionId = _childProcessSpawnStart.
|
|
427
|
-
command,
|
|
428
|
-
JSON.stringify(argsArray),
|
|
429
|
-
JSON.stringify({ cwd: effectiveCwd, env: opts.env }),
|
|
430
|
-
]);
|
|
421
|
+
const sessionId = _childProcessSpawnStart(command, JSON.stringify(argsArray), JSON.stringify({ cwd: effectiveCwd, env: opts.env }));
|
|
431
422
|
activeChildren.set(sessionId, child);
|
|
432
423
|
// Register handle to keep sandbox alive until child exits
|
|
433
424
|
// See: docs-internal/node/ACTIVE_HANDLES.md
|
|
@@ -439,12 +430,12 @@ function spawn(command, args, options) {
|
|
|
439
430
|
if (typeof _childProcessStdinWrite === "undefined")
|
|
440
431
|
return false;
|
|
441
432
|
const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
442
|
-
_childProcessStdinWrite
|
|
433
|
+
_childProcessStdinWrite(sessionId, bytes);
|
|
443
434
|
return true;
|
|
444
435
|
};
|
|
445
436
|
child.stdin.end = () => {
|
|
446
437
|
if (typeof _childProcessStdinClose !== "undefined") {
|
|
447
|
-
_childProcessStdinClose
|
|
438
|
+
_childProcessStdinClose(sessionId);
|
|
448
439
|
}
|
|
449
440
|
child.stdin.writable = false;
|
|
450
441
|
};
|
|
@@ -457,7 +448,7 @@ function spawn(command, args, options) {
|
|
|
457
448
|
: signal === "SIGINT" || signal === 2
|
|
458
449
|
? 2
|
|
459
450
|
: 15;
|
|
460
|
-
_childProcessKill
|
|
451
|
+
_childProcessKill(sessionId, sig);
|
|
461
452
|
child.killed = true;
|
|
462
453
|
child.signalCode = (typeof signal === "string" ? signal : "SIGTERM");
|
|
463
454
|
return true;
|
|
@@ -501,13 +492,8 @@ function spawnSync(command, args, options) {
|
|
|
501
492
|
const effectiveCwd = opts.cwd ?? (typeof process !== "undefined" ? process.cwd() : "/");
|
|
502
493
|
// Pass maxBuffer through to host for enforcement
|
|
503
494
|
const maxBuffer = opts.maxBuffer;
|
|
504
|
-
// Args passed as JSON
|
|
505
|
-
const
|
|
506
|
-
command,
|
|
507
|
-
JSON.stringify(argsArray),
|
|
508
|
-
JSON.stringify({ cwd: effectiveCwd, env: opts.env, maxBuffer }),
|
|
509
|
-
]);
|
|
510
|
-
const result = JSON.parse(jsonResult);
|
|
495
|
+
// Args and options passed as JSON strings for transferability
|
|
496
|
+
const result = _childProcessSpawnSync(command, JSON.stringify(argsArray), JSON.stringify({ cwd: effectiveCwd, env: opts.env, maxBuffer }));
|
|
511
497
|
const stdoutBuf = typeof Buffer !== "undefined" ? Buffer.from(result.stdout) : result.stdout;
|
|
512
498
|
const stderrBuf = typeof Buffer !== "undefined" ? Buffer.from(result.stderr) : result.stderr;
|
|
513
499
|
if (result.maxBufferExceeded) {
|
package/dist/bridge/fs.js
CHANGED
|
@@ -919,13 +919,13 @@ const fs = {
|
|
|
919
919
|
try {
|
|
920
920
|
if (encoding) {
|
|
921
921
|
// Text mode - use text read
|
|
922
|
-
const content = _fs.readFile
|
|
922
|
+
const content = _fs.readFile(pathStr);
|
|
923
923
|
return content;
|
|
924
924
|
}
|
|
925
925
|
else {
|
|
926
|
-
// Binary mode -
|
|
927
|
-
const
|
|
928
|
-
return Buffer.from(
|
|
926
|
+
// Binary mode - host returns raw Uint8Array via MessagePack bin
|
|
927
|
+
const binaryData = _fs.readFileBinary(pathStr);
|
|
928
|
+
return Buffer.from(binaryData);
|
|
929
929
|
}
|
|
930
930
|
}
|
|
931
931
|
catch (err) {
|
|
@@ -952,17 +952,16 @@ const fs = {
|
|
|
952
952
|
if (typeof data === "string") {
|
|
953
953
|
// Text mode - use text write
|
|
954
954
|
// Return the result so async callers (fs.promises) can await it.
|
|
955
|
-
return _fs.writeFile
|
|
955
|
+
return _fs.writeFile(pathStr, data);
|
|
956
956
|
}
|
|
957
957
|
else if (ArrayBuffer.isView(data)) {
|
|
958
|
-
// Binary mode -
|
|
958
|
+
// Binary mode - send raw Uint8Array via MessagePack bin
|
|
959
959
|
const uint8 = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
960
|
-
|
|
961
|
-
return _fs.writeFileBinary.applySyncPromise(undefined, [pathStr, base64]);
|
|
960
|
+
return _fs.writeFileBinary(pathStr, uint8);
|
|
962
961
|
}
|
|
963
962
|
else {
|
|
964
963
|
// Fallback to text mode
|
|
965
|
-
return _fs.writeFile
|
|
964
|
+
return _fs.writeFile(pathStr, String(data));
|
|
966
965
|
}
|
|
967
966
|
},
|
|
968
967
|
appendFileSync(path, data, options) {
|
|
@@ -975,9 +974,9 @@ const fs = {
|
|
|
975
974
|
readdirSync(path, options) {
|
|
976
975
|
const rawPath = toPathString(path);
|
|
977
976
|
const pathStr = rawPath;
|
|
978
|
-
let
|
|
977
|
+
let entries;
|
|
979
978
|
try {
|
|
980
|
-
|
|
979
|
+
entries = _fs.readDir(pathStr);
|
|
981
980
|
}
|
|
982
981
|
catch (err) {
|
|
983
982
|
// Convert "entry not found" and similar errors to proper ENOENT
|
|
@@ -987,7 +986,6 @@ const fs = {
|
|
|
987
986
|
}
|
|
988
987
|
throw err;
|
|
989
988
|
}
|
|
990
|
-
const entries = JSON.parse(entriesJson);
|
|
991
989
|
if (options?.withFileTypes) {
|
|
992
990
|
return entries.map((e) => new Dirent(e.name, e.isDirectory, rawPath));
|
|
993
991
|
}
|
|
@@ -997,12 +995,12 @@ const fs = {
|
|
|
997
995
|
const rawPath = toPathString(path);
|
|
998
996
|
const pathStr = rawPath;
|
|
999
997
|
const recursive = typeof options === "object" ? options?.recursive ?? false : false;
|
|
1000
|
-
_fs.mkdir
|
|
998
|
+
_fs.mkdir(pathStr, recursive);
|
|
1001
999
|
return recursive ? rawPath : undefined;
|
|
1002
1000
|
},
|
|
1003
1001
|
rmdirSync(path, _options) {
|
|
1004
1002
|
const pathStr = toPathString(path);
|
|
1005
|
-
_fs.rmdir
|
|
1003
|
+
_fs.rmdir(pathStr);
|
|
1006
1004
|
},
|
|
1007
1005
|
rmSync(path, options) {
|
|
1008
1006
|
const pathStr = toPathString(path);
|
|
@@ -1042,14 +1040,14 @@ const fs = {
|
|
|
1042
1040
|
},
|
|
1043
1041
|
existsSync(path) {
|
|
1044
1042
|
const pathStr = toPathString(path);
|
|
1045
|
-
return _fs.exists
|
|
1043
|
+
return _fs.exists(pathStr);
|
|
1046
1044
|
},
|
|
1047
1045
|
statSync(path, _options) {
|
|
1048
1046
|
const rawPath = toPathString(path);
|
|
1049
1047
|
const pathStr = rawPath;
|
|
1050
|
-
let
|
|
1048
|
+
let stat;
|
|
1051
1049
|
try {
|
|
1052
|
-
|
|
1050
|
+
stat = _fs.stat(pathStr);
|
|
1053
1051
|
}
|
|
1054
1052
|
catch (err) {
|
|
1055
1053
|
// Convert various "not found" errors to proper ENOENT
|
|
@@ -1062,23 +1060,21 @@ const fs = {
|
|
|
1062
1060
|
}
|
|
1063
1061
|
throw err;
|
|
1064
1062
|
}
|
|
1065
|
-
const stat = JSON.parse(statJson);
|
|
1066
1063
|
return new Stats(stat);
|
|
1067
1064
|
},
|
|
1068
1065
|
lstatSync(path, _options) {
|
|
1069
1066
|
const pathStr = toPathString(path);
|
|
1070
|
-
const
|
|
1071
|
-
const stat = JSON.parse(statJson);
|
|
1067
|
+
const stat = bridgeCall(() => _fs.lstat(pathStr), "lstat", pathStr);
|
|
1072
1068
|
return new Stats(stat);
|
|
1073
1069
|
},
|
|
1074
1070
|
unlinkSync(path) {
|
|
1075
1071
|
const pathStr = toPathString(path);
|
|
1076
|
-
_fs.unlink
|
|
1072
|
+
_fs.unlink(pathStr);
|
|
1077
1073
|
},
|
|
1078
1074
|
renameSync(oldPath, newPath) {
|
|
1079
1075
|
const oldPathStr = toPathString(oldPath);
|
|
1080
1076
|
const newPathStr = toPathString(newPath);
|
|
1081
|
-
_fs.rename
|
|
1077
|
+
_fs.rename(oldPathStr, newPathStr);
|
|
1082
1078
|
},
|
|
1083
1079
|
copyFileSync(src, dest, _mode) {
|
|
1084
1080
|
// readFileSync and writeFileSync already normalize paths
|
|
@@ -1340,35 +1336,35 @@ const fs = {
|
|
|
1340
1336
|
chmodSync(path, mode) {
|
|
1341
1337
|
const pathStr = toPathString(path);
|
|
1342
1338
|
const modeNum = typeof mode === "string" ? parseInt(mode, 8) : mode;
|
|
1343
|
-
bridgeCall(() => _fs.chmod
|
|
1339
|
+
bridgeCall(() => _fs.chmod(pathStr, modeNum), "chmod", pathStr);
|
|
1344
1340
|
},
|
|
1345
1341
|
chownSync(path, uid, gid) {
|
|
1346
1342
|
const pathStr = toPathString(path);
|
|
1347
|
-
bridgeCall(() => _fs.chown
|
|
1343
|
+
bridgeCall(() => _fs.chown(pathStr, uid, gid), "chown", pathStr);
|
|
1348
1344
|
},
|
|
1349
1345
|
linkSync(existingPath, newPath) {
|
|
1350
1346
|
const existingStr = toPathString(existingPath);
|
|
1351
1347
|
const newStr = toPathString(newPath);
|
|
1352
|
-
bridgeCall(() => _fs.link
|
|
1348
|
+
bridgeCall(() => _fs.link(existingStr, newStr), "link", newStr);
|
|
1353
1349
|
},
|
|
1354
1350
|
symlinkSync(target, path, _type) {
|
|
1355
1351
|
const targetStr = toPathString(target);
|
|
1356
1352
|
const pathStr = toPathString(path);
|
|
1357
|
-
bridgeCall(() => _fs.symlink
|
|
1353
|
+
bridgeCall(() => _fs.symlink(targetStr, pathStr), "symlink", pathStr);
|
|
1358
1354
|
},
|
|
1359
1355
|
readlinkSync(path, _options) {
|
|
1360
1356
|
const pathStr = toPathString(path);
|
|
1361
|
-
return bridgeCall(() => _fs.readlink
|
|
1357
|
+
return bridgeCall(() => _fs.readlink(pathStr), "readlink", pathStr);
|
|
1362
1358
|
},
|
|
1363
1359
|
truncateSync(path, len) {
|
|
1364
1360
|
const pathStr = toPathString(path);
|
|
1365
|
-
bridgeCall(() => _fs.truncate
|
|
1361
|
+
bridgeCall(() => _fs.truncate(pathStr, len ?? 0), "truncate", pathStr);
|
|
1366
1362
|
},
|
|
1367
1363
|
utimesSync(path, atime, mtime) {
|
|
1368
1364
|
const pathStr = toPathString(path);
|
|
1369
1365
|
const atimeNum = typeof atime === "number" ? atime : new Date(atime).getTime() / 1000;
|
|
1370
1366
|
const mtimeNum = typeof mtime === "number" ? mtime : new Date(mtime).getTime() / 1000;
|
|
1371
|
-
bridgeCall(() => _fs.utimes
|
|
1367
|
+
bridgeCall(() => _fs.utimes(pathStr, atimeNum, mtimeNum), "utimes", pathStr);
|
|
1372
1368
|
},
|
|
1373
1369
|
// Async methods - wrap sync methods in callbacks/promises
|
|
1374
1370
|
//
|
package/dist/bridge/module.js
CHANGED
|
@@ -78,10 +78,7 @@ export function createRequire(filename) {
|
|
|
78
78
|
};
|
|
79
79
|
// Create resolve function
|
|
80
80
|
const resolve = function (request, _options) {
|
|
81
|
-
const resolved = _resolveModule
|
|
82
|
-
request,
|
|
83
|
-
dirname,
|
|
84
|
-
]);
|
|
81
|
+
const resolved = _resolveModule(request, dirname);
|
|
85
82
|
if (resolved === null) {
|
|
86
83
|
const err = new Error("Cannot find module '" + request + "'");
|
|
87
84
|
err.code = "MODULE_NOT_FOUND";
|
|
@@ -155,10 +152,7 @@ export class Module {
|
|
|
155
152
|
const wrapper = new Function("exports", "require", "module", "__filename", "__dirname", content);
|
|
156
153
|
const moduleRequire = (request) => _requireFrom(request, this.path);
|
|
157
154
|
moduleRequire.resolve = (request) => {
|
|
158
|
-
const resolved = _resolveModule
|
|
159
|
-
request,
|
|
160
|
-
this.path,
|
|
161
|
-
]);
|
|
155
|
+
const resolved = _resolveModule(request, this.path);
|
|
162
156
|
if (resolved === null) {
|
|
163
157
|
const err = new Error("Cannot find module '" + request + "'");
|
|
164
158
|
err.code = "MODULE_NOT_FOUND";
|
|
@@ -190,10 +184,7 @@ export class Module {
|
|
|
190
184
|
: {};
|
|
191
185
|
static _resolveFilename(request, parent, _isMain, _options) {
|
|
192
186
|
const parentDir = parent && parent.path ? parent.path : "/";
|
|
193
|
-
const resolved = _resolveModule
|
|
194
|
-
request,
|
|
195
|
-
parentDir,
|
|
196
|
-
]);
|
|
187
|
+
const resolved = _resolveModule(request, parentDir);
|
|
197
188
|
if (resolved === null) {
|
|
198
189
|
const err = new Error("Cannot find module '" + request + "'");
|
|
199
190
|
err.code = "MODULE_NOT_FOUND";
|
package/dist/bridge/network.d.ts
CHANGED
package/dist/bridge/network.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Network module polyfill for isolated-vm
|
|
2
2
|
// Provides fetch, http, https, and dns module emulation that bridges to host
|
|
3
|
+
// Cap in-sandbox request/response buffering to prevent host memory exhaustion
|
|
4
|
+
const MAX_HTTP_BODY_BYTES = 50 * 1024 * 1024; // 50 MB
|
|
3
5
|
import { exposeCustomGlobal } from "../shared/global-exposure.js";
|
|
4
6
|
// Fetch polyfill
|
|
5
7
|
export async function fetch(url, options = {}) {
|
|
@@ -12,10 +14,7 @@ export async function fetch(url, options = {}) {
|
|
|
12
14
|
headers: options.headers || {},
|
|
13
15
|
body: options.body || null,
|
|
14
16
|
});
|
|
15
|
-
const
|
|
16
|
-
result: { promise: true },
|
|
17
|
-
});
|
|
18
|
-
const response = JSON.parse(responseJson);
|
|
17
|
+
const response = await _networkFetchRaw(String(url), optionsJson);
|
|
19
18
|
// Create Response-like object
|
|
20
19
|
return {
|
|
21
20
|
ok: response.ok,
|
|
@@ -162,10 +161,8 @@ export const dns = {
|
|
|
162
161
|
if (typeof options === "function") {
|
|
163
162
|
cb = options;
|
|
164
163
|
}
|
|
165
|
-
_networkDnsLookupRaw
|
|
166
|
-
.
|
|
167
|
-
.then((resultJson) => {
|
|
168
|
-
const result = JSON.parse(resultJson);
|
|
164
|
+
_networkDnsLookupRaw(hostname)
|
|
165
|
+
.then((result) => {
|
|
169
166
|
if (result.error) {
|
|
170
167
|
const err = new Error(result.error);
|
|
171
168
|
err.code = result.code || "ENOTFOUND";
|
|
@@ -539,6 +536,7 @@ export class ClientRequest {
|
|
|
539
536
|
_callback;
|
|
540
537
|
_listeners = {};
|
|
541
538
|
_body = "";
|
|
539
|
+
_bodyBytes = 0;
|
|
542
540
|
_ended = false;
|
|
543
541
|
_agent;
|
|
544
542
|
_hostKey;
|
|
@@ -580,15 +578,17 @@ export class ClientRequest {
|
|
|
580
578
|
throw new Error('http/https request requires NetworkAdapter to be configured');
|
|
581
579
|
}
|
|
582
580
|
const url = this._buildUrl();
|
|
581
|
+
const tls = {};
|
|
582
|
+
if (this._options.rejectUnauthorized !== undefined) {
|
|
583
|
+
tls.rejectUnauthorized = this._options.rejectUnauthorized;
|
|
584
|
+
}
|
|
583
585
|
const optionsJson = JSON.stringify({
|
|
584
586
|
method: this._options.method || "GET",
|
|
585
587
|
headers: this._options.headers || {},
|
|
586
588
|
body: this._body || null,
|
|
589
|
+
...tls,
|
|
587
590
|
});
|
|
588
|
-
const
|
|
589
|
-
result: { promise: true },
|
|
590
|
-
});
|
|
591
|
-
const response = JSON.parse(responseJson);
|
|
591
|
+
const response = await _networkHttpRequestRaw(url, optionsJson);
|
|
592
592
|
this.finished = true;
|
|
593
593
|
// 101 Switching Protocols → fire 'upgrade' event
|
|
594
594
|
if (response.status === 101) {
|
|
@@ -648,12 +648,17 @@ export class ClientRequest {
|
|
|
648
648
|
}
|
|
649
649
|
}
|
|
650
650
|
write(data) {
|
|
651
|
+
const addedBytes = typeof Buffer !== "undefined" ? Buffer.byteLength(data) : data.length;
|
|
652
|
+
if (this._bodyBytes + addedBytes > MAX_HTTP_BODY_BYTES) {
|
|
653
|
+
throw new Error("ERR_HTTP_BODY_TOO_LARGE: request body exceeds " + MAX_HTTP_BODY_BYTES + " byte limit");
|
|
654
|
+
}
|
|
651
655
|
this._body += data;
|
|
656
|
+
this._bodyBytes += addedBytes;
|
|
652
657
|
return true;
|
|
653
658
|
}
|
|
654
659
|
end(data) {
|
|
655
660
|
if (data)
|
|
656
|
-
this.
|
|
661
|
+
this.write(data);
|
|
657
662
|
this._ended = true;
|
|
658
663
|
return this;
|
|
659
664
|
}
|
|
@@ -919,6 +924,7 @@ class ServerResponseBridge {
|
|
|
919
924
|
writableFinished = false;
|
|
920
925
|
_headers = new Map();
|
|
921
926
|
_chunks = [];
|
|
927
|
+
_chunksBytes = 0;
|
|
922
928
|
_listeners = {};
|
|
923
929
|
_closedPromise;
|
|
924
930
|
_resolveClosed = null;
|
|
@@ -993,12 +999,12 @@ class ServerResponseBridge {
|
|
|
993
999
|
if (chunk == null)
|
|
994
1000
|
return true;
|
|
995
1001
|
this.headersSent = true;
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
else {
|
|
1000
|
-
this._chunks.push(chunk);
|
|
1002
|
+
const buf = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
1003
|
+
if (this._chunksBytes + buf.byteLength > MAX_HTTP_BODY_BYTES) {
|
|
1004
|
+
throw new Error("ERR_HTTP_BODY_TOO_LARGE: response body exceeds " + MAX_HTTP_BODY_BYTES + " byte limit");
|
|
1001
1005
|
}
|
|
1006
|
+
this._chunks.push(buf);
|
|
1007
|
+
this._chunksBytes += buf.byteLength;
|
|
1002
1008
|
return true;
|
|
1003
1009
|
}
|
|
1004
1010
|
end(chunk) {
|
|
@@ -1106,8 +1112,7 @@ class Server {
|
|
|
1106
1112
|
if (typeof _networkHttpServerListenRaw === "undefined") {
|
|
1107
1113
|
throw new Error("http.createServer requires NetworkAdapter.httpServerListen support");
|
|
1108
1114
|
}
|
|
1109
|
-
const
|
|
1110
|
-
const result = JSON.parse(resultJson);
|
|
1115
|
+
const result = await _networkHttpServerListenRaw(JSON.stringify({ serverId: this._serverId, port, hostname }));
|
|
1111
1116
|
this._address = result.address;
|
|
1112
1117
|
this.listening = true;
|
|
1113
1118
|
this._handleId = `http-server:${this._serverId}`;
|
|
@@ -1144,9 +1149,7 @@ class Server {
|
|
|
1144
1149
|
await this._listenPromise;
|
|
1145
1150
|
}
|
|
1146
1151
|
if (this.listening && typeof _networkHttpServerCloseRaw !== "undefined") {
|
|
1147
|
-
await _networkHttpServerCloseRaw
|
|
1148
|
-
result: { promise: true },
|
|
1149
|
-
});
|
|
1152
|
+
await _networkHttpServerCloseRaw(this._serverId);
|
|
1150
1153
|
}
|
|
1151
1154
|
this.listening = false;
|
|
1152
1155
|
this._address = null;
|
|
@@ -1222,12 +1225,11 @@ class Server {
|
|
|
1222
1225
|
}
|
|
1223
1226
|
}
|
|
1224
1227
|
/** Route an incoming HTTP request to the server's request listener and return the serialized response. */
|
|
1225
|
-
async function dispatchServerRequest(serverId,
|
|
1228
|
+
async function dispatchServerRequest(serverId, request) {
|
|
1226
1229
|
const listener = serverRequestListeners.get(serverId);
|
|
1227
1230
|
if (!listener) {
|
|
1228
1231
|
throw new Error(`Unknown HTTP server: ${serverId}`);
|
|
1229
1232
|
}
|
|
1230
|
-
const request = JSON.parse(requestJson);
|
|
1231
1233
|
const incoming = new ServerIncomingMessage(request);
|
|
1232
1234
|
const outgoing = new ServerResponseBridge();
|
|
1233
1235
|
try {
|
|
@@ -1242,13 +1244,20 @@ async function dispatchServerRequest(serverId, requestJson) {
|
|
|
1242
1244
|
}
|
|
1243
1245
|
catch (err) {
|
|
1244
1246
|
outgoing.statusCode = 500;
|
|
1245
|
-
|
|
1247
|
+
try {
|
|
1248
|
+
outgoing.end(err instanceof Error ? `Error: ${err.message}` : "Error");
|
|
1249
|
+
}
|
|
1250
|
+
catch {
|
|
1251
|
+
// Body cap may prevent writing error — finalize without data
|
|
1252
|
+
if (!outgoing.writableFinished)
|
|
1253
|
+
outgoing.end();
|
|
1254
|
+
}
|
|
1246
1255
|
}
|
|
1247
1256
|
if (!outgoing.writableFinished) {
|
|
1248
1257
|
outgoing.end();
|
|
1249
1258
|
}
|
|
1250
1259
|
await outgoing.waitForClose();
|
|
1251
|
-
return
|
|
1260
|
+
return outgoing.serialize();
|
|
1252
1261
|
}
|
|
1253
1262
|
// Function-based ServerResponse constructor — allows .call() inheritance
|
|
1254
1263
|
// used by light-my-request (Fastify's inject), which does
|
|
@@ -1262,6 +1271,7 @@ function ServerResponseCallable() {
|
|
|
1262
1271
|
this.writableFinished = false;
|
|
1263
1272
|
this._headers = new Map();
|
|
1264
1273
|
this._chunks = [];
|
|
1274
|
+
this._chunksBytes = 0;
|
|
1265
1275
|
this._listeners = {};
|
|
1266
1276
|
this._closedPromise = new Promise((resolve) => {
|
|
1267
1277
|
this._resolveClosed = resolve;
|
|
@@ -1287,10 +1297,17 @@ ServerResponseCallable.prototype = Object.create(ServerResponseBridge.prototype,
|
|
|
1287
1297
|
constructor: { value: ServerResponseCallable, writable: true, configurable: true },
|
|
1288
1298
|
});
|
|
1289
1299
|
// Create HTTP module
|
|
1290
|
-
function createHttpModule(
|
|
1300
|
+
function createHttpModule(protocol) {
|
|
1301
|
+
const defaultProtocol = protocol === "https" ? "https:" : "http:";
|
|
1291
1302
|
const moduleAgent = new Agent({ keepAlive: false });
|
|
1292
1303
|
// Set module-level globalAgent so ClientRequest defaults to it
|
|
1293
1304
|
_moduleGlobalAgent = moduleAgent;
|
|
1305
|
+
// Ensure protocol is set on request options (defaults to module protocol)
|
|
1306
|
+
function ensureProtocol(opts) {
|
|
1307
|
+
if (!opts.protocol)
|
|
1308
|
+
return { ...opts, protocol: defaultProtocol };
|
|
1309
|
+
return opts;
|
|
1310
|
+
}
|
|
1294
1311
|
return {
|
|
1295
1312
|
request(options, callback) {
|
|
1296
1313
|
let opts;
|
|
@@ -1314,7 +1331,7 @@ function createHttpModule(_protocol) {
|
|
|
1314
1331
|
else {
|
|
1315
1332
|
opts = options;
|
|
1316
1333
|
}
|
|
1317
|
-
return new ClientRequest(opts, callback);
|
|
1334
|
+
return new ClientRequest(ensureProtocol(opts), callback);
|
|
1318
1335
|
},
|
|
1319
1336
|
get(options, callback) {
|
|
1320
1337
|
let opts;
|
|
@@ -1340,7 +1357,7 @@ function createHttpModule(_protocol) {
|
|
|
1340
1357
|
else {
|
|
1341
1358
|
opts = { ...options, method: "GET" };
|
|
1342
1359
|
}
|
|
1343
|
-
const req = new ClientRequest(opts, callback);
|
|
1360
|
+
const req = new ClientRequest(ensureProtocol(opts), callback);
|
|
1344
1361
|
req.end();
|
|
1345
1362
|
return req;
|
|
1346
1363
|
},
|
package/dist/bridge/process.d.ts
CHANGED
|
@@ -19,6 +19,9 @@ export interface ProcessConfig {
|
|
|
19
19
|
stdin?: string;
|
|
20
20
|
timingMitigation?: "off" | "freeze";
|
|
21
21
|
frozenTimeMs?: number;
|
|
22
|
+
stdinIsTTY?: boolean;
|
|
23
|
+
stdoutIsTTY?: boolean;
|
|
24
|
+
stderrIsTTY?: boolean;
|
|
22
25
|
}
|
|
23
26
|
/**
|
|
24
27
|
* Thrown by `process.exit()` to unwind the sandbox call stack. The host
|
package/dist/bridge/process.js
CHANGED
|
@@ -40,8 +40,8 @@ function getNowMs() {
|
|
|
40
40
|
? performance.now()
|
|
41
41
|
: Date.now();
|
|
42
42
|
}
|
|
43
|
-
// Start time for uptime calculation
|
|
44
|
-
|
|
43
|
+
// Start time for uptime calculation (mutable for snapshot restore)
|
|
44
|
+
let _processStartTime = getNowMs();
|
|
45
45
|
const BUFFER_MAX_LENGTH = typeof BufferPolyfill.kMaxLength ===
|
|
46
46
|
"number"
|
|
47
47
|
? BufferPolyfill.kMaxLength
|
|
@@ -71,6 +71,18 @@ if (typeof bufferPolyfillMutable.constants !== "object" ||
|
|
|
71
71
|
// Exit code tracking
|
|
72
72
|
let _exitCode = 0;
|
|
73
73
|
let _exited = false;
|
|
74
|
+
// Expose reset function for snapshot restore — resets mutable state
|
|
75
|
+
// captured in this closure so each restored context starts fresh.
|
|
76
|
+
globalThis.__runtimeResetProcessState =
|
|
77
|
+
function () {
|
|
78
|
+
_processStartTime =
|
|
79
|
+
typeof performance !== "undefined" && performance.now
|
|
80
|
+
? performance.now()
|
|
81
|
+
: Date.now();
|
|
82
|
+
_exitCode = 0;
|
|
83
|
+
_exited = false;
|
|
84
|
+
delete globalThis.__runtimeResetProcessState;
|
|
85
|
+
};
|
|
74
86
|
/**
|
|
75
87
|
* Thrown by `process.exit()` to unwind the sandbox call stack. The host
|
|
76
88
|
* catches this to extract the exit code without killing the isolate.
|
|
@@ -122,7 +134,7 @@ function _addListener(event, listener, once = false) {
|
|
|
122
134
|
const warning = `MaxListenersExceededWarning: Possible EventEmitter memory leak detected. ${total} ${event} listeners added to [process]. MaxListeners is ${_processMaxListeners}. Use emitter.setMaxListeners() to increase limit`;
|
|
123
135
|
// Use console.error to emit warning without recursion risk
|
|
124
136
|
if (typeof _error !== "undefined") {
|
|
125
|
-
_error
|
|
137
|
+
_error(warning);
|
|
126
138
|
}
|
|
127
139
|
}
|
|
128
140
|
}
|
|
@@ -161,11 +173,15 @@ function _emit(event, ...args) {
|
|
|
161
173
|
}
|
|
162
174
|
return handled;
|
|
163
175
|
}
|
|
176
|
+
// Resolve isTTY flags from config
|
|
177
|
+
const _stdinIsTTY = (typeof _processConfig !== "undefined" && _processConfig.stdinIsTTY) || false;
|
|
178
|
+
const _stdoutIsTTY = (typeof _processConfig !== "undefined" && _processConfig.stdoutIsTTY) || false;
|
|
179
|
+
const _stderrIsTTY = (typeof _processConfig !== "undefined" && _processConfig.stderrIsTTY) || false;
|
|
164
180
|
// Stdout stream
|
|
165
181
|
const _stdout = {
|
|
166
182
|
write(data) {
|
|
167
183
|
if (typeof _log !== "undefined") {
|
|
168
|
-
_log
|
|
184
|
+
_log(String(data).replace(/\n$/, ""));
|
|
169
185
|
}
|
|
170
186
|
return true;
|
|
171
187
|
},
|
|
@@ -182,7 +198,7 @@ const _stdout = {
|
|
|
182
198
|
return false;
|
|
183
199
|
},
|
|
184
200
|
writable: true,
|
|
185
|
-
isTTY:
|
|
201
|
+
isTTY: _stdoutIsTTY,
|
|
186
202
|
columns: 80,
|
|
187
203
|
rows: 24,
|
|
188
204
|
};
|
|
@@ -190,7 +206,7 @@ const _stdout = {
|
|
|
190
206
|
const _stderr = {
|
|
191
207
|
write(data) {
|
|
192
208
|
if (typeof _error !== "undefined") {
|
|
193
|
-
_error
|
|
209
|
+
_error(String(data).replace(/\n$/, ""));
|
|
194
210
|
}
|
|
195
211
|
return true;
|
|
196
212
|
},
|
|
@@ -207,7 +223,7 @@ const _stderr = {
|
|
|
207
223
|
return false;
|
|
208
224
|
},
|
|
209
225
|
writable: true,
|
|
210
|
-
isTTY:
|
|
226
|
+
isTTY: _stderrIsTTY,
|
|
211
227
|
columns: 80,
|
|
212
228
|
rows: 24,
|
|
213
229
|
};
|
|
@@ -319,7 +335,16 @@ const _stdin = {
|
|
|
319
335
|
this.encoding = enc;
|
|
320
336
|
return this;
|
|
321
337
|
},
|
|
322
|
-
|
|
338
|
+
setRawMode(mode) {
|
|
339
|
+
if (!_stdinIsTTY) {
|
|
340
|
+
throw new Error("setRawMode is not supported when stdin is not a TTY");
|
|
341
|
+
}
|
|
342
|
+
if (typeof _ptySetRawMode !== "undefined") {
|
|
343
|
+
_ptySetRawMode(mode);
|
|
344
|
+
}
|
|
345
|
+
return this;
|
|
346
|
+
},
|
|
347
|
+
isTTY: _stdinIsTTY,
|
|
323
348
|
// For readline compatibility
|
|
324
349
|
[Symbol.asyncIterator]: async function* () {
|
|
325
350
|
const lines = getStdinData().split("\n");
|
|
@@ -419,9 +444,9 @@ const process = {
|
|
|
419
444
|
},
|
|
420
445
|
chdir(dir) {
|
|
421
446
|
// Validate directory exists in VFS before setting cwd
|
|
422
|
-
let
|
|
447
|
+
let stat;
|
|
423
448
|
try {
|
|
424
|
-
|
|
449
|
+
stat = _fs.stat(dir);
|
|
425
450
|
}
|
|
426
451
|
catch {
|
|
427
452
|
const err = new Error(`ENOENT: no such file or directory, chdir '${dir}'`);
|
|
@@ -431,8 +456,7 @@ const process = {
|
|
|
431
456
|
err.path = dir;
|
|
432
457
|
throw err;
|
|
433
458
|
}
|
|
434
|
-
|
|
435
|
-
if (!parsed.isDirectory) {
|
|
459
|
+
if (!stat.isDirectory) {
|
|
436
460
|
const err = new Error(`ENOTDIR: not a directory, chdir '${dir}'`);
|
|
437
461
|
err.code = "ENOTDIR";
|
|
438
462
|
err.errno = -20;
|
|
@@ -746,11 +770,7 @@ export function setTimeout(callback, delay, ...args) {
|
|
|
746
770
|
const actualDelay = delay ?? 0;
|
|
747
771
|
// Use host timer for actual delays if available and delay > 0
|
|
748
772
|
if (typeof _scheduleTimer !== "undefined" && actualDelay > 0) {
|
|
749
|
-
|
|
750
|
-
// Using { result: { promise: true } } tells isolated-vm to wait for the
|
|
751
|
-
// host Promise to resolve before resolving the apply() Promise
|
|
752
|
-
_scheduleTimer
|
|
753
|
-
.apply(undefined, [actualDelay], { result: { promise: true } })
|
|
773
|
+
_scheduleTimer(actualDelay)
|
|
754
774
|
.then(() => {
|
|
755
775
|
if (_timers.has(id)) {
|
|
756
776
|
_timers.delete(id);
|
|
@@ -798,8 +818,7 @@ export function setInterval(callback, delay, ...args) {
|
|
|
798
818
|
return; // Interval was cleared
|
|
799
819
|
if (typeof _scheduleTimer !== "undefined" && actualDelay > 0) {
|
|
800
820
|
// Use host timer for actual delays
|
|
801
|
-
_scheduleTimer
|
|
802
|
-
.apply(undefined, [actualDelay], { result: { promise: true } })
|
|
821
|
+
_scheduleTimer(actualDelay)
|
|
803
822
|
.then(() => {
|
|
804
823
|
if (_intervals.has(id)) {
|
|
805
824
|
try {
|
|
@@ -871,8 +890,7 @@ export const cryptoPolyfill = {
|
|
|
871
890
|
}
|
|
872
891
|
const bytes = new Uint8Array(array.buffer, array.byteOffset, array.byteLength);
|
|
873
892
|
try {
|
|
874
|
-
const
|
|
875
|
-
const hostBytes = BufferPolyfill.from(base64, "base64");
|
|
893
|
+
const hostBytes = _cryptoRandomFill(bytes.byteLength);
|
|
876
894
|
if (hostBytes.byteLength !== bytes.byteLength) {
|
|
877
895
|
throw new Error("invalid host entropy size");
|
|
878
896
|
}
|
|
@@ -888,7 +906,7 @@ export const cryptoPolyfill = {
|
|
|
888
906
|
throwUnsupportedCryptoApi("randomUUID");
|
|
889
907
|
}
|
|
890
908
|
try {
|
|
891
|
-
const uuid = _cryptoRandomUUID
|
|
909
|
+
const uuid = _cryptoRandomUUID();
|
|
892
910
|
if (typeof uuid !== "string") {
|
|
893
911
|
throw new Error("invalid host uuid");
|
|
894
912
|
}
|