@secure-exec/core 0.1.1-rc.1 → 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.
@@ -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(url, options = {}) {
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 response = await _networkFetchRaw(String(url), optionsJson);
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 || String(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(hostname)
165
- .then((result) => {
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 response = await _networkHttpRequestRaw(url, optionsJson);
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
- const head = typeof Buffer !== "undefined" ? Buffer.alloc(0) : new Uint8Array(0);
597
- this._emit("upgrade", res, this.socket, head);
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 result = await _networkHttpServerListenRaw(JSON.stringify({ serverId: this._serverId, port, hostname }));
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, request) {
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
  };