sandboxedjs 0.1.93 → 0.1.94

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/index.js CHANGED
@@ -19804,6 +19804,7 @@ var Op = {
19804
19804
  setsockopt: 1802,
19805
19805
  getsockopt: 1803,
19806
19806
  socket_close: 1804,
19807
+ resolve: 1805,
19807
19808
  spawn: 2048,
19808
19809
  waitpid: 2049,
19809
19810
  kill: 2050
@@ -20040,6 +20041,28 @@ var wheels_default = {
20040
20041
  patches: []
20041
20042
  }
20042
20043
  },
20044
+ {
20045
+ name: "asyncpg",
20046
+ version: "0.31.0",
20047
+ filename: "asyncpg-0.31.0-cp313-cp313-emscripten_5_0_6_wasm32.whl",
20048
+ sha256: "3eaf965cbd78d2314e8f12b9384e5642725323063aaff2708a8a4b3c01ef10ba",
20049
+ abiId: "sbxabi1-c2637d04695ad927",
20050
+ requires: [
20051
+ 'async_timeout>=4.0.3; python_version < "3.11.0"',
20052
+ 'gssapi; platform_system != "Windows" and extra == "gssauth"',
20053
+ 'sspilib; platform_system == "Windows" and extra == "gssauth"'
20054
+ ],
20055
+ provenance: {
20056
+ sourceTreeDigest: "a9ab0177da5c0c2d0ba635d1f5568687efbf8aefc08cd51cd086277184eba76f",
20057
+ recipeSha256: "fb9cf1a9fd35d8d63ac69dcf6a1671ceeebcb23f381a100b412058d75ff2eedc",
20058
+ buildTools: [
20059
+ "setuptools==84.0.0",
20060
+ "Cython==3.1.4"
20061
+ ],
20062
+ nativeDependencies: [],
20063
+ patches: []
20064
+ }
20065
+ },
20043
20066
  {
20044
20067
  name: "bcrypt",
20045
20068
  version: "5.0.0",
@@ -20061,18 +20084,20 @@ var wheels_default = {
20061
20084
  name: "brotli",
20062
20085
  version: "1.2.0",
20063
20086
  filename: "brotli-1.2.0-cp313-cp313-emscripten_5_0_6_wasm32.whl",
20064
- sha256: "b67d1b9169ba3e0790d04b8020adb1a58f0b89f8961aca360dca5f020de07a21",
20087
+ sha256: "d8b06cc04770a048b41956c9cc767e7332c49f0229e69744f4b1c2892b888b27",
20065
20088
  abiId: "sbxabi1-c2637d04695ad927",
20066
20089
  requires: [],
20067
20090
  provenance: {
20068
20091
  sourceTreeDigest: "7b563fa2e7f20edac204cc80004cae35c844b36a83758e5957184383332d1209",
20069
- recipeSha256: "09bf4f6072d637866a6b3c3deaec419dc3ff8a32a6f3b0813f57e06316bfcf39",
20092
+ recipeSha256: "1326c898e999feba536ff15f080fed9ac4b9cd14e0dbfee39a950decf19607ef",
20070
20093
  buildTools: [
20071
20094
  "setuptools==84.0.0",
20072
20095
  "pkgconfig==1.6.0"
20073
20096
  ],
20074
20097
  nativeDependencies: [],
20075
- patches: []
20098
+ patches: [
20099
+ "0001-meth-noargs-signatures.patch"
20100
+ ]
20076
20101
  }
20077
20102
  },
20078
20103
  {
@@ -20133,6 +20158,25 @@ var wheels_default = {
20133
20158
  patches: []
20134
20159
  }
20135
20160
  },
20161
+ {
20162
+ name: "google-crc32c",
20163
+ version: "1.8.0",
20164
+ filename: "google_crc32c-1.8.0-cp313-cp313-emscripten_5_0_6_wasm32.whl",
20165
+ sha256: "e64c21841598ea21121d7b7bb203e124d58c035db49ffce03631c55216c942cd",
20166
+ abiId: "sbxabi1-c2637d04695ad927",
20167
+ requires: [],
20168
+ provenance: {
20169
+ sourceTreeDigest: "fa277032a78adfccf6adcda32cadc4f6958a324979040c231e01f0963821cb75",
20170
+ recipeSha256: "843dea57727061bdfc7bcd27ac380d16a767effa538cbab6257e106939689f43",
20171
+ buildTools: [
20172
+ "setuptools==84.0.0"
20173
+ ],
20174
+ nativeDependencies: [
20175
+ "crc32c==1.1.2"
20176
+ ],
20177
+ patches: []
20178
+ }
20179
+ },
20136
20180
  {
20137
20181
  name: "httptools",
20138
20182
  version: "0.8.0",
@@ -20169,6 +20213,32 @@ var wheels_default = {
20169
20213
  patches: []
20170
20214
  }
20171
20215
  },
20216
+ {
20217
+ name: "lxml",
20218
+ version: "6.1.3",
20219
+ filename: "lxml-6.1.3-cp313-cp313-emscripten_5_0_6_wasm32.whl",
20220
+ sha256: "dc76d45314b806934bdaf35f9e4ae61e86e741e07bac53fae7e94f9d3dab9d4e",
20221
+ abiId: "sbxabi1-c2637d04695ad927",
20222
+ requires: [
20223
+ 'cssselect>=0.7; extra == "cssselect"',
20224
+ 'html5lib; extra == "html5"',
20225
+ 'BeautifulSoup4; extra == "htmlsoup"',
20226
+ 'lxml_html_clean; extra == "html-clean"'
20227
+ ],
20228
+ provenance: {
20229
+ sourceTreeDigest: "aedea680c1fe58ae9a124d81ea28f2316765e444e3809691854ddbf173c22bf5",
20230
+ recipeSha256: "aa62da97bb13eb5fa0a047f7c61ed93554f60142be9fcae64e3053e2a5673e9f",
20231
+ buildTools: [
20232
+ "setuptools==84.0.0",
20233
+ "Cython==3.1.4"
20234
+ ],
20235
+ nativeDependencies: [
20236
+ "xml2==2.13.8",
20237
+ "xslt==1.1.43"
20238
+ ],
20239
+ patches: []
20240
+ }
20241
+ },
20172
20242
  {
20173
20243
  name: "lz4",
20174
20244
  version: "4.4.5",
@@ -20270,6 +20340,113 @@ var wheels_default = {
20270
20340
  ]
20271
20341
  }
20272
20342
  },
20343
+ {
20344
+ name: "pandas",
20345
+ version: "3.0.5",
20346
+ filename: "pandas-3.0.5-cp313-cp313-emscripten_5_0_6_wasm32.whl",
20347
+ sha256: "ae36ad9b403b6fc28cb2bb79f54a38e1897ab43635c596fff2bce87f8148ac48",
20348
+ abiId: "sbxabi1-c2637d04695ad927",
20349
+ requires: [
20350
+ 'numpy>=1.26.0; python_version < "3.14"',
20351
+ 'numpy>=2.3.3; python_version >= "3.14"',
20352
+ "python-dateutil>=2.8.2",
20353
+ 'tzdata; sys_platform == "win32"',
20354
+ 'tzdata; sys_platform == "emscripten"',
20355
+ 'hypothesis>=6.116.0; extra == "test"',
20356
+ 'pytest<9.1,>=8.3.4; extra == "test"',
20357
+ 'pytest-xdist>=3.6.1; extra == "test"',
20358
+ 'pyarrow>=13.0.0; extra == "pyarrow"',
20359
+ 'bottleneck>=1.4.2; extra == "performance"',
20360
+ 'numba>=0.60.0; extra == "performance"',
20361
+ 'numexpr>=2.10.2; extra == "performance"',
20362
+ 'scipy>=1.14.1; extra == "computation"',
20363
+ 'xarray>=2024.10.0; extra == "computation"',
20364
+ 'fsspec>=2024.10.0; extra == "fss"',
20365
+ 's3fs>=2024.10.0; extra == "aws"',
20366
+ 'gcsfs>=2024.10.0; extra == "gcp"',
20367
+ 'odfpy>=1.4.1; extra == "excel"',
20368
+ 'openpyxl>=3.1.5; extra == "excel"',
20369
+ 'python-calamine>=0.3.0; extra == "excel"',
20370
+ 'pyxlsb>=1.0.10; extra == "excel"',
20371
+ 'xlrd>=2.0.1; extra == "excel"',
20372
+ 'xlsxwriter>=3.2.0; extra == "excel"',
20373
+ 'pyarrow>=13.0.0; extra == "parquet"',
20374
+ 'pyarrow>=13.0.0; extra == "feather"',
20375
+ 'pyiceberg>=0.8.1; extra == "iceberg"',
20376
+ 'tables>=3.10.1; extra == "hdf5"',
20377
+ 'pyreadstat>=1.2.8; extra == "spss"',
20378
+ 'SQLAlchemy>=2.0.36; extra == "postgresql"',
20379
+ 'psycopg2>=2.9.10; extra == "postgresql"',
20380
+ 'adbc-driver-postgresql>=1.2.0; extra == "postgresql"',
20381
+ 'SQLAlchemy>=2.0.36; extra == "mysql"',
20382
+ 'pymysql>=1.1.1; extra == "mysql"',
20383
+ 'SQLAlchemy>=2.0.36; extra == "sql-other"',
20384
+ 'adbc-driver-postgresql>=1.2.0; extra == "sql-other"',
20385
+ 'adbc-driver-sqlite>=1.2.0; extra == "sql-other"',
20386
+ 'beautifulsoup4>=4.12.3; extra == "html"',
20387
+ 'html5lib>=1.1; extra == "html"',
20388
+ 'lxml>=5.3.0; extra == "html"',
20389
+ 'lxml>=5.3.0; extra == "xml"',
20390
+ 'matplotlib>=3.9.3; extra == "plot"',
20391
+ 'jinja2>=3.1.5; extra == "output-formatting"',
20392
+ 'tabulate>=0.9.0; extra == "output-formatting"',
20393
+ 'PyQt5>=5.15.9; extra == "clipboard"',
20394
+ 'qtpy>=2.4.2; extra == "clipboard"',
20395
+ 'zstandard>=0.23.0; extra == "compression"',
20396
+ 'pytz>=2020.1; extra == "timezone"',
20397
+ 'adbc-driver-postgresql>=1.2.0; extra == "all"',
20398
+ 'adbc-driver-sqlite>=1.2.0; extra == "all"',
20399
+ 'beautifulsoup4>=4.12.3; extra == "all"',
20400
+ 'bottleneck>=1.4.2; extra == "all"',
20401
+ 'fastparquet>=2024.11.0; extra == "all"',
20402
+ 'fsspec>=2024.10.0; extra == "all"',
20403
+ 'gcsfs>=2024.10.0; extra == "all"',
20404
+ 'html5lib>=1.1; extra == "all"',
20405
+ 'hypothesis>=6.116.0; extra == "all"',
20406
+ 'jinja2>=3.1.5; extra == "all"',
20407
+ 'lxml>=5.3.0; extra == "all"',
20408
+ 'matplotlib>=3.9.3; extra == "all"',
20409
+ 'numba>=0.60.0; extra == "all"',
20410
+ 'numexpr>=2.10.2; extra == "all"',
20411
+ 'odfpy>=1.4.1; extra == "all"',
20412
+ 'openpyxl>=3.1.5; extra == "all"',
20413
+ 'psycopg2>=2.9.10; extra == "all"',
20414
+ 'pyarrow>=13.0.0; extra == "all"',
20415
+ 'pyiceberg>=0.8.1; extra == "all"',
20416
+ 'pymysql>=1.1.1; extra == "all"',
20417
+ 'PyQt5>=5.15.9; extra == "all"',
20418
+ 'pyreadstat>=1.2.8; extra == "all"',
20419
+ 'pytest>=8.3.4; extra == "all"',
20420
+ 'pytest-xdist>=3.6.1; extra == "all"',
20421
+ 'python-calamine>=0.3.0; extra == "all"',
20422
+ 'pytz>=2020.1; extra == "all"',
20423
+ 'pyxlsb>=1.0.10; extra == "all"',
20424
+ 'qtpy>=2.4.2; extra == "all"',
20425
+ 'scipy>=1.14.1; extra == "all"',
20426
+ 's3fs>=2024.10.0; extra == "all"',
20427
+ 'SQLAlchemy>=2.0.36; extra == "all"',
20428
+ 'tables>=3.10.1; extra == "all"',
20429
+ 'tabulate>=0.9.0; extra == "all"',
20430
+ 'xarray>=2024.10.0; extra == "all"',
20431
+ 'xlrd>=2.0.1; extra == "all"',
20432
+ 'xlsxwriter>=3.2.0; extra == "all"',
20433
+ 'zstandard>=0.23.0; extra == "all"'
20434
+ ],
20435
+ provenance: {
20436
+ sourceTreeDigest: "373f08151b5a209a84dd35b086316ef11a0d4030150457c2bfdab38305903d54",
20437
+ recipeSha256: "56976ed58b17053971334a62b503d5397b8ff739911fb596a116df23aeccabb0",
20438
+ buildTools: [
20439
+ "setuptools==84.0.0",
20440
+ "Cython==3.1.4",
20441
+ "meson-python==0.21.0",
20442
+ "meson==1.12.0",
20443
+ "ninja==1.13.2",
20444
+ "versioneer==0.29"
20445
+ ],
20446
+ nativeDependencies: [],
20447
+ patches: []
20448
+ }
20449
+ },
20273
20450
  {
20274
20451
  name: "pillow",
20275
20452
  version: "12.3.0",
@@ -20374,6 +20551,31 @@ var wheels_default = {
20374
20551
  patches: []
20375
20552
  }
20376
20553
  },
20554
+ {
20555
+ name: "rapidfuzz",
20556
+ version: "3.14.6",
20557
+ filename: "rapidfuzz-3.14.6-cp313-cp313-emscripten_5_0_6_wasm32.whl",
20558
+ sha256: "b9ed2ea058350925beda765364c0f382bf9a7c08b3bec33840795178de021d32",
20559
+ abiId: "sbxabi1-c2637d04695ad927",
20560
+ requires: [
20561
+ 'numpy; extra == "all"'
20562
+ ],
20563
+ provenance: {
20564
+ sourceTreeDigest: "ae2b2bb95f7aba2babbbe668b68d48b5347ca923e6508b76d24228cd13ac7cd3",
20565
+ recipeSha256: "46954814226fff070aa6771d2f09d32a96b718ce8ef7a9f9404375850f7d1f77",
20566
+ buildTools: [
20567
+ "setuptools==84.0.0",
20568
+ "scikit-build-core==1.0.3",
20569
+ "cmake==4.4.3",
20570
+ "ninja==1.13.2",
20571
+ "Cython==3.1.4",
20572
+ "pathspec==1.1.1",
20573
+ "packaging==26.3"
20574
+ ],
20575
+ nativeDependencies: [],
20576
+ patches: []
20577
+ }
20578
+ },
20377
20579
  {
20378
20580
  name: "regex",
20379
20581
  version: "2026.9.10",
@@ -20739,7 +20941,7 @@ var wheels_default = {
20739
20941
 
20740
20942
  // package.json
20741
20943
  var package_default = {
20742
- version: "0.1.93"};
20944
+ version: "0.1.94"};
20743
20945
 
20744
20946
  // src/python/extension-abi.ts
20745
20947
  var EXTENSION_ABI = {
@@ -21777,6 +21979,8 @@ var VirtualTcpSocket = class _VirtualTcpSocket {
21777
21979
  network;
21778
21980
  listenerValue = null;
21779
21981
  connectionValue = null;
21982
+ /** Held so closing the guest's socket closes the host's, not just the queues. */
21983
+ hostConnection = null;
21780
21984
  boundPort = 0;
21781
21985
  boundAddress = "0.0.0.0";
21782
21986
  bind(address, port) {
@@ -21794,13 +21998,35 @@ var VirtualTcpSocket = class _VirtualTcpSocket {
21794
21998
  }
21795
21999
  this.listenerValue = this.network.listen(this.boundPort, this.boundAddress, backlog);
21796
22000
  }
21797
- connect(port, address = "127.0.0.1") {
22001
+ /**
22002
+ * Connect, in the container or out of it.
22003
+ *
22004
+ * The destination used to be discarded, which quietly turned every connect
22005
+ * into a loopback connect: a guest dialling example.com:443 would reach a
22006
+ * container server listening on 443 and believe it had reached the internet.
22007
+ * Loopback is still routed inside the container; anything else goes to the
22008
+ * host's outbound stack, which owns the names and the real sockets, and
22009
+ * which refuses with a reason when the host has no way out.
22010
+ */
22011
+ async connect(port, address = "127.0.0.1") {
21798
22012
  if (this.listenerValue || this.connectionValue) throw new PosixError(Errno.EISCONN);
21799
22013
  if (!isLoopback(address)) {
21800
- throw new PosixError(
21801
- Errno.ENETUNREACH,
21802
- `cannot reach ${address}:${port}: outbound network access is not available through raw sockets in this container; only loopback is routed`
21803
- );
22014
+ const outbound = this.network.outbound;
22015
+ if (!outbound) {
22016
+ throw new PosixError(
22017
+ Errno.ENETUNREACH,
22018
+ `cannot reach ${address}:${port}: outbound network access is not available through raw sockets in this container; only loopback is routed`
22019
+ );
22020
+ }
22021
+ const dialled = await outbound.connect(address, port, {
22022
+ address: this.boundAddress,
22023
+ port: this.boundPort
22024
+ });
22025
+ this.connectionValue = dialled.connection;
22026
+ this.hostConnection = dialled.host;
22027
+ this.boundPort = dialled.connection.local.port;
22028
+ this.boundAddress = dialled.connection.local.address;
22029
+ return;
21804
22030
  }
21805
22031
  this.connectionValue = this.network.connect(port, this.boundPort, this.boundAddress);
21806
22032
  this.boundPort = this.connectionValue.local.port;
@@ -21851,6 +22077,8 @@ var VirtualTcpSocket = class _VirtualTcpSocket {
21851
22077
  close() {
21852
22078
  if (this.listenerValue) this.network.close(this.listenerValue);
21853
22079
  this.connectionValue?.close();
22080
+ this.hostConnection?.close();
22081
+ this.hostConnection = null;
21854
22082
  this.listenerValue = null;
21855
22083
  this.connectionValue = null;
21856
22084
  }
@@ -21915,7 +22143,7 @@ var SocketDescription = class _SocketDescription {
21915
22143
  this.resource.listen(backlog);
21916
22144
  }
21917
22145
  connect(address, port) {
21918
- this.resource.connect(port, address);
22146
+ return this.resource.connect(port, address);
21919
22147
  }
21920
22148
  accept() {
21921
22149
  return new _SocketDescription(this.resource.accept(), this.flags);
@@ -21927,7 +22155,11 @@ var SocketDescription = class _SocketDescription {
21927
22155
  this.options.set(`${level}:${option}`, value.slice());
21928
22156
  }
21929
22157
  getOption(level, option) {
21930
- return this.options.get(`${level}:${option}`)?.slice() ?? new Uint8Array(4);
22158
+ const stored = this.options.get(`${level}:${option}`);
22159
+ if (stored) return stored.slice();
22160
+ if (level === SOL_SOCKET && option === SO_TYPE) return int32(SOCK_STREAM);
22161
+ if (level === SOL_SOCKET && option === SO_ERROR) return int32(0);
22162
+ return new Uint8Array(4);
21931
22163
  }
21932
22164
  localAddress() {
21933
22165
  return this.resource.local;
@@ -21938,6 +22170,15 @@ var SocketDescription = class _SocketDescription {
21938
22170
  return peer;
21939
22171
  }
21940
22172
  };
22173
+ var SOL_SOCKET = 1;
22174
+ var SO_ERROR = 4;
22175
+ var SO_TYPE = 3;
22176
+ var SOCK_STREAM = 1;
22177
+ function int32(value) {
22178
+ const bytes2 = new Uint8Array(4);
22179
+ new DataView(bytes2.buffer).setInt32(0, value, true);
22180
+ return bytes2;
22181
+ }
21941
22182
  function isLoopback(address) {
21942
22183
  if (address === "" || address === "localhost" || address === "0.0.0.0") return true;
21943
22184
  if (address === "::1" || address === "::" || address === "[::1]") return true;
@@ -21949,6 +22190,18 @@ var VirtualTcpNetwork = class {
21949
22190
  }
21950
22191
  occupied;
21951
22192
  listeners = /* @__PURE__ */ new Map();
22193
+ /** The host's outbound stack, when this host can open sockets at all. */
22194
+ outbound = null;
22195
+ /**
22196
+ * Ports held by in-container servers that are not sockets.
22197
+ *
22198
+ * A JavaScript HTTP server in the container is registered with the request
22199
+ * router and never binds one of these sockets, so a guest connecting to its
22200
+ * port found nothing listening. Before sockets could leave the container
22201
+ * this did not arise -- every guest client went out through the egress --
22202
+ * and afterwards `urllib` dialling a container server got ECONNREFUSED.
22203
+ */
22204
+ loopbackHttp = null;
21952
22205
  listen(port, address = "0.0.0.0", backlog = 128) {
21953
22206
  if (this.listeners.has(port) || this.occupied?.(port)) throw new PosixError(Errno.EADDRINUSE, `port ${port} is already in use`);
21954
22207
  const listener = new VirtualTcpListener(port, address, backlog);
@@ -21962,7 +22215,11 @@ var VirtualTcpNetwork = class {
21962
22215
  }
21963
22216
  connect(port, localPort = 0, localAddress = "127.0.0.1") {
21964
22217
  const listener = this.listeners.get(port);
21965
- if (!listener || listener.isClosed) throw new PosixError(Errno.ECONNREFUSED, `port ${port} is not listening`);
22218
+ if (!listener || listener.isClosed) {
22219
+ const bridged = this.connectToNonSocketServer(port, localPort, localAddress);
22220
+ if (bridged) return bridged;
22221
+ throw new PosixError(Errno.ECONNREFUSED, `port ${port} is not listening`);
22222
+ }
21966
22223
  const client = new VirtualTcpConnection(
21967
22224
  { address: localAddress, port: localPort },
21968
22225
  { address: listener.address, port }
@@ -21976,6 +22233,26 @@ var VirtualTcpNetwork = class {
21976
22233
  listener.enqueue(server);
21977
22234
  return client;
21978
22235
  }
22236
+ /** A connection to a port an in-container server holds without a socket. */
22237
+ connectToNonSocketServer(port, localPort, localAddress) {
22238
+ if (!this.loopbackHttp) return null;
22239
+ const client = new VirtualTcpConnection(
22240
+ { address: localAddress, port: localPort },
22241
+ { address: "127.0.0.1", port }
22242
+ );
22243
+ const server = new VirtualTcpConnection(
22244
+ { address: "127.0.0.1", port },
22245
+ { address: localAddress, port: localPort }
22246
+ );
22247
+ client.pairWith(server);
22248
+ server.pairWith(client);
22249
+ if (!this.loopbackHttp(port, server)) {
22250
+ client.close();
22251
+ server.close();
22252
+ return null;
22253
+ }
22254
+ return client;
22255
+ }
21979
22256
  hasListener(port) {
21980
22257
  return this.listeners.has(port);
21981
22258
  }
@@ -21993,7 +22270,7 @@ var POLLIN = 1;
21993
22270
  var POLLOUT = 4;
21994
22271
  var POLLHUP = 16;
21995
22272
  var AF_INET = 2;
21996
- var SOCK_STREAM = 1;
22273
+ var SOCK_STREAM2 = 1;
21997
22274
  var IPPROTO_TCP = 6;
21998
22275
  function createHostAbiServer(proc) {
21999
22276
  const files = new FileService(proc.vfs, proc.cred);
@@ -22284,7 +22561,7 @@ function createHostAbiServer(proc) {
22284
22561
  const family = r.u32();
22285
22562
  const type = r.u32();
22286
22563
  const protocol = r.i32();
22287
- if (family !== AF_INET || (type & 15) !== SOCK_STREAM) throw new PosixError(Errno.EAFNOSUPPORT);
22564
+ if (family !== AF_INET || (type & 15) !== SOCK_STREAM2) throw new PosixError(Errno.EAFNOSUPPORT);
22288
22565
  if (protocol !== 0 && protocol !== IPPROTO_TCP) throw new PosixError(Errno.EPROTONOSUPPORT);
22289
22566
  if (!proc.sockets) throw new PosixError(Errno.ENOSYS);
22290
22567
  return { status: proc.table.add(new SocketDescription(new VirtualTcpSocket(proc.sockets))) };
@@ -22308,9 +22585,15 @@ function createHostAbiServer(proc) {
22308
22585
  }
22309
22586
  case Op.connect: {
22310
22587
  const description = socketDescription(proc.table.get(r.i32()));
22311
- description.connect(r.string(), r.u32());
22588
+ await description.connect(r.string(), r.u32());
22312
22589
  return { status: 0 };
22313
22590
  }
22591
+ case Op.resolve: {
22592
+ const sockets = proc.sockets;
22593
+ if (!sockets?.outbound) throw new PosixError(Errno.ENOSYS);
22594
+ const address = sockets.outbound.resolve(r.string());
22595
+ return { status: 0, payload: new Writer().string(address).finish() };
22596
+ }
22314
22597
  case Op.send: {
22315
22598
  const description = socketDescription(proc.table.get(r.i32()));
22316
22599
  return { status: await writeBlocking(description, r.bytes32()) };
@@ -22868,6 +23151,32 @@ async function serve(ctx, connection) {
22868
23151
  connection.close();
22869
23152
  }
22870
23153
  }
23154
+ async function serveHttpOverConnection(connection, handle) {
23155
+ try {
23156
+ const request = await readRequest(connection);
23157
+ if (!request) {
23158
+ writeResponse(connection, 400, "Bad Request", {}, new TextEncoder().encode("malformed request\n"));
23159
+ return;
23160
+ }
23161
+ const result = await handle(request);
23162
+ writeResponse(connection, result.status, result.statusText, result.headers, result.body);
23163
+ } catch (error) {
23164
+ const message = error instanceof Error ? error.message : String(error);
23165
+ try {
23166
+ writeResponse(
23167
+ connection,
23168
+ 502,
23169
+ "Bad Gateway",
23170
+ { "x-sbx-error": "1" },
23171
+ new TextEncoder().encode(`${message}
23172
+ `)
23173
+ );
23174
+ } catch {
23175
+ }
23176
+ } finally {
23177
+ connection.close();
23178
+ }
23179
+ }
22871
23180
  var listeners = /* @__PURE__ */ new WeakMap();
22872
23181
  function ensureHttpEgress(ctx) {
22873
23182
  const sockets = ctx.kernel.pod.sockets;
@@ -23398,7 +23707,7 @@ async function resolve2(options) {
23398
23707
  if (viable.length === 0) {
23399
23708
  const wanted = [...unusable.get(name)?.versions ?? []].filter((version) => clauses.every((s) => compareVersions(version, s.version, s.op)));
23400
23709
  const nothingUsable = all.length === 0 || wanted.length > 0;
23401
- const error2 = new ResolutionError(
23710
+ const error = new ResolutionError(
23402
23711
  nothingUsable ? `no distribution of ${name}${all.length > 0 ? ` ${newest(wanted)}` : ""} is usable by this runtime (it needs a pure-Python wheel or one tagged ${WHEEL_TAG})` : `no version of ${name} satisfies every requirement`,
23403
23712
  explain(name, next.constraints, null, all),
23404
23713
  nothingUsable ? {
@@ -23410,8 +23719,8 @@ async function resolve2(options) {
23410
23719
  sourceAvailable: unusable.get(name)?.sourceAvailable ?? false
23411
23720
  } : { reason: "conflicting-requirements", package: name }
23412
23721
  );
23413
- nogoods.set(signature, error2);
23414
- throw error2;
23722
+ nogoods.set(signature, error);
23723
+ throw error;
23415
23724
  }
23416
23725
  let failure2 = null;
23417
23726
  const seenVersions = /* @__PURE__ */ new Set();
@@ -23423,9 +23732,9 @@ async function resolve2(options) {
23423
23732
  let requires;
23424
23733
  try {
23425
23734
  requires = await requiresFor(candidate);
23426
- } catch (error2) {
23735
+ } catch (error) {
23427
23736
  failure2 = new ResolutionError(
23428
- `could not read the dependencies of ${name} ${candidate.version}: ${error2.message}`
23737
+ `could not read the dependencies of ${name} ${candidate.version}: ${error.message}`
23429
23738
  );
23430
23739
  continue;
23431
23740
  }
@@ -23449,15 +23758,13 @@ async function resolve2(options) {
23449
23758
  chosen.set(name, { ...candidate, requires });
23450
23759
  try {
23451
23760
  return await search([...edges, ...rest], { chosen, constraints: next.constraints });
23452
- } catch (error2) {
23453
- if (!(error2 instanceof ResolutionError)) throw error2;
23454
- if (/cancelled|budget/.test(error2.message)) throw error2;
23455
- failure2 = error2;
23761
+ } catch (error) {
23762
+ if (!(error instanceof ResolutionError)) throw error;
23763
+ if (/cancelled|budget/.test(error.message)) throw error;
23764
+ failure2 = error;
23456
23765
  }
23457
23766
  }
23458
- const error = failure2 ?? new ResolutionError(`could not resolve ${name}`);
23459
- nogoods.set(signature, error);
23460
- throw error;
23767
+ throw failure2 ?? new ResolutionError(`could not resolve ${name}`);
23461
23768
  };
23462
23769
  const seeded = /* @__PURE__ */ new Map();
23463
23770
  for (const root of roots) {
@@ -23963,8 +24270,18 @@ async function runOwnedPython(ctx, argv) {
23963
24270
  if (!manifest) throw new Error("no Python runtime manifest is configured");
23964
24271
  const mounts = containerMounts(ctx);
23965
24272
  const env2 = withSitePackages(ctx.env);
24273
+ await ensureTrustStore(ctx);
23966
24274
  const egressPort = ensureHttpEgress(ctx);
23967
24275
  if (egressPort > 0) env2[EGRESS_ENV] = `127.0.0.1:${egressPort}`;
24276
+ try {
24277
+ if (ctx.vfs.stat(CA_BUNDLE_PATH, { cred: ctx.cred }).size > 0) {
24278
+ env2.SSL_CERT_FILE = CA_BUNDLE_PATH;
24279
+ env2.SSL_CERT_DIR = "/etc/ssl/certs";
24280
+ env2.REQUESTS_CA_BUNDLE = CA_BUNDLE_PATH;
24281
+ }
24282
+ } catch {
24283
+ }
24284
+ if (ctx.kernel.pod.sockets?.outbound) env2.SBX_OUTBOUND_SOCKETS = "1";
23968
24285
  env2.SBX_SERIAL_HOST_CALLS = "1";
23969
24286
  const inherit = claimInheritance(env2);
23970
24287
  const process2 = await startPythonProcess({
@@ -24005,6 +24322,200 @@ function isTerminal(ctx) {
24005
24322
  const stdin = ctx.stdin;
24006
24323
  return Boolean(stdin.isTTY || stdin.interactive);
24007
24324
  }
24325
+ var TLS_MODULE_SOURCE = `"""TLS over a SandboxedJs socket, using this interpreter's own OpenSSL.
24326
+
24327
+ CPython's \`ssl\` cannot wrap one of these sockets directly: \`_ssl\` wants a real
24328
+ \`_socket.socket\` whose descriptor OpenSSL can read and write, and a socket here
24329
+ is a host resource reached through the SandboxedJs ABI. But OpenSSL does not
24330
+ need a descriptor -- \`SSLContext.wrap_bio\` drives the same protocol over a pair
24331
+ of memory buffers, which is how asyncio has always done TLS. So the handshake
24332
+ and every record are performed here, in the container, and only the ciphertext
24333
+ crosses to the host. Certificates are verified by this interpreter against the
24334
+ trust store the host writes into the image, and \`getpeercert\` answers.
24335
+ """
24336
+
24337
+ import io
24338
+ import socket as _socket_module
24339
+ import ssl as _ssl
24340
+
24341
+
24342
+ def wrappable(sock):
24343
+ """Whether this socket is one of the container's own."""
24344
+ target = getattr(sock, "_sock", sock)
24345
+ return hasattr(target, "_fd")
24346
+
24347
+
24348
+ class SbxTlsSocket:
24349
+ """What \`SSLContext.wrap_socket\` returns for a container socket."""
24350
+
24351
+ def __init__(self, context, sock, server_hostname, suppress_ragged_eofs=True):
24352
+ self._sock = sock
24353
+ self.context = context
24354
+ self.server_hostname = server_hostname
24355
+ self.suppress_ragged_eofs = suppress_ragged_eofs
24356
+ self._incoming = _ssl.MemoryBIO()
24357
+ self._outgoing = _ssl.MemoryBIO()
24358
+ self._closed = False
24359
+ self._tls = context.wrap_bio(
24360
+ self._incoming, self._outgoing,
24361
+ server_side=False,
24362
+ server_hostname=server_hostname,
24363
+ )
24364
+
24365
+ # -- moving bytes between OpenSSL and the socket ------------------------
24366
+ def _flush(self):
24367
+ data = self._outgoing.read()
24368
+ if data:
24369
+ self._sock.sendall(data)
24370
+
24371
+ def _feed(self):
24372
+ chunk = self._sock.recv(32768)
24373
+ if not chunk:
24374
+ self._incoming.write_eof()
24375
+ return False
24376
+ self._incoming.write(chunk)
24377
+ return True
24378
+
24379
+ def _run(self, operation):
24380
+ """Drive one OpenSSL operation, pumping the socket as it asks."""
24381
+ while True:
24382
+ try:
24383
+ result = operation()
24384
+ except _ssl.SSLWantReadError:
24385
+ self._flush()
24386
+ if not self._feed():
24387
+ raise
24388
+ continue
24389
+ except _ssl.SSLWantWriteError:
24390
+ self._flush()
24391
+ continue
24392
+ self._flush()
24393
+ return result
24394
+
24395
+ # -- the handshake ------------------------------------------------------
24396
+ def do_handshake(self, block=False):
24397
+ self._run(self._tls.do_handshake)
24398
+
24399
+ # -- the socket ---------------------------------------------------------
24400
+ def recv(self, length=1024, flags=0):
24401
+ if flags:
24402
+ raise OSError("socket flags are not supported")
24403
+ try:
24404
+ return self._run(lambda: self._tls.read(length))
24405
+ except _ssl.SSLZeroReturnError:
24406
+ return b""
24407
+ except _ssl.SSLWantReadError:
24408
+ # The peer closed without close_notify. Ragged, and normal enough
24409
+ # on the public internet that clients ask to tolerate it.
24410
+ if self.suppress_ragged_eofs:
24411
+ return b""
24412
+ raise
24413
+
24414
+ def recv_into(self, buffer, nbytes=0, flags=0):
24415
+ data = self.recv(nbytes or len(buffer), flags)
24416
+ buffer[:len(data)] = data
24417
+ return len(data)
24418
+
24419
+ def read(self, length=1024, buffer=None):
24420
+ if buffer is not None:
24421
+ return self.recv_into(buffer, length)
24422
+ return self.recv(length)
24423
+
24424
+ def send(self, data, flags=0):
24425
+ if flags:
24426
+ raise OSError("socket flags are not supported")
24427
+ return self._run(lambda: self._tls.write(data))
24428
+
24429
+ def sendall(self, data, flags=0):
24430
+ view = memoryview(data)
24431
+ while view:
24432
+ view = view[self.send(view, flags):]
24433
+
24434
+ def write(self, data):
24435
+ return self.send(data)
24436
+
24437
+ _io_refs = 0
24438
+
24439
+ def _decref_socketios(self):
24440
+ # socket.SocketIO calls this when a file object built on this socket is
24441
+ # closed; without it http.client failed with "no attribute
24442
+ # _decref_socketios" the moment it read a response.
24443
+ if self._io_refs > 0:
24444
+ self._io_refs -= 1
24445
+
24446
+ def makefile(self, mode="r", buffering=None, *, encoding=None, errors=None, newline=None):
24447
+ # http.client and everything built on it read the response through a
24448
+ # file object, so this has to work like socket.makefile does.
24449
+ raw = _socket_module.SocketIO(self, "rw" if "w" in mode else "r")
24450
+ self._io_refs += 1
24451
+ buffer = io.BufferedRWPair(raw, raw) if "w" in mode and "r" in mode else (
24452
+ io.BufferedWriter(raw) if "w" in mode else io.BufferedReader(raw)
24453
+ )
24454
+ if "b" in mode:
24455
+ return buffer
24456
+ text = io.TextIOWrapper(buffer, encoding, errors, newline)
24457
+ text.mode = mode
24458
+ return text
24459
+
24460
+ def settimeout(self, value): return self._sock.settimeout(value)
24461
+ def gettimeout(self): return self._sock.gettimeout()
24462
+ def setblocking(self, flag): return self._sock.setblocking(flag)
24463
+ def getblocking(self): return self._sock.getblocking()
24464
+ def setsockopt(self, *args): return self._sock.setsockopt(*args)
24465
+ def getsockopt(self, *args): return self._sock.getsockopt(*args)
24466
+ def fileno(self): return self._sock.fileno()
24467
+ def getpeername(self): return self._sock.getpeername()
24468
+ def getsockname(self): return self._sock.getsockname()
24469
+ def detach(self): return self._sock.detach()
24470
+
24471
+ def shutdown(self, how):
24472
+ return self._sock.shutdown(how)
24473
+
24474
+ def unwrap(self):
24475
+ self._run(self._tls.unwrap)
24476
+ return self._sock
24477
+
24478
+ def close(self):
24479
+ if not self._closed:
24480
+ self._closed = True
24481
+ self._sock.close()
24482
+
24483
+ def __enter__(self): return self
24484
+ def __exit__(self, *args): self.close()
24485
+
24486
+ @property
24487
+ def family(self): return getattr(self._sock, "family", _socket_module.AF_INET)
24488
+ @property
24489
+ def type(self): return getattr(self._sock, "type", _socket_module.SOCK_STREAM)
24490
+ @property
24491
+ def proto(self): return getattr(self._sock, "proto", 0)
24492
+ @property
24493
+ def session(self): return self._tls.session
24494
+ @session.setter
24495
+ def session(self, value): self._tls.session = value
24496
+ @property
24497
+ def session_reused(self): return self._tls.session_reused
24498
+
24499
+ # -- what OpenSSL knows about this connection ---------------------------
24500
+ def getpeercert(self, binary_form=False): return self._tls.getpeercert(binary_form)
24501
+ def get_unverified_chain(self): return self._tls.get_unverified_chain()
24502
+ def get_verified_chain(self): return self._tls.get_verified_chain()
24503
+ def cipher(self): return self._tls.cipher()
24504
+ def shared_ciphers(self): return self._tls.shared_ciphers()
24505
+ def compression(self): return self._tls.compression()
24506
+ def selected_alpn_protocol(self): return self._tls.selected_alpn_protocol()
24507
+ def selected_npn_protocol(self): return None
24508
+ def version(self): return self._tls.version()
24509
+ def pending(self): return self._tls.pending()
24510
+ def get_channel_binding(self, cb_type="tls-unique"):
24511
+ return self._tls.get_channel_binding(cb_type)
24512
+
24513
+
24514
+ def wrap(context, sock, server_hostname, suppress_ragged_eofs=True):
24515
+ wrapped = SbxTlsSocket(context, sock, server_hostname, suppress_ragged_eofs)
24516
+ wrapped.do_handshake()
24517
+ return wrapped
24518
+ `;
24008
24519
  var SHIM_HEADER = "# generated by sandboxedjs - safe to delete or replace\n";
24009
24520
  var SHIM_SOURCE = `"""Rebuild os.system and os.popen on the container's process table."""
24010
24521
  import io, os, subprocess, sys
@@ -24141,7 +24652,51 @@ if sys.platform in ("emscripten", "wasi"):
24141
24652
  sys.meta_path.insert(0, _SbxPyodideFetchHook())
24142
24653
 
24143
24654
 
24144
- _egress = os.environ.get("SBX_HTTP_EGRESS")
24655
+ # The egress shims exist for a host that cannot open sockets. Where the host
24656
+ # can, the guest's own clients are used instead: real streaming, real TLS, and
24657
+ # whatever protocol the library speaks rather than the subset HTTP-over-egress
24658
+ # allows. Both worlds are supported, and which one this is comes from the host
24659
+ # rather than from probing.
24660
+ if os.environ.get("SBX_OUTBOUND_SOCKETS") == "1":
24661
+
24662
+ class _SbxTruststoreHook:
24663
+ """truststore verifies the peer's chain itself, against the operating
24664
+ system's trust store. In this container the handshake happens on the
24665
+ host, which verified the chain against exactly that store, and the
24666
+ chain is not carried back -- so truststore's own check cannot run and
24667
+ httpx2 failed with "no attribute get_unverified_chain". Its context is
24668
+ therefore the standard one: same verification, done once, where the
24669
+ connection is."""
24670
+
24671
+ def find_spec(self, name, path=None, target=None):
24672
+ if name != "truststore":
24673
+ return None
24674
+ import importlib.util
24675
+
24676
+ class _Loader:
24677
+ def create_module(self, spec):
24678
+ return None
24679
+
24680
+ def exec_module(self, module):
24681
+ import ssl as _ssl
24682
+ module.SSLContext = _ssl.SSLContext
24683
+
24684
+ def inject_into_ssl():
24685
+ pass
24686
+
24687
+ def extract_from_ssl():
24688
+ pass
24689
+
24690
+ module.inject_into_ssl = inject_into_ssl
24691
+ module.extract_from_ssl = extract_from_ssl
24692
+ module.__version__ = "sandboxedjs"
24693
+
24694
+ return importlib.util.spec_from_loader(name, _Loader(), is_package=True)
24695
+
24696
+ sys.meta_path.insert(0, _SbxTruststoreHook())
24697
+
24698
+
24699
+ _egress = None if os.environ.get("SBX_OUTBOUND_SOCKETS") == "1" else os.environ.get("SBX_HTTP_EGRESS")
24145
24700
  if _egress:
24146
24701
  import http.client
24147
24702
  import urllib.request
@@ -24539,10 +25094,86 @@ if os.environ.get("SBX_SERIAL_HOST_CALLS") == "1":
24539
25094
  return spec
24540
25095
 
24541
25096
  sys.meta_path.insert(0, _SbxAnyioThreadHook())
25097
+
25098
+ class _SbxLoopResolverHook:
25099
+ """asyncio resolves names on a worker thread, which cannot run here.
25100
+
25101
+ loop.getaddrinfo hands the call to a ThreadPoolExecutor and awaits
25102
+ it -- the deadlock described above -- so every asyncio client that
25103
+ connects by name hung: aiohttp never got past its first request. In
25104
+ this container resolution is a host call that answers immediately, so
25105
+ it is done on the loop thread instead of being handed away."""
25106
+
25107
+ def find_spec(self, name, path=None, target=None):
25108
+ if name != "asyncio.base_events":
25109
+ return None
25110
+ import importlib.machinery
25111
+ spec = importlib.machinery.PathFinder.find_spec(name, path)
25112
+ if spec is None or spec.loader is None:
25113
+ return spec
25114
+ load = spec.loader.exec_module
25115
+
25116
+ def exec_module(module):
25117
+ load(module)
25118
+ import socket as _socket
25119
+
25120
+ async def getaddrinfo(self, host, port, *, family=0, type=0, proto=0, flags=0):
25121
+ return _socket.getaddrinfo(host, port, family, type, proto, flags)
25122
+
25123
+ async def getnameinfo(self, sockaddr, flags=0):
25124
+ return _socket.getnameinfo(sockaddr, flags)
25125
+
25126
+ module.BaseEventLoop.getaddrinfo = getaddrinfo
25127
+ module.BaseEventLoop.getnameinfo = getnameinfo
25128
+
25129
+ spec.loader.exec_module = exec_module
25130
+ return spec
25131
+
25132
+ sys.meta_path.insert(0, _SbxLoopResolverHook())
25133
+
25134
+ def _sbx_patch_loop_resolver(module):
25135
+ """Also patch the module if it is already imported.
25136
+
25137
+ This file imports asyncio itself, a few lines above, so by the time the
25138
+ hook is installed asyncio.base_events is usually already in
25139
+ sys.modules and the hook would never see it -- which is why aiohttp
25140
+ still hung after the hook existed."""
25141
+ import socket as _socket
25142
+
25143
+ async def getaddrinfo(self, host, port, *, family=0, type=0, proto=0, flags=0):
25144
+ return _socket.getaddrinfo(host, port, family, type, proto, flags)
25145
+
25146
+ async def getnameinfo(self, sockaddr, flags=0):
25147
+ return _socket.getnameinfo(sockaddr, flags)
25148
+
25149
+ module.BaseEventLoop.getaddrinfo = getaddrinfo
25150
+ module.BaseEventLoop.getnameinfo = getnameinfo
25151
+
25152
+ _loaded_base_events = sys.modules.get("asyncio.base_events")
25153
+ if _loaded_base_events is not None:
25154
+ _sbx_patch_loop_resolver(_loaded_base_events)
24542
25155
  `;
25156
+ var CA_BUNDLE_PATH = "/etc/ssl/certs/ca-certificates.crt";
25157
+ async function ensureTrustStore(ctx) {
25158
+ try {
25159
+ if (ctx.vfs.stat(CA_BUNDLE_PATH, { cred: ctx.cred }).size > 0) return;
25160
+ } catch {
25161
+ }
25162
+ try {
25163
+ const tls = await nodeBuiltin("tls");
25164
+ const bundle = tls.rootCertificates.join("\n");
25165
+ if (!bundle) return;
25166
+ ctx.vfs.mkdir("/etc/ssl/certs", { cred: ctx.cred, recursive: true });
25167
+ ctx.vfs.writeFile(CA_BUNDLE_PATH, bundle, { privileged: true, mode: 420 });
25168
+ } catch {
25169
+ }
25170
+ }
24543
25171
  function ensureProcessShims(ctx) {
24544
- const hook = `${SITE_PACKAGES}/sitecustomize.py`;
24545
- const source = `${SHIM_HEADER}${SHIM_SOURCE}`;
25172
+ writeShim(ctx, `${SITE_PACKAGES}/sitecustomize.py`, SHIM_SOURCE);
25173
+ writeShim(ctx, `${SITE_PACKAGES}/_sbx_tls.py`, TLS_MODULE_SOURCE);
25174
+ }
25175
+ function writeShim(ctx, hook, body) {
25176
+ const source = `${SHIM_HEADER}${body}`;
24546
25177
  try {
24547
25178
  const existing = ctx.vfs.readText(hook, ctx.cred);
24548
25179
  if (existing === source || !existing.startsWith(SHIM_HEADER)) return;
@@ -24589,6 +25220,7 @@ function containerMounts(ctx) {
24589
25220
 
24590
25221
  // src/python/pip-command.ts
24591
25222
  init_binary();
25223
+ var RESOLVE_DEADLINE_MS = 15 * 6e4;
24592
25224
  async function requestRemoteBuild(service, requirement, line) {
24593
25225
  line(`Asking ${new URL(service).host} to build ${requirement}`);
24594
25226
  try {
@@ -24791,6 +25423,14 @@ environments. A package with no usable wheel is an error, never a silent skip.`,
24791
25423
  vfs: ctx.vfs,
24792
25424
  cred: ctx.cred,
24793
25425
  requirements,
25426
+ /* The resolver's own default is three minutes, which is a fine bound
25427
+ * for a library but far too tight for a real requirements file: a
25428
+ * `langchain` plus `deepagents` graph is hundreds of distributions,
25429
+ * each needing a metadata fetch, and the install died mid-resolution
25430
+ * with "exceeded its 180s budget" while still making progress. `pip`
25431
+ * already has a 20-minute ceiling on the whole command; this leaves
25432
+ * room for the download phase inside it. */
25433
+ deadlineMs: RESOLVE_DEADLINE_MS,
24794
25434
  pythonVersion: pythonBackend().manifest?.pythonVersion ?? "3.13.5",
24795
25435
  progress: {
24796
25436
  collecting: (name) => ctx.line(`Collecting ${name}`),
@@ -24943,7 +25583,7 @@ function mountContainerFs(FS, opts) {
24943
25583
  const throwErrno = (code) => {
24944
25584
  throw new FS.ErrnoError(emErrno(code));
24945
25585
  };
24946
- const translate = (e) => {
25586
+ const translate2 = (e) => {
24947
25587
  if (isSysError(e)) return throwErrno(e.code);
24948
25588
  throw new FS.ErrnoError(emErrno("EIO"));
24949
25589
  };
@@ -24992,7 +25632,7 @@ function mountContainerFs(FS, opts) {
24992
25632
  blocks: Math.ceil(st.size / 512)
24993
25633
  };
24994
25634
  } catch (e) {
24995
- return translate(e);
25635
+ return translate2(e);
24996
25636
  }
24997
25637
  },
24998
25638
  setattr(node2, attr) {
@@ -25008,7 +25648,7 @@ function mountContainerFs(FS, opts) {
25008
25648
  opts.onWrite?.(path);
25009
25649
  }
25010
25650
  } catch (e) {
25011
- translate(e);
25651
+ translate2(e);
25012
25652
  }
25013
25653
  },
25014
25654
  lookup(parent, name) {
@@ -25033,7 +25673,7 @@ function mountContainerFs(FS, opts) {
25033
25673
  }
25034
25674
  opts.onWrite?.(path);
25035
25675
  } catch (e) {
25036
- translate(e);
25676
+ translate2(e);
25037
25677
  }
25038
25678
  return backend.createNode(parent, name, mode, dev);
25039
25679
  },
@@ -25044,7 +25684,7 @@ function mountContainerFs(FS, opts) {
25044
25684
  vfs.rename(from, to, cred);
25045
25685
  opts.onWrite?.(to);
25046
25686
  } catch (e) {
25047
- translate(e);
25687
+ translate2(e);
25048
25688
  }
25049
25689
  oldNode.name = newName;
25050
25690
  oldNode.parent = newDir;
@@ -25055,7 +25695,7 @@ function mountContainerFs(FS, opts) {
25055
25695
  vfs.unlink(path, cred);
25056
25696
  opts.onWrite?.(path);
25057
25697
  } catch (e) {
25058
- translate(e);
25698
+ translate2(e);
25059
25699
  }
25060
25700
  },
25061
25701
  rmdir(parent, name) {
@@ -25064,7 +25704,7 @@ function mountContainerFs(FS, opts) {
25064
25704
  vfs.rmdir(path, cred);
25065
25705
  opts.onWrite?.(path);
25066
25706
  } catch (e) {
25067
- translate(e);
25707
+ translate2(e);
25068
25708
  }
25069
25709
  },
25070
25710
  readdir(node2) {
@@ -25072,7 +25712,7 @@ function mountContainerFs(FS, opts) {
25072
25712
  try {
25073
25713
  return [".", "..", ...vfs.readdir(path, cred)];
25074
25714
  } catch (e) {
25075
- return translate(e);
25715
+ return translate2(e);
25076
25716
  }
25077
25717
  },
25078
25718
  symlink(parent, newName, oldPath) {
@@ -25081,7 +25721,7 @@ function mountContainerFs(FS, opts) {
25081
25721
  vfs.symlink(oldPath, path, cred);
25082
25722
  opts.onWrite?.(path);
25083
25723
  } catch (e) {
25084
- translate(e);
25724
+ translate2(e);
25085
25725
  }
25086
25726
  },
25087
25727
  readlink(node2) {
@@ -25089,7 +25729,7 @@ function mountContainerFs(FS, opts) {
25089
25729
  try {
25090
25730
  return vfs.readlink(path, cred);
25091
25731
  } catch (e) {
25092
- return translate(e);
25732
+ return translate2(e);
25093
25733
  }
25094
25734
  }
25095
25735
  };
@@ -25101,7 +25741,7 @@ function mountContainerFs(FS, opts) {
25101
25741
  stream.sbxBuffer = vfs.lexists(path) ? vfs.readFile(path, cred) : new Uint8Array(0);
25102
25742
  stream.sbxDirty = false;
25103
25743
  } catch (e) {
25104
- translate(e);
25744
+ translate2(e);
25105
25745
  }
25106
25746
  },
25107
25747
  close(stream) {
@@ -25111,7 +25751,7 @@ function mountContainerFs(FS, opts) {
25111
25751
  vfs.writeFile(path, stream.sbxBuffer, { cred });
25112
25752
  opts.onWrite?.(path);
25113
25753
  } catch (e) {
25114
- translate(e);
25754
+ translate2(e);
25115
25755
  }
25116
25756
  stream.sbxDirty = false;
25117
25757
  },
@@ -25137,7 +25777,7 @@ function mountContainerFs(FS, opts) {
25137
25777
  opts.onWrite?.(path);
25138
25778
  stream.sbxDirty = false;
25139
25779
  } catch (e) {
25140
- translate(e);
25780
+ translate2(e);
25141
25781
  }
25142
25782
  return length;
25143
25783
  },
@@ -34818,6 +35458,212 @@ ${BUNDLER_HINT}`),
34818
35458
 
34819
35459
  // src/worker/local-runtime-pod.ts
34820
35460
  init_path();
35461
+
35462
+ // src/net/outbound-tcp.ts
35463
+ var FIRST_SYNTHETIC = 2887581696;
35464
+ var SYNTHETIC_COUNT = 65534;
35465
+ function toDotted(value) {
35466
+ return [value >>> 24, value >>> 16 & 255, value >>> 8 & 255, value & 255].join(".");
35467
+ }
35468
+ var OutboundTcp = class {
35469
+ constructor(policy, dialer) {
35470
+ this.policy = policy;
35471
+ this.dialer = dialer;
35472
+ }
35473
+ policy;
35474
+ dialer;
35475
+ byName = /* @__PURE__ */ new Map();
35476
+ byAddress = /* @__PURE__ */ new Map();
35477
+ next = 0;
35478
+ /** Whether this address was handed out by `resolve`. */
35479
+ knows(address) {
35480
+ return this.byAddress.has(address);
35481
+ }
35482
+ hostnameFor(address) {
35483
+ return this.byAddress.get(address);
35484
+ }
35485
+ /**
35486
+ * The address for `hostname`, allocating one on first use.
35487
+ *
35488
+ * Refusal happens here as well as at connect, because a name that cannot be
35489
+ * reached should fail as a resolution failure -- which is what every client
35490
+ * reports as "unknown host" rather than as a mid-connection error.
35491
+ */
35492
+ resolve(hostname) {
35493
+ const name = hostname.trim().toLowerCase();
35494
+ if (!name) throw new PosixError(Errno.EINVAL, "empty hostname");
35495
+ if (/^\d{1,3}(\.\d{1,3}){3}$/.test(name)) return name;
35496
+ if (isLoopbackHostname(name)) return "127.0.0.1";
35497
+ const known = this.byName.get(name);
35498
+ if (known) return known;
35499
+ if (!this.allowed(name)) {
35500
+ throw new PosixError(
35501
+ Errno.ENETUNREACH,
35502
+ `cannot resolve ${hostname}: outbound network access is not allowed for this container`
35503
+ );
35504
+ }
35505
+ if (this.next >= SYNTHETIC_COUNT) {
35506
+ throw new PosixError(Errno.ENOSPC, "no addresses left for resolved names");
35507
+ }
35508
+ const address = toDotted(FIRST_SYNTHETIC + this.next + 1);
35509
+ this.next += 1;
35510
+ this.byName.set(name, address);
35511
+ this.byAddress.set(address, name);
35512
+ return address;
35513
+ }
35514
+ /** Open a connection to a resolved address, or to a literal one. */
35515
+ async connect(address, port, local) {
35516
+ const hostname = this.byAddress.get(address) ?? address;
35517
+ if (!this.allowed(hostname)) {
35518
+ throw new PosixError(
35519
+ Errno.ENETUNREACH,
35520
+ `cannot reach ${hostname}:${port}: outbound network access is not allowed for this container`
35521
+ );
35522
+ }
35523
+ if (!this.dialer) {
35524
+ throw new PosixError(
35525
+ Errno.ENETUNREACH,
35526
+ `cannot reach ${hostname}:${port}: this host cannot open sockets (a browser cannot), so outbound traffic has to leave over HTTP through the container's egress`
35527
+ );
35528
+ }
35529
+ const host2 = await this.dialer(hostname, port);
35530
+ const guest = new VirtualTcpConnection(
35531
+ { address: local.address, port: host2.localPort || local.port },
35532
+ { address, port }
35533
+ );
35534
+ const bridge = new VirtualTcpConnection({ address, port }, { address: local.address, port: local.port });
35535
+ guest.pairWith(bridge);
35536
+ bridge.pairWith(guest);
35537
+ host2.onData((bytes2) => {
35538
+ try {
35539
+ bridge.write(bytes2);
35540
+ } catch {
35541
+ host2.close();
35542
+ }
35543
+ });
35544
+ host2.onClose(() => {
35545
+ bridge.shutdown(false, true);
35546
+ bridge.close();
35547
+ });
35548
+ void (async () => {
35549
+ for (; ; ) {
35550
+ if (bridge.readable) {
35551
+ let chunk;
35552
+ try {
35553
+ chunk = bridge.read(64 * 1024);
35554
+ } catch {
35555
+ break;
35556
+ }
35557
+ if (chunk.length === 0) {
35558
+ host2.end();
35559
+ break;
35560
+ }
35561
+ host2.write(chunk);
35562
+ continue;
35563
+ }
35564
+ await bridge.waitForChange();
35565
+ }
35566
+ })();
35567
+ return { connection: guest, host: host2 };
35568
+ }
35569
+ allowed(hostname) {
35570
+ const policy = this.policy();
35571
+ if (isLoopbackHostname(hostname)) return true;
35572
+ if (!policy.allowOutbound) return false;
35573
+ const hosts = policy.allowedHosts;
35574
+ if (!hosts) return true;
35575
+ return hosts.some((allowed) => hostname === allowed || hostname.endsWith(`.${allowed}`));
35576
+ }
35577
+ };
35578
+
35579
+ // src/net/node-tcp-dialer.ts
35580
+ init_binary();
35581
+ function canDialTcp() {
35582
+ return typeof process !== "undefined" && Boolean(process.versions?.node);
35583
+ }
35584
+ function createNodeTcpDialer() {
35585
+ return async (host2, port) => {
35586
+ const net = await nodeBuiltin("net");
35587
+ return await new Promise((resolve3, reject) => {
35588
+ const socket = net.connect({ host: host2, port });
35589
+ const pending = [];
35590
+ let onData = null;
35591
+ let onClose = null;
35592
+ let closed = false;
35593
+ socket.on("data", (chunk) => {
35594
+ const bytes2 = new Uint8Array(chunk);
35595
+ if (onData) onData(bytes2);
35596
+ else pending.push(bytes2);
35597
+ });
35598
+ const finish = () => {
35599
+ if (closed) return;
35600
+ closed = true;
35601
+ onClose?.();
35602
+ };
35603
+ socket.on("close", finish);
35604
+ socket.on("end", finish);
35605
+ socket.on("error", (error) => {
35606
+ if (!closed) {
35607
+ closed = true;
35608
+ onClose?.();
35609
+ }
35610
+ reject(translate(error, host2, port));
35611
+ });
35612
+ socket.on("connect", () => {
35613
+ socket.setNoDelay(true);
35614
+ resolve3({
35615
+ write(bytes2) {
35616
+ socket.write(bytes2);
35617
+ },
35618
+ end() {
35619
+ socket.end();
35620
+ },
35621
+ close() {
35622
+ socket.destroy();
35623
+ },
35624
+ onData(handler) {
35625
+ onData = handler;
35626
+ for (const chunk of pending.splice(0)) handler(chunk);
35627
+ },
35628
+ onClose(handler) {
35629
+ onClose = handler;
35630
+ if (closed) handler();
35631
+ },
35632
+ get remoteAddress() {
35633
+ return socket.remoteAddress ?? host2;
35634
+ },
35635
+ get remotePort() {
35636
+ return socket.remotePort ?? port;
35637
+ },
35638
+ get localPort() {
35639
+ return socket.localPort ?? 0;
35640
+ }
35641
+ });
35642
+ });
35643
+ });
35644
+ };
35645
+ }
35646
+ function translate(error, host2, port) {
35647
+ const where = `${host2}:${port}`;
35648
+ switch (error.code) {
35649
+ case "ECONNREFUSED":
35650
+ return new PosixError(Errno.ECONNREFUSED, `connection refused by ${where}`);
35651
+ case "ENOTFOUND":
35652
+ case "EAI_AGAIN":
35653
+ return new PosixError(Errno.ENETUNREACH, `cannot resolve ${host2}`);
35654
+ case "ETIMEDOUT":
35655
+ return new PosixError(Errno.ETIMEDOUT, `connection to ${where} timed out`);
35656
+ case "ECONNRESET":
35657
+ return new PosixError(Errno.ECONNRESET, `connection to ${where} was reset`);
35658
+ case "EHOSTUNREACH":
35659
+ case "ENETUNREACH":
35660
+ return new PosixError(Errno.ENETUNREACH, `cannot reach ${where}`);
35661
+ default:
35662
+ return new PosixError(Errno.ECONNREFUSED, `connection to ${where} failed: ${error.message}`);
35663
+ }
35664
+ }
35665
+
35666
+ // src/worker/local-runtime-pod.ts
34821
35667
  var WASM_ALIASES = {
34822
35668
  esbuild: "esbuild-wasm",
34823
35669
  rollup: "@rollup/wasm-node"
@@ -35173,6 +36019,32 @@ var LocalRuntimePod = class _LocalRuntimePod {
35173
36019
  constructor(options) {
35174
36020
  ensureProcessGlobal();
35175
36021
  this.sockets = new VirtualTcpNetwork((port) => this.router.activePortsIncludes(port));
36022
+ this.sockets.loopbackHttp = (port, connection) => {
36023
+ if (!this.router.activePortsIncludes(port)) return false;
36024
+ void serveHttpOverConnection(connection, async (request) => {
36025
+ const url = new URL(request.target, `http://127.0.0.1:${port}`);
36026
+ const answer = await this.router.request(port, {
36027
+ method: request.method,
36028
+ path: `${url.pathname}${url.search}`,
36029
+ headers: request.headers,
36030
+ ...request.body.length > 0 ? { body: request.body } : {}
36031
+ });
36032
+ const payload = answer.body ?? new Uint8Array();
36033
+ return {
36034
+ status: Number(answer.statusCode ?? 200),
36035
+ statusText: String(answer.statusMessage ?? ""),
36036
+ headers: answer.headers ?? {},
36037
+ body: typeof payload === "string" ? new TextEncoder().encode(payload) : payload instanceof Uint8Array ? payload : new Uint8Array(payload)
36038
+ };
36039
+ });
36040
+ return true;
36041
+ };
36042
+ if (canDialTcp()) {
36043
+ this.sockets.outbound = new OutboundTcp(
36044
+ () => this.networkPolicy ?? { allowOutbound: false, allowedHosts: null },
36045
+ createNodeTcpDialer()
36046
+ );
36047
+ }
35176
36048
  this.router = new VirtualHttpRouter(this.sockets);
35177
36049
  this.workdir = options.workdir ?? "/";
35178
36050
  this.env = { ...options.env };