@secure-exec/core 0.1.1-rc.2 → 0.1.1-rc.3
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 +22 -8
- package/dist/bridge/fs.js +29 -25
- package/dist/bridge/module.js +12 -3
- package/dist/bridge/network.d.ts +101 -1
- package/dist/bridge/network.js +629 -12
- package/dist/bridge/process.js +46 -25
- package/dist/bridge.js +795 -65
- 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-timing-mitigation-freeze.js +8 -6
- package/dist/isolate-runtime/bridge-initial-globals.js +4 -143
- package/dist/isolate-runtime/require-setup.js +960 -5
- package/dist/isolate-runtime/setup-dynamic-import.js +5 -1
- package/dist/isolate-runtime/setup-fs-facade.js +21 -60
- package/dist/shared/bridge-contract.d.ts +220 -94
- package/dist/shared/bridge-contract.js +31 -3
- package/dist/shared/console-formatter.js +4 -4
- package/dist/shared/global-exposure.js +95 -0
- package/dist/shared/permissions.js +16 -0
- package/dist/types.d.ts +42 -1
- package/package.json +1 -1
package/dist/bridge/network.js
CHANGED
|
@@ -4,24 +4,41 @@
|
|
|
4
4
|
const MAX_HTTP_BODY_BYTES = 50 * 1024 * 1024; // 50 MB
|
|
5
5
|
import { exposeCustomGlobal } from "../shared/global-exposure.js";
|
|
6
6
|
// Fetch polyfill
|
|
7
|
-
export async function fetch(
|
|
7
|
+
export async function fetch(input, options = {}) {
|
|
8
8
|
if (typeof _networkFetchRaw === 'undefined') {
|
|
9
9
|
console.error('fetch requires NetworkAdapter to be configured');
|
|
10
10
|
throw new Error('fetch requires NetworkAdapter to be configured');
|
|
11
11
|
}
|
|
12
|
+
// Extract URL and options from Request object (used by axios fetch adapter)
|
|
13
|
+
let resolvedUrl;
|
|
14
|
+
if (input instanceof Request) {
|
|
15
|
+
resolvedUrl = input.url;
|
|
16
|
+
options = {
|
|
17
|
+
method: input.method,
|
|
18
|
+
headers: Object.fromEntries(input.headers.entries()),
|
|
19
|
+
body: input.body,
|
|
20
|
+
...options,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
resolvedUrl = String(input);
|
|
25
|
+
}
|
|
12
26
|
const optionsJson = JSON.stringify({
|
|
13
27
|
method: options.method || "GET",
|
|
14
28
|
headers: options.headers || {},
|
|
15
29
|
body: options.body || null,
|
|
16
30
|
});
|
|
17
|
-
const
|
|
31
|
+
const responseJson = await _networkFetchRaw.apply(undefined, [resolvedUrl, optionsJson], {
|
|
32
|
+
result: { promise: true },
|
|
33
|
+
});
|
|
34
|
+
const response = JSON.parse(responseJson);
|
|
18
35
|
// Create Response-like object
|
|
19
36
|
return {
|
|
20
37
|
ok: response.ok,
|
|
21
38
|
status: response.status,
|
|
22
39
|
statusText: response.statusText,
|
|
23
40
|
headers: new Map(Object.entries(response.headers || {})),
|
|
24
|
-
url: response.url ||
|
|
41
|
+
url: response.url || resolvedUrl,
|
|
25
42
|
redirected: response.redirected || false,
|
|
26
43
|
type: "basic",
|
|
27
44
|
async text() {
|
|
@@ -161,8 +178,10 @@ export const dns = {
|
|
|
161
178
|
if (typeof options === "function") {
|
|
162
179
|
cb = options;
|
|
163
180
|
}
|
|
164
|
-
_networkDnsLookupRaw
|
|
165
|
-
.
|
|
181
|
+
_networkDnsLookupRaw
|
|
182
|
+
.apply(undefined, [hostname], { result: { promise: true } })
|
|
183
|
+
.then((resultJson) => {
|
|
184
|
+
const result = JSON.parse(resultJson);
|
|
166
185
|
if (result.error) {
|
|
167
186
|
const err = new Error(result.error);
|
|
168
187
|
err.code = result.code || "ENOTFOUND";
|
|
@@ -588,13 +607,27 @@ export class ClientRequest {
|
|
|
588
607
|
body: this._body || null,
|
|
589
608
|
...tls,
|
|
590
609
|
});
|
|
591
|
-
const
|
|
610
|
+
const responseJson = await _networkHttpRequestRaw.apply(undefined, [url, optionsJson], {
|
|
611
|
+
result: { promise: true },
|
|
612
|
+
});
|
|
613
|
+
const response = JSON.parse(responseJson);
|
|
592
614
|
this.finished = true;
|
|
593
615
|
// 101 Switching Protocols → fire 'upgrade' event
|
|
594
616
|
if (response.status === 101) {
|
|
595
617
|
const res = new IncomingMessage(response);
|
|
596
|
-
|
|
597
|
-
|
|
618
|
+
// Use UpgradeSocket for bidirectional data relay when socketId is available
|
|
619
|
+
let socket = this.socket;
|
|
620
|
+
if (response.upgradeSocketId != null) {
|
|
621
|
+
socket = new UpgradeSocket(response.upgradeSocketId, {
|
|
622
|
+
host: this._options.hostname,
|
|
623
|
+
port: Number(this._options.port) || 80,
|
|
624
|
+
});
|
|
625
|
+
upgradeSocketInstances.set(response.upgradeSocketId, socket);
|
|
626
|
+
}
|
|
627
|
+
const head = typeof Buffer !== "undefined"
|
|
628
|
+
? (response.body ? Buffer.from(response.body, "base64") : Buffer.alloc(0))
|
|
629
|
+
: new Uint8Array(0);
|
|
630
|
+
this._emit("upgrade", res, socket, head);
|
|
598
631
|
return;
|
|
599
632
|
}
|
|
600
633
|
const res = new IncomingMessage(response);
|
|
@@ -807,6 +840,8 @@ class Agent {
|
|
|
807
840
|
}
|
|
808
841
|
let nextServerId = 1;
|
|
809
842
|
const serverRequestListeners = new Map();
|
|
843
|
+
// Server instances indexed by serverId — used by upgrade dispatch to emit 'upgrade' events
|
|
844
|
+
const serverInstances = new Map();
|
|
810
845
|
class ServerIncomingMessage {
|
|
811
846
|
headers;
|
|
812
847
|
rawHeaders;
|
|
@@ -1101,7 +1136,9 @@ class Server {
|
|
|
1101
1136
|
else {
|
|
1102
1137
|
serverRequestListeners.set(this._serverId, () => undefined);
|
|
1103
1138
|
}
|
|
1139
|
+
serverInstances.set(this._serverId, this);
|
|
1104
1140
|
}
|
|
1141
|
+
/** @internal Emit an event — used by upgrade dispatch to fire 'upgrade' events. */
|
|
1105
1142
|
_emit(event, ...args) {
|
|
1106
1143
|
const listeners = this._listeners[event];
|
|
1107
1144
|
if (!listeners || listeners.length === 0)
|
|
@@ -1112,7 +1149,8 @@ class Server {
|
|
|
1112
1149
|
if (typeof _networkHttpServerListenRaw === "undefined") {
|
|
1113
1150
|
throw new Error("http.createServer requires NetworkAdapter.httpServerListen support");
|
|
1114
1151
|
}
|
|
1115
|
-
const
|
|
1152
|
+
const resultJson = await _networkHttpServerListenRaw.apply(undefined, [JSON.stringify({ serverId: this._serverId, port, hostname })], { result: { promise: true } });
|
|
1153
|
+
const result = JSON.parse(resultJson);
|
|
1116
1154
|
this._address = result.address;
|
|
1117
1155
|
this.listening = true;
|
|
1118
1156
|
this._handleId = `http-server:${this._serverId}`;
|
|
@@ -1149,10 +1187,13 @@ class Server {
|
|
|
1149
1187
|
await this._listenPromise;
|
|
1150
1188
|
}
|
|
1151
1189
|
if (this.listening && typeof _networkHttpServerCloseRaw !== "undefined") {
|
|
1152
|
-
await _networkHttpServerCloseRaw(this._serverId
|
|
1190
|
+
await _networkHttpServerCloseRaw.apply(undefined, [this._serverId], {
|
|
1191
|
+
result: { promise: true },
|
|
1192
|
+
});
|
|
1153
1193
|
}
|
|
1154
1194
|
this.listening = false;
|
|
1155
1195
|
this._address = null;
|
|
1196
|
+
serverInstances.delete(this._serverId);
|
|
1156
1197
|
if (this._handleId && typeof _unregisterHandle === "function") {
|
|
1157
1198
|
_unregisterHandle(this._handleId);
|
|
1158
1199
|
}
|
|
@@ -1225,11 +1266,12 @@ class Server {
|
|
|
1225
1266
|
}
|
|
1226
1267
|
}
|
|
1227
1268
|
/** Route an incoming HTTP request to the server's request listener and return the serialized response. */
|
|
1228
|
-
async function dispatchServerRequest(serverId,
|
|
1269
|
+
async function dispatchServerRequest(serverId, requestJson) {
|
|
1229
1270
|
const listener = serverRequestListeners.get(serverId);
|
|
1230
1271
|
if (!listener) {
|
|
1231
1272
|
throw new Error(`Unknown HTTP server: ${serverId}`);
|
|
1232
1273
|
}
|
|
1274
|
+
const request = JSON.parse(requestJson);
|
|
1233
1275
|
const incoming = new ServerIncomingMessage(request);
|
|
1234
1276
|
const outgoing = new ServerResponseBridge();
|
|
1235
1277
|
try {
|
|
@@ -1257,7 +1299,184 @@ async function dispatchServerRequest(serverId, request) {
|
|
|
1257
1299
|
outgoing.end();
|
|
1258
1300
|
}
|
|
1259
1301
|
await outgoing.waitForClose();
|
|
1260
|
-
return outgoing.serialize();
|
|
1302
|
+
return JSON.stringify(outgoing.serialize());
|
|
1303
|
+
}
|
|
1304
|
+
// Upgrade socket for bidirectional data relay through the host bridge
|
|
1305
|
+
const upgradeSocketInstances = new Map();
|
|
1306
|
+
class UpgradeSocket {
|
|
1307
|
+
remoteAddress;
|
|
1308
|
+
remotePort;
|
|
1309
|
+
localAddress = "127.0.0.1";
|
|
1310
|
+
localPort = 0;
|
|
1311
|
+
connecting = false;
|
|
1312
|
+
destroyed = false;
|
|
1313
|
+
writable = true;
|
|
1314
|
+
readable = true;
|
|
1315
|
+
readyState = "open";
|
|
1316
|
+
bytesWritten = 0;
|
|
1317
|
+
_listeners = {};
|
|
1318
|
+
_socketId;
|
|
1319
|
+
// Readable stream state stub for ws compatibility (socketOnClose checks _readableState.endEmitted)
|
|
1320
|
+
_readableState = { endEmitted: false };
|
|
1321
|
+
_writableState = { finished: false, errorEmitted: false };
|
|
1322
|
+
constructor(socketId, options) {
|
|
1323
|
+
this._socketId = socketId;
|
|
1324
|
+
this.remoteAddress = options?.host || "127.0.0.1";
|
|
1325
|
+
this.remotePort = options?.port || 80;
|
|
1326
|
+
}
|
|
1327
|
+
setTimeout(_ms, _cb) { return this; }
|
|
1328
|
+
setNoDelay(_noDelay) { return this; }
|
|
1329
|
+
setKeepAlive(_enable, _delay) { return this; }
|
|
1330
|
+
ref() { return this; }
|
|
1331
|
+
unref() { return this; }
|
|
1332
|
+
cork() { }
|
|
1333
|
+
uncork() { }
|
|
1334
|
+
pause() { return this; }
|
|
1335
|
+
resume() { return this; }
|
|
1336
|
+
address() {
|
|
1337
|
+
return { address: this.localAddress, family: "IPv4", port: this.localPort };
|
|
1338
|
+
}
|
|
1339
|
+
on(event, listener) {
|
|
1340
|
+
if (!this._listeners[event])
|
|
1341
|
+
this._listeners[event] = [];
|
|
1342
|
+
this._listeners[event].push(listener);
|
|
1343
|
+
return this;
|
|
1344
|
+
}
|
|
1345
|
+
addListener(event, listener) {
|
|
1346
|
+
return this.on(event, listener);
|
|
1347
|
+
}
|
|
1348
|
+
once(event, listener) {
|
|
1349
|
+
const wrapper = (...args) => {
|
|
1350
|
+
this.off(event, wrapper);
|
|
1351
|
+
listener(...args);
|
|
1352
|
+
};
|
|
1353
|
+
return this.on(event, wrapper);
|
|
1354
|
+
}
|
|
1355
|
+
off(event, listener) {
|
|
1356
|
+
if (this._listeners[event]) {
|
|
1357
|
+
const idx = this._listeners[event].indexOf(listener);
|
|
1358
|
+
if (idx !== -1)
|
|
1359
|
+
this._listeners[event].splice(idx, 1);
|
|
1360
|
+
}
|
|
1361
|
+
return this;
|
|
1362
|
+
}
|
|
1363
|
+
removeListener(event, listener) {
|
|
1364
|
+
return this.off(event, listener);
|
|
1365
|
+
}
|
|
1366
|
+
removeAllListeners(event) {
|
|
1367
|
+
if (event) {
|
|
1368
|
+
delete this._listeners[event];
|
|
1369
|
+
}
|
|
1370
|
+
else {
|
|
1371
|
+
this._listeners = {};
|
|
1372
|
+
}
|
|
1373
|
+
return this;
|
|
1374
|
+
}
|
|
1375
|
+
emit(event, ...args) {
|
|
1376
|
+
const handlers = this._listeners[event];
|
|
1377
|
+
if (handlers)
|
|
1378
|
+
handlers.slice().forEach((fn) => fn.call(this, ...args));
|
|
1379
|
+
return handlers !== undefined && handlers.length > 0;
|
|
1380
|
+
}
|
|
1381
|
+
listenerCount(event) {
|
|
1382
|
+
return this._listeners[event]?.length || 0;
|
|
1383
|
+
}
|
|
1384
|
+
write(data, encodingOrCb, cb) {
|
|
1385
|
+
if (this.destroyed)
|
|
1386
|
+
return false;
|
|
1387
|
+
const callback = typeof encodingOrCb === "function" ? encodingOrCb : cb;
|
|
1388
|
+
if (typeof _upgradeSocketWriteRaw !== "undefined") {
|
|
1389
|
+
let base64;
|
|
1390
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(data)) {
|
|
1391
|
+
base64 = data.toString("base64");
|
|
1392
|
+
}
|
|
1393
|
+
else if (typeof data === "string") {
|
|
1394
|
+
base64 = typeof Buffer !== "undefined" ? Buffer.from(data).toString("base64") : btoa(data);
|
|
1395
|
+
}
|
|
1396
|
+
else if (data instanceof Uint8Array) {
|
|
1397
|
+
base64 = typeof Buffer !== "undefined" ? Buffer.from(data).toString("base64") : btoa(String.fromCharCode(...data));
|
|
1398
|
+
}
|
|
1399
|
+
else {
|
|
1400
|
+
base64 = typeof Buffer !== "undefined" ? Buffer.from(String(data)).toString("base64") : btoa(String(data));
|
|
1401
|
+
}
|
|
1402
|
+
this.bytesWritten += base64.length;
|
|
1403
|
+
_upgradeSocketWriteRaw.applySync(undefined, [this._socketId, base64]);
|
|
1404
|
+
}
|
|
1405
|
+
if (callback)
|
|
1406
|
+
callback();
|
|
1407
|
+
return true;
|
|
1408
|
+
}
|
|
1409
|
+
end(data) {
|
|
1410
|
+
if (data)
|
|
1411
|
+
this.write(data);
|
|
1412
|
+
if (typeof _upgradeSocketEndRaw !== "undefined" && !this.destroyed) {
|
|
1413
|
+
_upgradeSocketEndRaw.applySync(undefined, [this._socketId]);
|
|
1414
|
+
}
|
|
1415
|
+
this.writable = false;
|
|
1416
|
+
this.emit("finish");
|
|
1417
|
+
return this;
|
|
1418
|
+
}
|
|
1419
|
+
destroy(err) {
|
|
1420
|
+
if (this.destroyed)
|
|
1421
|
+
return this;
|
|
1422
|
+
this.destroyed = true;
|
|
1423
|
+
this.writable = false;
|
|
1424
|
+
this.readable = false;
|
|
1425
|
+
this._readableState.endEmitted = true;
|
|
1426
|
+
this._writableState.finished = true;
|
|
1427
|
+
if (typeof _upgradeSocketDestroyRaw !== "undefined") {
|
|
1428
|
+
_upgradeSocketDestroyRaw.applySync(undefined, [this._socketId]);
|
|
1429
|
+
}
|
|
1430
|
+
upgradeSocketInstances.delete(this._socketId);
|
|
1431
|
+
if (err)
|
|
1432
|
+
this.emit("error", err);
|
|
1433
|
+
this.emit("close", false);
|
|
1434
|
+
return this;
|
|
1435
|
+
}
|
|
1436
|
+
// Push data received from the host into this socket
|
|
1437
|
+
_pushData(data) {
|
|
1438
|
+
this.emit("data", data);
|
|
1439
|
+
}
|
|
1440
|
+
// Signal end-of-stream from the host
|
|
1441
|
+
_pushEnd() {
|
|
1442
|
+
this.readable = false;
|
|
1443
|
+
this._readableState.endEmitted = true;
|
|
1444
|
+
this._writableState.finished = true;
|
|
1445
|
+
this.emit("end");
|
|
1446
|
+
this.emit("close", false);
|
|
1447
|
+
upgradeSocketInstances.delete(this._socketId);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
/** Route an incoming HTTP upgrade to the server's 'upgrade' event listeners. */
|
|
1451
|
+
function dispatchUpgradeRequest(serverId, requestJson, headBase64, socketId) {
|
|
1452
|
+
const server = serverInstances.get(serverId);
|
|
1453
|
+
if (!server) {
|
|
1454
|
+
throw new Error(`Unknown HTTP server for upgrade: ${serverId}`);
|
|
1455
|
+
}
|
|
1456
|
+
const request = JSON.parse(requestJson);
|
|
1457
|
+
const incoming = new ServerIncomingMessage(request);
|
|
1458
|
+
const head = typeof Buffer !== "undefined" ? Buffer.from(headBase64, "base64") : new Uint8Array(0);
|
|
1459
|
+
const socket = new UpgradeSocket(socketId, {
|
|
1460
|
+
host: incoming.headers["host"]?.split(":")[0] || "127.0.0.1",
|
|
1461
|
+
});
|
|
1462
|
+
upgradeSocketInstances.set(socketId, socket);
|
|
1463
|
+
// Emit 'upgrade' on the server — ws.WebSocketServer listens for this
|
|
1464
|
+
server._emit("upgrade", incoming, socket, head);
|
|
1465
|
+
}
|
|
1466
|
+
/** Push data from host to an upgrade socket. */
|
|
1467
|
+
function onUpgradeSocketData(socketId, dataBase64) {
|
|
1468
|
+
const socket = upgradeSocketInstances.get(socketId);
|
|
1469
|
+
if (socket) {
|
|
1470
|
+
const data = typeof Buffer !== "undefined" ? Buffer.from(dataBase64, "base64") : new Uint8Array(0);
|
|
1471
|
+
socket._pushData(data);
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
/** Signal end-of-stream from host to an upgrade socket. */
|
|
1475
|
+
function onUpgradeSocketEnd(socketId) {
|
|
1476
|
+
const socket = upgradeSocketInstances.get(socketId);
|
|
1477
|
+
if (socket) {
|
|
1478
|
+
socket._pushEnd();
|
|
1479
|
+
}
|
|
1261
1480
|
}
|
|
1262
1481
|
// Function-based ServerResponse constructor — allows .call() inheritance
|
|
1263
1482
|
// used by light-my-request (Fastify's inject), which does
|
|
@@ -1403,12 +1622,408 @@ export const http2 = {
|
|
|
1403
1622
|
throw new Error("http2.createSecureServer is not supported in sandbox");
|
|
1404
1623
|
},
|
|
1405
1624
|
};
|
|
1625
|
+
// ----------------------------------------------------------------
|
|
1626
|
+
// net module — TCP socket bridge
|
|
1627
|
+
// ----------------------------------------------------------------
|
|
1628
|
+
const netSocketInstances = new Map();
|
|
1629
|
+
class NetSocket {
|
|
1630
|
+
remoteAddress = "";
|
|
1631
|
+
remotePort = 0;
|
|
1632
|
+
remoteFamily = "";
|
|
1633
|
+
localAddress = "0.0.0.0";
|
|
1634
|
+
localPort = 0;
|
|
1635
|
+
connecting = true;
|
|
1636
|
+
pending = true;
|
|
1637
|
+
destroyed = false;
|
|
1638
|
+
writable = true;
|
|
1639
|
+
readable = true;
|
|
1640
|
+
readyState = "opening";
|
|
1641
|
+
bytesRead = 0;
|
|
1642
|
+
bytesWritten = 0;
|
|
1643
|
+
_listeners = {};
|
|
1644
|
+
/** @internal socket ID shared with TLS upgrade bridge */
|
|
1645
|
+
_socketId = -1;
|
|
1646
|
+
_connectHost = "";
|
|
1647
|
+
_connectPort = 0;
|
|
1648
|
+
// Stream state stubs for compatibility (ssh2 checks _readableState.ended)
|
|
1649
|
+
_readableState = { endEmitted: false, ended: false };
|
|
1650
|
+
_writableState = { finished: false, errorEmitted: false, ended: false };
|
|
1651
|
+
constructor(_options) {
|
|
1652
|
+
// Options like { allowHalfOpen } are accepted but ignored
|
|
1653
|
+
}
|
|
1654
|
+
connect(...args) {
|
|
1655
|
+
// Parse overloaded signatures: connect(port, host?, cb?) or connect({port, host}, cb?)
|
|
1656
|
+
let port;
|
|
1657
|
+
let host;
|
|
1658
|
+
let connectListener;
|
|
1659
|
+
if (typeof args[0] === "object" && args[0] !== null) {
|
|
1660
|
+
const opts = args[0];
|
|
1661
|
+
port = Number(opts.port);
|
|
1662
|
+
host = String(opts.host || "127.0.0.1");
|
|
1663
|
+
if (typeof args[1] === "function")
|
|
1664
|
+
connectListener = args[1];
|
|
1665
|
+
}
|
|
1666
|
+
else {
|
|
1667
|
+
port = Number(args[0]);
|
|
1668
|
+
host = typeof args[1] === "string" ? args[1] : "127.0.0.1";
|
|
1669
|
+
if (typeof args[1] === "function") {
|
|
1670
|
+
connectListener = args[1];
|
|
1671
|
+
host = "127.0.0.1";
|
|
1672
|
+
}
|
|
1673
|
+
else if (typeof args[2] === "function") {
|
|
1674
|
+
connectListener = args[2];
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
this._connectHost = host;
|
|
1678
|
+
this._connectPort = port;
|
|
1679
|
+
if (connectListener)
|
|
1680
|
+
this.once("connect", connectListener);
|
|
1681
|
+
if (typeof _netSocketConnectRaw === "undefined") {
|
|
1682
|
+
// Schedule error emission asynchronously like real Node
|
|
1683
|
+
Promise.resolve().then(() => {
|
|
1684
|
+
const err = new Error("net.Socket requires NetworkAdapter to be configured");
|
|
1685
|
+
this._onError(err.message);
|
|
1686
|
+
});
|
|
1687
|
+
return this;
|
|
1688
|
+
}
|
|
1689
|
+
// Register active handle
|
|
1690
|
+
if (typeof _registerHandle !== "undefined") {
|
|
1691
|
+
_registerHandle(`net.socket:${host}:${port}`, `TCP connection to ${host}:${port}`);
|
|
1692
|
+
}
|
|
1693
|
+
// Synchronous call: host creates socket, starts connecting, returns socketId
|
|
1694
|
+
this._socketId = _netSocketConnectRaw.applySync(undefined, [host, port]);
|
|
1695
|
+
netSocketInstances.set(this._socketId, this);
|
|
1696
|
+
return this;
|
|
1697
|
+
}
|
|
1698
|
+
setTimeout(_ms, _cb) { return this; }
|
|
1699
|
+
setNoDelay(_noDelay) { return this; }
|
|
1700
|
+
setKeepAlive(_enable, _delay) { return this; }
|
|
1701
|
+
setMaxListeners(_n) { return this; }
|
|
1702
|
+
getMaxListeners() { return 10; }
|
|
1703
|
+
ref() { return this; }
|
|
1704
|
+
unref() { return this; }
|
|
1705
|
+
cork() { }
|
|
1706
|
+
uncork() { }
|
|
1707
|
+
pause() { return this; }
|
|
1708
|
+
resume() { return this; }
|
|
1709
|
+
pipe(destination) { return destination; }
|
|
1710
|
+
address() {
|
|
1711
|
+
return { address: this.localAddress, family: "IPv4", port: this.localPort };
|
|
1712
|
+
}
|
|
1713
|
+
listeners(event) {
|
|
1714
|
+
return (this._listeners[event] || []).slice();
|
|
1715
|
+
}
|
|
1716
|
+
rawListeners(event) {
|
|
1717
|
+
return this.listeners(event);
|
|
1718
|
+
}
|
|
1719
|
+
eventNames() {
|
|
1720
|
+
return Object.keys(this._listeners).filter((k) => (this._listeners[k]?.length ?? 0) > 0);
|
|
1721
|
+
}
|
|
1722
|
+
prependListener(event, listener) {
|
|
1723
|
+
if (!this._listeners[event])
|
|
1724
|
+
this._listeners[event] = [];
|
|
1725
|
+
this._listeners[event].unshift(listener);
|
|
1726
|
+
return this;
|
|
1727
|
+
}
|
|
1728
|
+
prependOnceListener(event, listener) {
|
|
1729
|
+
const wrapper = (...args) => {
|
|
1730
|
+
this.off(event, wrapper);
|
|
1731
|
+
listener(...args);
|
|
1732
|
+
};
|
|
1733
|
+
return this.prependListener(event, wrapper);
|
|
1734
|
+
}
|
|
1735
|
+
on(event, listener) {
|
|
1736
|
+
if (!this._listeners[event])
|
|
1737
|
+
this._listeners[event] = [];
|
|
1738
|
+
this._listeners[event].push(listener);
|
|
1739
|
+
return this;
|
|
1740
|
+
}
|
|
1741
|
+
addListener(event, listener) { return this.on(event, listener); }
|
|
1742
|
+
once(event, listener) {
|
|
1743
|
+
const wrapper = (...args) => {
|
|
1744
|
+
this.off(event, wrapper);
|
|
1745
|
+
listener(...args);
|
|
1746
|
+
};
|
|
1747
|
+
return this.on(event, wrapper);
|
|
1748
|
+
}
|
|
1749
|
+
off(event, listener) {
|
|
1750
|
+
if (this._listeners[event]) {
|
|
1751
|
+
const idx = this._listeners[event].indexOf(listener);
|
|
1752
|
+
if (idx !== -1)
|
|
1753
|
+
this._listeners[event].splice(idx, 1);
|
|
1754
|
+
}
|
|
1755
|
+
return this;
|
|
1756
|
+
}
|
|
1757
|
+
removeListener(event, listener) { return this.off(event, listener); }
|
|
1758
|
+
removeAllListeners(event) {
|
|
1759
|
+
if (event) {
|
|
1760
|
+
delete this._listeners[event];
|
|
1761
|
+
}
|
|
1762
|
+
else {
|
|
1763
|
+
this._listeners = {};
|
|
1764
|
+
}
|
|
1765
|
+
return this;
|
|
1766
|
+
}
|
|
1767
|
+
emit(event, ...args) {
|
|
1768
|
+
const handlers = this._listeners[event];
|
|
1769
|
+
if (handlers)
|
|
1770
|
+
handlers.slice().forEach((fn) => fn.call(this, ...args));
|
|
1771
|
+
return handlers !== undefined && handlers.length > 0;
|
|
1772
|
+
}
|
|
1773
|
+
listenerCount(event) {
|
|
1774
|
+
return this._listeners[event]?.length || 0;
|
|
1775
|
+
}
|
|
1776
|
+
write(data, encodingOrCb, cb) {
|
|
1777
|
+
if (this.destroyed)
|
|
1778
|
+
return false;
|
|
1779
|
+
const callback = typeof encodingOrCb === "function" ? encodingOrCb : cb;
|
|
1780
|
+
if (typeof _netSocketWriteRaw !== "undefined" && this._socketId >= 0) {
|
|
1781
|
+
let base64;
|
|
1782
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(data)) {
|
|
1783
|
+
base64 = data.toString("base64");
|
|
1784
|
+
}
|
|
1785
|
+
else if (typeof data === "string") {
|
|
1786
|
+
const encoding = typeof encodingOrCb === "string" ? encodingOrCb : "utf8";
|
|
1787
|
+
base64 = typeof Buffer !== "undefined" ? Buffer.from(data, encoding).toString("base64") : btoa(data);
|
|
1788
|
+
}
|
|
1789
|
+
else if (data instanceof Uint8Array) {
|
|
1790
|
+
base64 = typeof Buffer !== "undefined" ? Buffer.from(data).toString("base64") : btoa(String.fromCharCode(...data));
|
|
1791
|
+
}
|
|
1792
|
+
else {
|
|
1793
|
+
base64 = typeof Buffer !== "undefined" ? Buffer.from(String(data)).toString("base64") : btoa(String(data));
|
|
1794
|
+
}
|
|
1795
|
+
this.bytesWritten += base64.length;
|
|
1796
|
+
_netSocketWriteRaw.applySync(undefined, [this._socketId, base64]);
|
|
1797
|
+
}
|
|
1798
|
+
if (callback)
|
|
1799
|
+
callback();
|
|
1800
|
+
return true;
|
|
1801
|
+
}
|
|
1802
|
+
end(data, encodingOrCb, cb) {
|
|
1803
|
+
if (data !== undefined && data !== null)
|
|
1804
|
+
this.write(data, encodingOrCb, cb);
|
|
1805
|
+
if (typeof _netSocketEndRaw !== "undefined" && this._socketId >= 0 && !this.destroyed) {
|
|
1806
|
+
_netSocketEndRaw.applySync(undefined, [this._socketId]);
|
|
1807
|
+
}
|
|
1808
|
+
this.writable = false;
|
|
1809
|
+
this.readyState = this.readable ? "readOnly" : "closed";
|
|
1810
|
+
this.emit("finish");
|
|
1811
|
+
return this;
|
|
1812
|
+
}
|
|
1813
|
+
destroy(err) {
|
|
1814
|
+
if (this.destroyed)
|
|
1815
|
+
return this;
|
|
1816
|
+
this.destroyed = true;
|
|
1817
|
+
this.writable = false;
|
|
1818
|
+
this.readable = false;
|
|
1819
|
+
this.readyState = "closed";
|
|
1820
|
+
this._readableState.endEmitted = true;
|
|
1821
|
+
this._writableState.finished = true;
|
|
1822
|
+
if (typeof _netSocketDestroyRaw !== "undefined" && this._socketId >= 0) {
|
|
1823
|
+
_netSocketDestroyRaw.applySync(undefined, [this._socketId]);
|
|
1824
|
+
}
|
|
1825
|
+
this._cleanup();
|
|
1826
|
+
if (err)
|
|
1827
|
+
this.emit("error", err);
|
|
1828
|
+
this.emit("close", !!err);
|
|
1829
|
+
return this;
|
|
1830
|
+
}
|
|
1831
|
+
// Host→Guest event dispatch handlers
|
|
1832
|
+
_onConnect() {
|
|
1833
|
+
this.connecting = false;
|
|
1834
|
+
this.pending = false;
|
|
1835
|
+
this.remoteAddress = this._connectHost;
|
|
1836
|
+
this.remotePort = this._connectPort;
|
|
1837
|
+
this.remoteFamily = "IPv4";
|
|
1838
|
+
this.readyState = "open";
|
|
1839
|
+
this.emit("connect");
|
|
1840
|
+
this.emit("ready");
|
|
1841
|
+
}
|
|
1842
|
+
_onData(dataBase64) {
|
|
1843
|
+
const buf = typeof Buffer !== "undefined" ? Buffer.from(dataBase64, "base64") : new Uint8Array(0);
|
|
1844
|
+
this.bytesRead += buf.length;
|
|
1845
|
+
this.emit("data", buf);
|
|
1846
|
+
}
|
|
1847
|
+
_onEnd() {
|
|
1848
|
+
this.readable = false;
|
|
1849
|
+
this._readableState.endEmitted = true;
|
|
1850
|
+
this._readableState.ended = true;
|
|
1851
|
+
this.readyState = this.writable ? "writeOnly" : "closed";
|
|
1852
|
+
this.emit("end");
|
|
1853
|
+
}
|
|
1854
|
+
_onError(message) {
|
|
1855
|
+
const err = new Error(message);
|
|
1856
|
+
this.destroy(err);
|
|
1857
|
+
}
|
|
1858
|
+
_onClose(hadError) {
|
|
1859
|
+
this._cleanup();
|
|
1860
|
+
if (!this.destroyed) {
|
|
1861
|
+
this.destroyed = true;
|
|
1862
|
+
this.readable = false;
|
|
1863
|
+
this.writable = false;
|
|
1864
|
+
this.readyState = "closed";
|
|
1865
|
+
this.emit("close", hadError);
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
_cleanup() {
|
|
1869
|
+
if (this._socketId >= 0) {
|
|
1870
|
+
netSocketInstances.delete(this._socketId);
|
|
1871
|
+
if (typeof _unregisterHandle !== "undefined") {
|
|
1872
|
+
_unregisterHandle(`net.socket:${this._connectHost}:${this._connectPort}`);
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
/** Dispatch events from host to guest net sockets. */
|
|
1878
|
+
function onNetSocketDispatch(socketId, type, data) {
|
|
1879
|
+
const socket = netSocketInstances.get(socketId);
|
|
1880
|
+
if (!socket)
|
|
1881
|
+
return;
|
|
1882
|
+
switch (type) {
|
|
1883
|
+
case "connect":
|
|
1884
|
+
socket._onConnect();
|
|
1885
|
+
break;
|
|
1886
|
+
case "data":
|
|
1887
|
+
socket._onData(data);
|
|
1888
|
+
break;
|
|
1889
|
+
case "end":
|
|
1890
|
+
socket._onEnd();
|
|
1891
|
+
break;
|
|
1892
|
+
case "error":
|
|
1893
|
+
socket._onError(data);
|
|
1894
|
+
break;
|
|
1895
|
+
case "close":
|
|
1896
|
+
socket._onClose(data === "1");
|
|
1897
|
+
break;
|
|
1898
|
+
case "secureConnect":
|
|
1899
|
+
socket.emit("secureConnect");
|
|
1900
|
+
break;
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
// Validate IP address format
|
|
1904
|
+
function netIsIP(input) {
|
|
1905
|
+
if (typeof input !== "string")
|
|
1906
|
+
return 0;
|
|
1907
|
+
// IPv4: four octets 0-255
|
|
1908
|
+
if (/^(\d{1,3}\.){3}\d{1,3}$/.test(input)) {
|
|
1909
|
+
const parts = input.split(".");
|
|
1910
|
+
if (parts.every((p) => { const n = Number(p); return n >= 0 && n <= 255; }))
|
|
1911
|
+
return 4;
|
|
1912
|
+
}
|
|
1913
|
+
// IPv6: simplified check
|
|
1914
|
+
if (/^(::)?([0-9a-fA-F]{1,4}(::?)){0,7}([0-9a-fA-F]{1,4})?$/.test(input))
|
|
1915
|
+
return 6;
|
|
1916
|
+
if (/^::ffff:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(input))
|
|
1917
|
+
return 6;
|
|
1918
|
+
return 0;
|
|
1919
|
+
}
|
|
1920
|
+
export const net = {
|
|
1921
|
+
Socket: NetSocket,
|
|
1922
|
+
connect(portOrOpts, hostOrCb, cb) {
|
|
1923
|
+
const socket = new NetSocket();
|
|
1924
|
+
socket.connect(portOrOpts, hostOrCb, cb);
|
|
1925
|
+
return socket;
|
|
1926
|
+
},
|
|
1927
|
+
createConnection(portOrOpts, hostOrCb, cb) {
|
|
1928
|
+
return net.connect(portOrOpts, hostOrCb, cb);
|
|
1929
|
+
},
|
|
1930
|
+
createServer() {
|
|
1931
|
+
throw new Error("net.createServer is not supported in sandbox");
|
|
1932
|
+
},
|
|
1933
|
+
isIP: netIsIP,
|
|
1934
|
+
isIPv4(input) { return netIsIP(input) === 4; },
|
|
1935
|
+
isIPv6(input) { return netIsIP(input) === 6; },
|
|
1936
|
+
};
|
|
1937
|
+
// ----------------------------------------------------------------
|
|
1938
|
+
// tls module — TLS socket upgrade bridge
|
|
1939
|
+
// ----------------------------------------------------------------
|
|
1940
|
+
/** TLS socket that wraps an existing NetSocket after host-side TLS upgrade. */
|
|
1941
|
+
class TLSSocket extends NetSocket {
|
|
1942
|
+
encrypted = true;
|
|
1943
|
+
authorized = false;
|
|
1944
|
+
authorizationError = null;
|
|
1945
|
+
alpnProtocol = false;
|
|
1946
|
+
_wrappedSocket = null;
|
|
1947
|
+
constructor(originalSocket) {
|
|
1948
|
+
super();
|
|
1949
|
+
this._wrappedSocket = originalSocket;
|
|
1950
|
+
// Copy connection state from original socket
|
|
1951
|
+
this.remoteAddress = originalSocket.remoteAddress;
|
|
1952
|
+
this.remotePort = originalSocket.remotePort;
|
|
1953
|
+
this.remoteFamily = originalSocket.remoteFamily;
|
|
1954
|
+
this.localAddress = originalSocket.localAddress;
|
|
1955
|
+
this.localPort = originalSocket.localPort;
|
|
1956
|
+
this.connecting = false;
|
|
1957
|
+
this.pending = false;
|
|
1958
|
+
this.readyState = "open";
|
|
1959
|
+
// Share the same socketId — bridge events route here after upgrade
|
|
1960
|
+
this._socketId = originalSocket._socketId;
|
|
1961
|
+
// Copy private connect info so _cleanup unregisters the correct handle
|
|
1962
|
+
this._connectHost = originalSocket._connectHost;
|
|
1963
|
+
this._connectPort = originalSocket._connectPort;
|
|
1964
|
+
netSocketInstances.set(this._socketId, this);
|
|
1965
|
+
}
|
|
1966
|
+
_onSecureConnect() {
|
|
1967
|
+
this.authorized = true;
|
|
1968
|
+
this.emit("secureConnect");
|
|
1969
|
+
}
|
|
1970
|
+
// Forward end/close to the wrapped raw socket — Node.js tls.TLSSocket
|
|
1971
|
+
// closes the underlying socket, which fires its 'close' event. Libraries
|
|
1972
|
+
// like pg rely on the original socket's 'close' listener to detect shutdown.
|
|
1973
|
+
_onEnd() {
|
|
1974
|
+
super._onEnd();
|
|
1975
|
+
if (this._wrappedSocket)
|
|
1976
|
+
this._wrappedSocket._onEnd();
|
|
1977
|
+
}
|
|
1978
|
+
_onClose(hadError) {
|
|
1979
|
+
super._onClose(hadError);
|
|
1980
|
+
if (this._wrappedSocket) {
|
|
1981
|
+
this._wrappedSocket._onClose(hadError);
|
|
1982
|
+
this._wrappedSocket = null;
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
export const tlsModule = {
|
|
1987
|
+
TLSSocket: TLSSocket,
|
|
1988
|
+
connect(options) {
|
|
1989
|
+
const existingSocket = options.socket;
|
|
1990
|
+
if (!existingSocket || existingSocket._socketId < 0) {
|
|
1991
|
+
throw new Error("tls.connect requires an existing connected socket via options.socket");
|
|
1992
|
+
}
|
|
1993
|
+
// Create TLS socket wrapper on sandbox side
|
|
1994
|
+
const tlsSocket = new TLSSocket(existingSocket);
|
|
1995
|
+
if (typeof _netSocketUpgradeTlsRaw === "undefined") {
|
|
1996
|
+
Promise.resolve().then(() => {
|
|
1997
|
+
tlsSocket._onError("tls.connect requires NetworkAdapter TLS support");
|
|
1998
|
+
});
|
|
1999
|
+
return tlsSocket;
|
|
2000
|
+
}
|
|
2001
|
+
// Tell host to wrap the underlying TCP socket with TLS
|
|
2002
|
+
_netSocketUpgradeTlsRaw.applySync(undefined, [
|
|
2003
|
+
existingSocket._socketId,
|
|
2004
|
+
JSON.stringify({
|
|
2005
|
+
rejectUnauthorized: options.rejectUnauthorized ?? true,
|
|
2006
|
+
servername: options.servername,
|
|
2007
|
+
}),
|
|
2008
|
+
]);
|
|
2009
|
+
return tlsSocket;
|
|
2010
|
+
},
|
|
2011
|
+
createSecureContext(_options) {
|
|
2012
|
+
return {};
|
|
2013
|
+
},
|
|
2014
|
+
};
|
|
1406
2015
|
// Export modules and make them available as globals for require()
|
|
1407
2016
|
exposeCustomGlobal("_httpModule", http);
|
|
1408
2017
|
exposeCustomGlobal("_httpsModule", https);
|
|
1409
2018
|
exposeCustomGlobal("_http2Module", http2);
|
|
1410
2019
|
exposeCustomGlobal("_dnsModule", dns);
|
|
2020
|
+
exposeCustomGlobal("_netModule", net);
|
|
2021
|
+
exposeCustomGlobal("_tlsModule", tlsModule);
|
|
1411
2022
|
exposeCustomGlobal("_httpServerDispatch", dispatchServerRequest);
|
|
2023
|
+
exposeCustomGlobal("_httpServerUpgradeDispatch", dispatchUpgradeRequest);
|
|
2024
|
+
exposeCustomGlobal("_upgradeSocketData", onUpgradeSocketData);
|
|
2025
|
+
exposeCustomGlobal("_upgradeSocketEnd", onUpgradeSocketEnd);
|
|
2026
|
+
exposeCustomGlobal("_netSocketDispatch", onNetSocketDispatch);
|
|
1412
2027
|
// Harden fetch API globals (non-writable, non-configurable)
|
|
1413
2028
|
exposeCustomGlobal("fetch", fetch);
|
|
1414
2029
|
exposeCustomGlobal("Headers", Headers);
|
|
@@ -1428,6 +2043,8 @@ export default {
|
|
|
1428
2043
|
http,
|
|
1429
2044
|
https,
|
|
1430
2045
|
http2,
|
|
2046
|
+
net,
|
|
2047
|
+
tls: tlsModule,
|
|
1431
2048
|
IncomingMessage,
|
|
1432
2049
|
ClientRequest,
|
|
1433
2050
|
};
|