@volter/world-core 3.0.38 → 3.0.40

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.
Files changed (179) hide show
  1. package/app-route.cjs +26 -25
  2. package/app-route.d.cts +1 -0
  3. package/dist/app-route.cjs +26 -25
  4. package/dist/app-route.d.cts +1 -0
  5. package/dist/generated/pack-facts.json +409 -352
  6. package/dist/inject.cjs +474 -95
  7. package/dist/network-policy.cjs +110 -13
  8. package/dist/network-policy.d.cts +8 -3
  9. package/dist/src/actions.js +2 -1
  10. package/dist/src/anthropic-wire.d.ts +2 -0
  11. package/dist/src/anthropic-wire.js +5 -0
  12. package/dist/src/attestation.d.ts +6 -0
  13. package/dist/src/attestation.js +27 -0
  14. package/dist/src/bytes.d.ts +3 -0
  15. package/dist/src/bytes.js +17 -0
  16. package/dist/src/changeset.js +2 -1
  17. package/dist/src/clickhouse/sql.js +73 -14
  18. package/dist/src/derived-core.d.ts +210 -32
  19. package/dist/src/derived-core.js +510 -244
  20. package/dist/src/derived-real.d.ts +1 -1
  21. package/dist/src/derived-real.js +704 -93
  22. package/dist/src/derived.d.ts +18 -2
  23. package/dist/src/derived.js +21 -2
  24. package/dist/src/events.d.ts +28 -45
  25. package/dist/src/events.js +40 -58
  26. package/dist/src/exact-json.d.ts +10 -0
  27. package/dist/src/exact-json.js +98 -0
  28. package/dist/src/executor.d.ts +17 -0
  29. package/dist/src/executor.js +91 -30
  30. package/dist/src/file-response.js +1 -1
  31. package/dist/src/form.d.ts +8 -0
  32. package/dist/src/form.js +76 -0
  33. package/dist/src/git/format.d.ts +16 -0
  34. package/dist/src/git/format.js +110 -0
  35. package/dist/src/git/index.d.ts +1 -0
  36. package/dist/src/git/index.js +1 -0
  37. package/dist/src/git/lfs.d.ts +1 -1
  38. package/dist/src/git/lfs.js +4 -3
  39. package/dist/src/graphql-wire.d.ts +6 -0
  40. package/dist/src/graphql-wire.js +6 -3
  41. package/dist/src/grpc-wire.js +2 -1
  42. package/dist/src/head.d.ts +1 -0
  43. package/dist/src/head.js +15 -4
  44. package/dist/src/held-reads.d.ts +6 -0
  45. package/dist/src/held-reads.js +60 -0
  46. package/dist/src/history.js +5 -4
  47. package/dist/src/host-port.d.ts +5 -0
  48. package/dist/src/host-port.js +14 -0
  49. package/dist/src/index.d.ts +11 -3
  50. package/dist/src/index.js +9 -4
  51. package/dist/src/log.js +12 -7
  52. package/dist/src/machines.d.ts +81 -5
  53. package/dist/src/machines.js +125 -9
  54. package/dist/src/managed-database.d.ts +15 -11
  55. package/dist/src/managed-database.js +38 -37
  56. package/dist/src/multipart.d.ts +21 -3
  57. package/dist/src/multipart.js +112 -38
  58. package/dist/src/observe.d.ts +3 -0
  59. package/dist/src/observe.js +6 -2
  60. package/dist/src/openai-wire.d.ts +16 -0
  61. package/dist/src/openai-wire.js +43 -12
  62. package/dist/src/pack-fetch.d.ts +4 -6
  63. package/dist/src/pack-fetch.js +189 -57
  64. package/dist/src/packRegistry.d.ts +7 -1
  65. package/dist/src/packRegistry.js +20 -3
  66. package/dist/src/private-transport.d.ts +1 -0
  67. package/dist/src/private-transport.js +11 -0
  68. package/dist/src/protobuf.js +10 -4
  69. package/dist/src/rateBudget.d.ts +8 -1
  70. package/dist/src/rateBudget.js +1 -0
  71. package/dist/src/redis/engine.d.ts +14 -5
  72. package/dist/src/redis/engine.js +271 -72
  73. package/dist/src/redis/frames.d.ts +17 -0
  74. package/dist/src/redis/frames.js +123 -0
  75. package/dist/src/redis/index.d.ts +6 -0
  76. package/dist/src/redis/index.js +6 -0
  77. package/dist/src/redis/json.d.ts +2 -0
  78. package/dist/src/redis/json.js +14 -0
  79. package/dist/src/redis/lua-libs.d.ts +4 -0
  80. package/dist/src/redis/lua-libs.js +173 -0
  81. package/dist/src/redis/lua.d.ts +1 -0
  82. package/dist/src/redis/lua.js +36 -0
  83. package/dist/src/redis/member-storage.d.ts +5 -0
  84. package/dist/src/redis/member-storage.js +75 -0
  85. package/dist/src/redis/resp2.d.ts +4 -0
  86. package/dist/src/redis/resp2.js +29 -0
  87. package/dist/src/redis/stream.d.ts +14 -0
  88. package/dist/src/redis/stream.js +335 -0
  89. package/dist/src/remote-execute.d.ts +2 -2
  90. package/dist/src/request-body.d.ts +11 -0
  91. package/dist/src/request-body.js +11 -0
  92. package/dist/src/runtime.d.ts +6 -5
  93. package/dist/src/runtime.js +5 -5
  94. package/dist/src/s3/wire.d.ts +4 -4
  95. package/dist/src/s3/wire.js +5 -3
  96. package/dist/src/scenario.d.ts +22 -0
  97. package/dist/src/scenario.js +35 -31
  98. package/dist/src/serve-http.d.ts +2 -1
  99. package/dist/src/serve-http.js +10 -1
  100. package/dist/src/serve.js +7 -4
  101. package/dist/src/signing.d.ts +23 -5
  102. package/dist/src/signing.js +74 -11
  103. package/dist/src/sigv4.d.ts +3 -1
  104. package/dist/src/sigv4.js +4 -2
  105. package/dist/src/smtp.js +2 -1
  106. package/dist/src/sockets.js +8 -3
  107. package/dist/src/state-system.d.ts +3 -0
  108. package/dist/src/storage.js +5 -2
  109. package/dist/src/twin-fetch.d.ts +3 -1
  110. package/dist/src/twin-fetch.js +3 -2
  111. package/dist/src/vendor-call.d.ts +10 -3
  112. package/dist/src/vendor-call.js +58 -14
  113. package/dist/test-fixtures/attestation.SOURCE.md +1 -0
  114. package/dist/test-fixtures/attestation.json +50 -0
  115. package/generated/pack-facts.json +409 -352
  116. package/inject.cjs +474 -95
  117. package/network-policy.cjs +110 -13
  118. package/network-policy.d.cts +8 -3
  119. package/package.json +11 -4
  120. package/src/actions.ts +2 -1
  121. package/src/anthropic-wire.ts +6 -0
  122. package/src/attestation.ts +23 -0
  123. package/src/bytes.ts +21 -0
  124. package/src/changeset.ts +2 -1
  125. package/src/clickhouse/sql.ts +50 -16
  126. package/src/derived-core.ts +573 -249
  127. package/src/derived-real.ts +552 -42
  128. package/src/derived.ts +23 -4
  129. package/src/events.ts +54 -85
  130. package/src/exact-json.ts +71 -0
  131. package/src/executor.ts +86 -27
  132. package/src/file-response.ts +1 -1
  133. package/src/form.ts +66 -0
  134. package/src/git/format.ts +89 -0
  135. package/src/git/index.ts +1 -0
  136. package/src/git/lfs.ts +5 -4
  137. package/src/graphql-wire.ts +8 -3
  138. package/src/grpc-wire.ts +2 -1
  139. package/src/head.ts +15 -5
  140. package/src/held-reads.ts +54 -0
  141. package/src/history.ts +5 -4
  142. package/src/host-port.ts +9 -0
  143. package/src/index.ts +10 -4
  144. package/src/log.ts +11 -7
  145. package/src/machines.ts +135 -13
  146. package/src/managed-database.ts +48 -40
  147. package/src/multipart.ts +110 -35
  148. package/src/observe.ts +6 -3
  149. package/src/openai-wire.ts +45 -14
  150. package/src/pack-fetch.ts +173 -50
  151. package/src/packRegistry.ts +27 -4
  152. package/src/private-transport.ts +8 -0
  153. package/src/protobuf.ts +8 -4
  154. package/src/rateBudget.ts +6 -1
  155. package/src/redis/engine.ts +168 -63
  156. package/src/redis/frames.ts +53 -0
  157. package/src/redis/index.ts +6 -0
  158. package/src/redis/json.ts +13 -0
  159. package/src/redis/lua-libs.ts +62 -0
  160. package/src/redis/lua.ts +20 -0
  161. package/src/redis/member-storage.ts +58 -0
  162. package/src/redis/resp2.ts +25 -0
  163. package/src/redis/stream.ts +102 -0
  164. package/src/remote-execute.ts +2 -2
  165. package/src/request-body.ts +12 -0
  166. package/src/runtime.ts +6 -5
  167. package/src/s3/wire.ts +8 -5
  168. package/src/scenario.ts +34 -13
  169. package/src/serve-http.ts +11 -2
  170. package/src/serve.ts +4 -3
  171. package/src/signing.ts +71 -17
  172. package/src/sigv4.ts +5 -3
  173. package/src/smtp.ts +3 -2
  174. package/src/sockets.ts +8 -3
  175. package/src/state-system.ts +3 -0
  176. package/src/storage.ts +5 -2
  177. package/src/twin-fetch.ts +4 -3
  178. package/src/vendor-call.ts +59 -12
  179. package/test-fixtures/attestation.json +50 -0
package/inject.cjs CHANGED
@@ -29,7 +29,7 @@
29
29
  //
30
30
  // A client can still open its own sockets (npm `undici`'s own Agent, which
31
31
  // @turbopuffer/turbopuffer uses under Node, honours neither patch nor HTTPS_PROXY).
32
- // `net.connect`/`tls.connect` are the seam every Node client ends at, so they carry
32
+ // Node TCP clients using `net.connect`/`tls.connect` or Socket instances reach guarded seams, which carry
33
33
  // the backstop: a connect to a twinned vendor host went around the rewrite and is
34
34
  // refused, never sent to the real vendor; a connect to an untwinned host is refused
35
35
  // wherever strict egress or the World's network policy refuses it. The refusal
@@ -38,11 +38,14 @@ const http = require('http');
38
38
  const https = require('https');
39
39
  const net = require('net');
40
40
  const tls = require('tls');
41
+ const dgram = require('node:dgram');
41
42
  const { isIP } = require('node:net');
42
43
  const { EventEmitter } = require('events');
43
44
  const { Readable } = require('stream');
44
45
  const Module = require('module');
45
- const { worldNetworkPolicyFromEnv, isWorldInternalHost, worldEgressRefusal } = require('./network-policy.cjs');
46
+ const { urlToHttpOptions } = require('node:url');
47
+ const claimsRefreshChannel = require('node:diagnostics_channel').channel('volter.inject.claims-refresh');
48
+ const { allowsWorldNetworkEgress, worldNetworkPolicyFromEnv, isWorldInternalHost, worldEgressRefusal, worldNetworkNeedsPathInspection, worldNetworkUsesPassthrough } = require('./network-policy.cjs');
46
49
  const { createPublicKey, createVerify } = require('crypto');
47
50
 
48
51
  // the vendor host table (VENDOR_HOSTS: a hostname, and a path where two vendors share a host, to the vendor whose twin
@@ -247,8 +250,8 @@ const { appRouteFor, appForwardHeaders, appRequestOptions, sessionCa } = require
247
250
  // HOSTS A TWIN CLAIMS WHILE IT RUNS (packRegistry `hostsClaimed`): no descriptor can list a name a person makes answer
248
251
  // to a vendor, so each claiming twin lists its own at its door, read here every five seconds from the moment the
249
252
  // injector installs (or, in a process that only resolves, the proxy, from its first resolve). A fetch that would be
250
- // refused while a first read is still in flight waits for it; a name connected moments ago is refused until the next
251
- // read, as a new DNS record takes a moment. The door's `appHosts` are the names a hosting twin serves as the
253
+ // refused refreshes the claims once before deciding, so a name connected by a child process is usable immediately.
254
+ // The door's `appHosts` are the names a hosting twin serves as the
252
255
  // customer's own application (a domain added to a Vercel project): those route to the application, not the twin.
253
256
  const CLAIMERS = {};
254
257
  {
@@ -256,30 +259,45 @@ const CLAIMERS = {};
256
259
  for (const vendor of Object.keys(packs)) if (packs[vendor].hostsClaimed) CLAIMERS[vendor] = packs[vendor].hostsClaimed.door;
257
260
  }
258
261
  const CLAIMS = new Map(); // cache: origin -> the hosts its twin last listed; refreshed by the poll below
262
+ // dfb9058, Node 23.11.0 build-policy World, 15 co-located twins, no app/build concurrent there:
263
+ // attach cookbook-dub-build -- node $T/cookbook/dub/claim-refresh.mjs measured five batches
264
+ // at 2.34–14.08 ms with one claiming twin. Retain the existing first-read ceiling for headroom.
265
+ // The host was shared; other actors' workloads were not controlled or measured.
266
+ // Full command, conditions and transcript: cookbook/dub/README.md, Claim refresh measurement.
267
+ const CLAIMS_WAIT_MS = 1500;
259
268
  function claimsAt(origin, door) {
260
269
  const held = CLAIMS.get(origin);
261
270
  if (held) return held;
262
- const entry = { hosts: new Set(), appHosts: new Set(), loaded: false, ready: null };
271
+ const entry = { hosts: new Set(), appHosts: new Set(), loaded: false, ready: null, refresh: null };
263
272
  let settle;
264
273
  entry.ready = new Promise((resolve) => { settle = resolve; });
265
274
  CLAIMS.set(origin, entry);
275
+ let reading = null;
266
276
  const read = () => {
277
+ if (reading) return reading;
267
278
  // the unpatched fetch: the door is the twin's own origin, never a vendor host to redirect
268
279
  const f = (ORIGINALS && ORIGINALS.fetch) || globalThis.fetch;
269
- if (typeof f !== 'function') return;
270
- Promise.resolve()
271
- .then(() => f(`${origin}${door}`, { headers: attachTwinsKey({}) }))
280
+ if (typeof f !== 'function') { entry.loaded = true; settle(); return Promise.resolve(); }
281
+ const controller = new AbortController();
282
+ const deadline = setTimeout(() => controller.abort(), CLAIMS_WAIT_MS);
283
+ reading = Promise.resolve()
284
+ .then(() => f(`${origin}${door}`, { headers: attachTwinsKey({}), signal: controller.signal }))
272
285
  .then((r) => (r.ok ? r.json() : null))
273
286
  .then((body) => {
274
287
  if (body && Array.isArray(body.hosts)) entry.hosts = new Set(body.hosts.map((h) => String(h).toLowerCase()));
275
288
  if (body) entry.appHosts = new Set(Array.isArray(body.appHosts) ? body.appHosts.map((h) => String(h).toLowerCase()) : []);
276
289
  })
277
290
  .catch(() => {})
278
- .then(() => { entry.loaded = true; settle(); });
291
+ .then(() => { entry.loaded = true; settle(); reading = null; })
292
+ .finally(() => clearTimeout(deadline));
293
+ return reading;
279
294
  };
295
+ let refreshing = null;
296
+ // An in-flight read may precede a mutation made while this process was blocked in a child.
297
+ // Finish that read, then obtain a new snapshot; simultaneous misses share that refresh.
298
+ entry.refresh = () => refreshing ??= Promise.resolve(reading).then(read).finally(() => { refreshing = null; });
280
299
  read();
281
- // every five seconds: each process that loads the injector reads (the app's workers, the proxy), and a custom domain
282
- // takes Cloudflare minutes to turn Active, so a faster read buys nothing and loads the twin
300
+ // Periodic discovery keeps already known claims current; a fetch miss refreshes on demand.
283
301
  const timer = setInterval(read, 5000);
284
302
  if (timer.unref) timer.unref();
285
303
  return entry;
@@ -307,6 +325,13 @@ function strictEgressEnabled() {
307
325
  return process.env.VOLTER_TWIN_STRICT_EGRESS === '1' || process.env.VOLTER_TWIN_STRICT_EGRESS === 'true';
308
326
  }
309
327
 
328
+ // Only an explicit public build grant takes precedence over vendor substitution.
329
+ function publicBuildRequest(url) {
330
+ if (!url || strictEgressEnabled()) return false;
331
+ const policy = worldNetworkPolicyFromEnv(process.env);
332
+ return !!policy && policy.phase === 'build' && allowsWorldNetworkEgress(policy, url);
333
+ }
334
+
310
335
  function shouldBlockUntwinned(url) {
311
336
  const routed = url && resolveTwin(url.hostname, MAP, url.pathname);
312
337
  if (!url || (routed && routed.vendor !== 'app')) return false;
@@ -355,37 +380,59 @@ function annotateUntwinnedTlsError(error, hostname) {
355
380
  // --- request-args → { url, method, headers, callback } across every overload ---
356
381
  function describe(args, defaultProtocol) {
357
382
  const input = args[0];
358
- let options = {};
359
- let callback;
360
- if (typeof args[1] === 'function') callback = args[1];
361
- else {
362
- options = args[1] || {};
363
- if (typeof args[2] === 'function') callback = args[2];
364
- }
365
- let url = null;
366
- let headers = {};
367
- let method = 'GET';
368
- if (input instanceof URL || typeof input === 'string') {
369
- try { url = new URL(String(input)); } catch { url = null; }
370
- headers = options.headers || {};
371
- method = options.method || 'GET';
372
- } else {
373
- const merged = Object.assign({}, input || {}, options);
374
- headers = merged.headers || {};
375
- method = merged.method || 'GET';
376
- const proto = String(merged.protocol || defaultProtocol);
377
- const host = merged.hostname || merged.host;
378
- const path = merged.path || '/';
379
- // A request addressed to a proxy (axios and others honour the World's HTTPS_PROXY) names the proxy as its host
380
- // and carries the real target as its path: "When making a request to a proxy ... a client MUST send the target
381
- // URI in "absolute-form" as the request-target" (RFC 9112 §3.2.2). That target is the request's URL.
383
+ const options = args[1] && typeof args[1] === 'object' ? args[1] : {};
384
+ const callback = typeof args[1] === 'function' ? args[1] : typeof args[2] === 'function' ? args[2] : undefined;
385
+ let merged;
386
+ try {
387
+ merged = Object.assign({}, input instanceof URL || typeof input === 'string'
388
+ ? urlToHttpOptions(input instanceof URL ? input : new URL(input)) : input || {}, options);
389
+ } catch { merged = {}; }
390
+ const headers = merged.headers || {};
391
+ const proto = String(merged.protocol || defaultProtocol);
392
+ const host = merged.hostname || merged.host || 'localhost';
393
+ // The mediated transport owns its agent. Do not invoke a caller getter (agent-base infers
394
+ // protocol from Node's native HTTPS stack, which is absent during this authorization).
395
+ const port = merged.port || merged.defaultPort || (proto === 'https:' ? 443 : 80);
396
+ const path = merged.path ?? '/';
397
+ let url = null; let invalid = false;
398
+ try {
399
+ const authority = isIP(String(host)) === 6 ? `[${host}]` : host;
400
+ const physical = new URL(`${proto.endsWith(':') ? proto : `${proto}:`}//${authority}:${port}`);
382
401
  if (/^[a-z][a-z0-9+.-]*:\/\//i.test(path)) {
383
- try { url = new URL(path); } catch { url = null; }
384
- } else if (host) {
385
- try { url = new URL(`${proto.endsWith(':') ? proto : `${proto}:`}//${host}${path}`); } catch { url = null; }
402
+ url = new URL(path);
403
+ // Absolute-form names a target only on this World's own proxy. Else retain both authorities.
404
+ let proxy;
405
+ try { proxy = new URL(process.env.VOLTER_WORLD_PROXY || ''); } catch { /* no proxy */ }
406
+ invalid = physical.origin !== url.origin && physical.origin !== proxy?.origin;
407
+ } else {
408
+ if (!String(path).startsWith('/') || /\\/.test(path)) invalid = true;
409
+ url = new URL(String(path), physical.origin);
410
+ if (url.origin !== physical.origin) invalid = true;
386
411
  }
412
+ } catch { invalid = true; }
413
+ return { url, method: String(merged.method || 'GET').toUpperCase(), headers, callback, options: merged, invalid };
414
+ }
415
+
416
+ function headersForProxy(headers, url) {
417
+ const entries = Array.isArray(headers) ? headers.reduce((out, _v, i) => i % 2 ? out : [...out, [headers[i], headers[i + 1]]], []) : Object.entries(headers);
418
+ const out = Object.create(null);
419
+ for (const [key, value] of entries) {
420
+ const name = String(key).toLowerCase();
421
+ if (name === 'host') {
422
+ for (const host of Array.isArray(value) ? value : [value]) {
423
+ const at = new URL(`${url.protocol}//${String(host)}`);
424
+ if (at.host !== url.host || at.username || at.password || at.pathname !== '/' || at.search || at.hash) throw new Error('path-granted request Host differs from its authority');
425
+ }
426
+ }
427
+ if (!['x-twins-key', 'x-volter-twin-original-host', 'proxy-authorization', 'proxy-connection', 'connection', 'host'].includes(name)) out[key] = value;
387
428
  }
388
- return { url, method: String(method).toUpperCase(), headers, callback };
429
+ return Object.assign(out, { host: url.host, connection: 'close' });
430
+ }
431
+
432
+ function pathGrantProxy() {
433
+ const proxy = new URL(process.env.VOLTER_WORLD_PROXY || '');
434
+ if (proxy.protocol !== 'http:' || !isLocalOrPrivateHost(proxy.hostname) || proxy.username || proxy.password) throw new Error('inspected egress needs the World proxy');
435
+ return proxy;
389
436
  }
390
437
 
391
438
  let ORIGINALS = null; // captured once, used to forward to the twin without re-entering the patch
@@ -413,6 +460,25 @@ function worldBase() {
413
460
  if (!ref || !/^https?:\/\//i.test(ref)) return null;
414
461
  try { const base = new URL(ref.replace(/\/\.well-known\/volter-world\/?$/, '').replace(/\/+$/, '')); return { origin: base.origin, path: base.pathname.replace(/\/+$/, '') }; } catch { return null; }
415
462
  }
463
+ // The local Substrate agent keeps host control routing; its product commands select the
464
+ // existing guest listener. Route original destinations through that listener before rewriting.
465
+ function localGuestProxy() {
466
+ const guest = process.env.VOLTER_WORLD_GUEST_PROXY;
467
+ if (!guest || process.env.VOLTER_WORLD_PROXY !== guest) return null;
468
+ try { return new URL(guest); } catch { return null; }
469
+ }
470
+ function explicitTwinEndpoint(url) {
471
+ return url && Object.values(MAP).some(origin => {
472
+ try {
473
+ const endpoint = new URL(origin), path = endpoint.pathname.replace(/\/$/, '');
474
+ return endpoint.origin === url.origin && (!path || url.pathname === path || url.pathname.startsWith(path + '/'));
475
+ } catch { return false; }
476
+ });
477
+ }
478
+ function mediateLocalGuest(url) {
479
+ const proxy = localGuestProxy();
480
+ return proxy && url && /^https?:$/.test(url.protocol) && !addressedToWorld(url) && !explicitTwinEndpoint(url) && url.origin !== proxy.origin ? proxy : null;
481
+ }
416
482
  function addressedToWorld(url) {
417
483
  const base = worldBase();
418
484
  if (!base || !url || !base.path || url.origin.replace(/^ws/, 'http') !== base.origin) return false;
@@ -467,6 +533,11 @@ function hopToWorld(opts) {
467
533
  const at = String(opts.path == null ? '/' : opts.path);
468
534
  return origin === base.origin && (at.startsWith(`${base.path}/`) || at.startsWith(`/-${base.path}/`));
469
535
  }
536
+ function undiciHeaderObject(raw) {
537
+ const entries = Array.isArray(raw) && raw.length && !Array.isArray(raw[0])
538
+ ? Array.from({ length: raw.length / 2 }, (_, i) => [String(raw[i * 2]), String(raw[i * 2 + 1])]) : raw;
539
+ return Object.fromEntries(new Headers(entries ?? {}));
540
+ }
470
541
  function keyedView(dispatcher) {
471
542
  if (!dispatcher || typeof dispatcher.dispatch !== 'function' || KEYED_VIEWS.has(dispatcher)) return dispatcher;
472
543
  const view = new Proxy(dispatcher, {
@@ -474,6 +545,42 @@ function keyedView(dispatcher) {
474
545
  if (name === 'dispatch') {
475
546
  return function keyedDispatch(opts, handler) {
476
547
  const key = ORIGINAL_DEFINE_PROPERTY ? twinsKey() : null;
548
+ if (ORIGINAL_DEFINE_PROPERTY && opts?.origin != null) {
549
+ const policy = worldNetworkPolicyFromEnv(process.env);
550
+ let at;
551
+ try {
552
+ const origin = new URL(String(opts.origin));
553
+ const path = String(opts.path ?? '/');
554
+ if (!path.startsWith('/') || /\\/.test(path)) throw new Error('invalid dispatch path');
555
+ at = new URL(origin.origin + path);
556
+ } catch {
557
+ if (policy) { process.nextTick(() => handler.onError(new Error('[twin-inject] invalid effective undici request'))); return false; }
558
+ }
559
+ if (at && !isLocalOrPrivateHost(at.hostname) && !isTwinOriginHost(at.hostname) && (!resolveTwin(at.hostname, MAP, at.pathname) || publicBuildRequest(at))
560
+ && (shouldBlockUntwinned(at) || (policy && worldNetworkNeedsPathInspection(policy, at)))) {
561
+ process.nextTick(() => handler.onError(new Error('[twin-inject] unmediated undici hop cannot use inspected egress')));
562
+ return false;
563
+ }
564
+ }
565
+ // Undici's shared dispatcher is an HTTP seam even when its caller bypasses global fetch.
566
+ // Rewrite the hop, not its socket: the original host remains available to the twin.
567
+ if (ORIGINAL_DEFINE_PROPERTY && opts?.origin != null) {
568
+ let at;
569
+ try { at = new URL(new URL(String(opts.origin)).origin + String(opts.path ?? '/')); } catch {}
570
+ const twin = at && !publicBuildRequest(at) && resolveTwin(at.hostname, MAP, at.pathname);
571
+ if (twin && twin.vendor !== 'app') {
572
+ const endpoint = new URL(twin.origin);
573
+ const headers = undiciHeaderObject(opts.headers);
574
+ headers['host'] = at.host;
575
+ headers['x-volter-twin-original-host'] = at.host;
576
+ if (key) headers['x-twins-key'] = key;
577
+ return target.dispatch(Object.assign({}, opts, {
578
+ origin: endpoint.origin,
579
+ path: endpoint.pathname.replace(/\/$/, '') + at.pathname + at.search,
580
+ headers, maxRedirections: 0,
581
+ }), handler);
582
+ }
583
+ }
477
584
  // a hop the caller asked undici to follow is not keyed; a keyed hop is sent with maxRedirections 0, so no
478
585
  // redirect follower below this view (a composed interceptor, a RetryAgent or proxy agent around one, undici
479
586
  // 6's option or interceptors) carries its headers to the next origin: the redirect comes back to the caller
@@ -490,7 +597,7 @@ function keyedView(dispatcher) {
490
597
  }
491
598
  let ORIGINAL_DEFINE_PROPERTY = null;
492
599
  function keyUndiciDispatches() {
493
- if (ORIGINAL_DEFINE_PROPERTY || !worldBase() || Object.defineProperty.name === 'keyingDefineProperty') return;
600
+ if (ORIGINAL_DEFINE_PROPERTY || (!worldBase() && !worldNetworkPolicyFromEnv(process.env)) || Object.defineProperty.name === 'keyingDefineProperty') return;
494
601
  const define = ORIGINAL_DEFINE_PROPERTY = Object.defineProperty;
495
602
  Object.defineProperty = function keyingDefineProperty(target, name, descriptor) {
496
603
  if (ORIGINAL_DEFINE_PROPERTY && target === globalThis && UNDICI_DISPATCHERS.includes(name) && descriptor && 'value' in descriptor) descriptor = Object.assign({}, descriptor, { value: keyedView(descriptor.value) });
@@ -498,6 +605,53 @@ function keyUndiciDispatches() {
498
605
  };
499
606
  for (const name of UNDICI_DISPATCHERS) if (globalThis[name]) globalThis[name] = keyedView(globalThis[name]);
500
607
  }
608
+ // Imported Undici APIs may hold a dispatcher obtained before the global view was installed.
609
+ // Rewrite their public HTTP doors as well; an independent connector still faces the socket guards.
610
+ let UNDICI_LOAD = null;
611
+ function routeImportedUndici() {
612
+ if (UNDICI_LOAD) return;
613
+ const original = UNDICI_LOAD = Module._load;
614
+ const seen = new WeakMap();
615
+ Module._load = function volterUndiciLoad(request, parent, isMain) {
616
+ const exports = original.apply(this, arguments);
617
+ if (request !== 'undici' || !exports || typeof exports.request !== 'function') return exports;
618
+ if (seen.has(exports)) return seen.get(exports);
619
+ const view = Object.assign({}, exports);
620
+ view.fetch = function routedUndiciFetch(input, init) {
621
+ if (!installed) return exports.fetch(input, init);
622
+ let url; try { url = new URL(typeof input === 'object' && input?.url ? input.url : String(input)); } catch {}
623
+ const twin = url && !publicBuildRequest(url) && resolveTwin(url.hostname, MAP, url.pathname);
624
+ // Local control planes may use their own Request class and dispatcher. Preserve both;
625
+ // connections still pass the socket guards, and claimed vendor hops still enter routing.
626
+ if (url && isLocalOrPrivateHost(url.hostname) && (!twin || twin.vendor === 'app')) return exports.fetch(input, init);
627
+ if (input && typeof input === 'object' && typeof input.url === 'string' && !(input instanceof globalThis.Request)) {
628
+ const options = {};
629
+ for (const name of ['method', 'signal', 'redirect', 'cache', 'credentials', 'integrity', 'keepalive', 'referrer', 'referrerPolicy']) {
630
+ if (input[name] !== undefined) options[name] = input[name];
631
+ }
632
+ options.headers = Array.from(input.headers);
633
+ if (input.body !== null && input.body !== undefined) { options.body = input.body; options.duplex = 'half'; }
634
+ return globalThis.fetch(input.url, { ...options, ...init });
635
+ }
636
+ return globalThis.fetch(input, init);
637
+ };
638
+ view.request = function routedUndiciRequest(input, options, callback) {
639
+ if (!installed) return exports.request.apply(exports, arguments);
640
+ const opts = typeof options === 'function' ? {} : options ?? {};
641
+ let url; try { url = new URL(String(input)); if (opts.path !== undefined) url = new URL(url.origin + String(opts.path)); } catch {}
642
+ const twin = url && !publicBuildRequest(url) && resolveTwin(url.hostname, MAP, url.pathname);
643
+ if (!twin || twin.vendor === 'app') return exports.request.apply(exports, arguments);
644
+ const headers = undiciHeaderObject(opts.headers);
645
+ headers.host = url.host; headers['x-volter-twin-original-host'] = url.host;
646
+ const key = twinsKey(); if (key) headers['x-twins-key'] = key;
647
+ const target = twin.origin.replace(/\/$/, '') + url.pathname + url.search;
648
+ const { path: _path, ...requestOptions } = opts; // target already carries the effective path and endpoint prefix
649
+ return exports.request(target, { ...requestOptions, headers, maxRedirections: 0 }, typeof options === 'function' ? options : callback);
650
+ };
651
+ seen.set(exports, view);
652
+ return view;
653
+ };
654
+ }
501
655
  function unkeyUndiciDispatches() {
502
656
  if (!ORIGINAL_DEFINE_PROPERTY) return;
503
657
  // only what this put in place is put back: a wrapper installed over it since stays
@@ -510,55 +664,94 @@ function unkeyUndiciDispatches() {
510
664
  // twin (or the World) answering 30x to an external URL would carry x-twins-key to a REAL host. A
511
665
  // keyed fetch follows redirects itself: a hop that is a twin or the World re-enters the patched
512
666
  // fetch (the key re-attached there); any other hop is fetched WITHOUT the key.
513
- async function followKeyless(first, target, reqInit) {
667
+ // The HTTPS cache first needed the vendor URL rather than its loopback transport destination.
668
+ function fetchLocation(response, url) {
669
+ Object.defineProperty(response, 'url', { value: String(url), configurable: true });
670
+ return response;
671
+ }
672
+
673
+ // WHATWG Fetch, HTTP-redirect fetch, step 13 removes Authorization when URL origins differ:
674
+ // source: https://fetch.spec.whatwg.org/#http-redirect-fetch "delete headerName from request’s header list"
675
+ // Cookie and Proxy-Authorization are also scoped credentials; do not copy them to another origin.
676
+ const CROSS_ORIGIN_DROPPED = ['authorization', 'cookie', 'proxy-authorization', 'x-twins-key', 'x-volter-twin-original-host'];
677
+ function dropCrossOriginHeaders(headers, from, to) {
678
+ if (new URL(from).origin === new URL(to).origin) return;
679
+ if (headers instanceof Headers) {
680
+ for (const name of CROSS_ORIGIN_DROPPED) headers.delete(name);
681
+ } else {
682
+ for (const name of Object.keys(headers)) if (CROSS_ORIGIN_DROPPED.includes(name.toLowerCase())) delete headers[name];
683
+ }
684
+ }
685
+
686
+ // A chain's bound belongs to the request, not to one transport follower.
687
+ const redirectChains = new WeakSet();
688
+ let patchedWorldFetch;
689
+ function redirectChain(known) {
690
+ if (known && redirectChains.has(known)) return known;
691
+ const chain = { hops: 0 }; redirectChains.add(chain); return chain;
692
+ }
693
+ function takeRedirect(chain) {
694
+ // Fetch Standard, HTTP-redirect fetch: a redirect count of 20 returns a network error.
695
+ // https://fetch.spec.whatwg.org/#http-redirect-fetch
696
+ if (chain.hops >= 20) throw new TypeError('fetch failed: redirect count exceeded');
697
+ chain.hops++;
698
+ }
699
+ function reenterFetch(input, init, chain) {
700
+ return patchedWorldFetch(input, init, false, chain);
701
+ }
702
+
703
+ async function followKeyless(first, target, reqInit, chain) {
514
704
  let hop = await first();
515
705
  let hopUrl = target;
516
706
  let hopMethod = String(reqInit.method || 'GET').toUpperCase();
517
707
  let hopBody = reqInit.body;
518
- for (let hops = 0; hops < 5; hops += 1) {
519
- const location = hop.status >= 300 && hop.status < 400 ? hop.headers.get('location') : null;
520
- if (!location) return hop;
708
+ const hopHeaders = new Headers(reqInit.headers || {});
709
+ for (;;) {
710
+ const location = [301, 302, 303, 307, 308].includes(hop.status) ? hop.headers.get('location') : null;
711
+ if (!location) return fetchLocation(hop, hopUrl);
712
+ try { takeRedirect(chain); } catch (error) { try { await hop.body?.cancel(); } catch {} throw error; }
521
713
  // RFC semantics per hop (audit M7): 303 → GET; 301/302 demote POST to GET
522
714
  // (the browser convention SDKs expect); 307/308 preserve method AND body.
523
- if (hop.status === 303 || ((hop.status === 301 || hop.status === 302) && hopMethod === 'POST')) {
715
+ if ((hop.status === 303 && hopMethod !== 'HEAD') || ((hop.status === 301 || hop.status === 302) && hopMethod === 'POST')) {
524
716
  hopMethod = 'GET';
525
717
  hopBody = undefined;
526
718
  }
527
719
  // Resolve against the CURRENT hop (a relative Location from hop 2 is relative to hop 2),
528
- // cancel the interim body, and re-enter the PATCHED fetch with the ORIGINAL headers minus
529
- // the key.
720
+ // cancel the interim body, and re-enter the PATCHED fetch with the surviving headers.
721
+ // Keep that reduced set across hops, so credentials never return on a later same-origin hop.
530
722
  const nextUrl = new URL(location, hopUrl);
531
723
  try { hop.body && (await hop.body.cancel()); } catch { /* interim body */ }
532
- const hopHeaders = new Headers(reqInit.headers || {});
724
+ dropCrossOriginHeaders(hopHeaders, hopUrl, nextUrl);
533
725
  hopHeaders.delete('x-twins-key');
534
726
  hopHeaders.delete('x-volter-twin-original-host');
535
- hop = await globalThis.fetch(nextUrl, {
727
+ hop = await reenterFetch(nextUrl, {
536
728
  method: hopMethod,
537
729
  headers: hopHeaders,
538
730
  ...(hopBody === undefined ? {} : { body: hopBody }),
539
731
  redirect: 'manual',
540
- });
732
+ ...(reqInit.signal ? { signal: reqInit.signal } : {}),
733
+ }, chain);
541
734
  hopUrl = nextUrl;
542
735
  }
543
- return hop;
736
+ return fetchLocation(hop, hopUrl);
544
737
  }
545
738
 
546
739
  // A request routed to the application is a real request to where it listens (the original http/https request, so
547
740
  // streaming, aborts and timeouts behave as the client expects); its options are app-route.cjs's, shared with the twins'
548
741
  // deliveries.
549
742
  function requestApp(route, url, method, headers, extra, callback) {
550
- const options = appRequestOptions(route, url, method, headers, extra);
743
+ const options = route.proxy
744
+ ? { protocol: 'http:', hostname: route.proxy.hostname, port: route.proxy.port, path: url.href, method,
745
+ headers: headersForProxy(headers, url), agent: false, ...extra }
746
+ : appRequestOptions(route, url, method, headers, extra);
551
747
  const make = options.protocol === 'https:' ? ORIGINALS.httpsRequest : ORIGINALS.httpRequest;
552
748
  return callback ? make(options, callback) : make(options);
553
749
  }
554
750
 
555
- // What leaves the application with a redirect to anywhere else: what native fetch drops crossing origins, and the World's own.
556
- const CROSS_ORIGIN_DROPPED = ['authorization', 'cookie', 'proxy-authorization', 'x-twins-key', 'x-volter-twin-original-host'];
557
-
558
751
  /** fetch() routed to the application, as native fetch behaves: the caller's redirect mode, its signal, a streamed
559
752
  * body, decoded content; redirects followed (at most 20) within the application while they stay on its hosts, and
560
753
  * through the patched fetch, without credentials or the World's headers, once they leave. */
561
- async function fetchApp(route, url, input, init) {
754
+ async function fetchApp(route, url, input, init, chain) {
562
755
  const request = typeof input === 'object' && input !== null && !(input instanceof URL) && typeof input.url === 'string' ? input : null;
563
756
  const mode = (init && init.redirect) || (request && request.redirect) || 'follow';
564
757
  const signal = (init && init.signal) || (request && request.signal) || undefined;
@@ -573,8 +766,8 @@ async function fetchApp(route, url, input, init) {
573
766
  if (type && !Object.keys(headers).some((k) => k.toLowerCase() === 'content-type')) headers['content-type'] = type;
574
767
  body = Buffer.from(await read.arrayBuffer());
575
768
  } else if (typeof body === 'string' && !Object.keys(headers).some((k) => k.toLowerCase() === 'content-type')) headers['content-type'] = 'text/plain;charset=UTF-8';
576
- let at = url; let current = route;
577
- for (let hops = 0; ; hops++) {
769
+ let at = url; let current = route; const firstHop = chain.hops;
770
+ for (;;) {
578
771
  if (signal && signal.aborted) throw signal.reason ?? new DOMException('This operation was aborted', 'AbortError');
579
772
  const res = await new Promise((resolve, reject) => {
580
773
  const req = requestApp(current, at, method, headers, { signal }, resolve);
@@ -586,17 +779,19 @@ async function fetchApp(route, url, input, init) {
586
779
  if (res.statusCode >= 300 && res.statusCode < 400 && location && [301, 302, 303, 307, 308].includes(res.statusCode) && mode !== 'manual') {
587
780
  res.resume();
588
781
  if (mode === 'error') throw new TypeError('fetch failed: unexpected redirect');
589
- if (hops >= 20) throw new TypeError('fetch failed: redirect count exceeded');
782
+ takeRedirect(chain);
590
783
  const next = new URL(String(location), at);
591
784
  // 303 answers with a GET (a HEAD stays one); 301 and 302 turn only a POST into a GET, as fetch does
592
785
  if ((res.statusCode === 303 && method !== 'HEAD') || ((res.statusCode === 301 || res.statusCode === 302) && method === 'POST')) {
593
786
  method = 'GET'; body = undefined;
594
787
  for (const k of Object.keys(headers)) if (/^content-/i.test(k)) delete headers[k];
595
788
  }
789
+ dropCrossOriginHeaders(headers, at, next);
596
790
  const nextRoute = resolveTwin(next.hostname, MAP, next.pathname);
597
- if (!nextRoute || nextRoute.vendor !== 'app') {
598
- for (const k of Object.keys(headers)) if (CROSS_ORIGIN_DROPPED.includes(k.toLowerCase())) delete headers[k];
599
- const onward = await globalThis.fetch(next.toString(), { method, headers, redirect: mode, ...(signal ? { signal } : {}), ...(body != null ? { body } : {}) });
791
+ if ((current.proxy && localGuestProxy()) || !nextRoute || nextRoute.vendor !== 'app') {
792
+ delete headers['x-twins-key'];
793
+ delete headers['x-volter-twin-original-host'];
794
+ const onward = await reenterFetch(next.toString(), { method, headers, redirect: mode, ...(signal ? { signal } : {}), ...(body != null ? { body } : {}) }, chain);
600
795
  return onward;
601
796
  }
602
797
  at = next; current = nextRoute;
@@ -613,7 +808,7 @@ async function fetchApp(route, url, input, init) {
613
808
  if (empty) res.resume();
614
809
  const response = new Response(empty ? null : Readable.toWeb(stream), { status: res.statusCode, statusText: res.statusMessage, headers: out });
615
810
  Object.defineProperty(response, 'url', { value: at.toString() });
616
- if (hops > 0) Object.defineProperty(response, 'redirected', { value: true });
811
+ if (chain.hops > firstHop) Object.defineProperty(response, 'redirected', { value: true });
617
812
  return response;
618
813
  }
619
814
  }
@@ -869,7 +1064,30 @@ function lookupWaitsForClaims(hostname) {
869
1064
  * slow resolver would, and a name it claims is known from its next read. Null when every first read has landed. */
870
1065
  function claimsPendingBriefly() {
871
1066
  const pending = claimsPending(MAP);
872
- return pending ? Promise.race([pending, new Promise((ok) => { const t = setTimeout(ok, 1500); if (t.unref) t.unref(); })]) : null;
1067
+ return pending ? waitForClaims(pending) : null;
1068
+ }
1069
+ /** The caller's whole wait is bounded independently of reads already in flight. */
1070
+ function waitForClaims(pending, claimingTwins) {
1071
+ return new Promise((resolve) => {
1072
+ const started = performance.now();
1073
+ let settled = false;
1074
+ const finish = (outcome) => {
1075
+ if (settled) return;
1076
+ settled = true;
1077
+ clearTimeout(deadline);
1078
+ if (claimingTwins !== undefined && claimsRefreshChannel.hasSubscribers) {
1079
+ claimsRefreshChannel.publish({ outcome, elapsedMs: performance.now() - started, limitMs: CLAIMS_WAIT_MS, claimingTwins });
1080
+ }
1081
+ resolve();
1082
+ };
1083
+ const deadline = setTimeout(() => finish('expired'), CLAIMS_WAIT_MS);
1084
+ pending.then(() => finish('complete'), () => finish('failed'));
1085
+ });
1086
+ }
1087
+ /** Refresh unresolved fetch destinations once; a claim acquired after startup must not await the poll. */
1088
+ function claimsRefreshBriefly() {
1089
+ const reads = Object.keys(CLAIMERS).filter((v) => MAP[v]).map((v) => claimsAt(MAP[v], CLAIMERS[v]).refresh());
1090
+ return reads.length ? waitForClaims(Promise.all(reads), reads.length) : null;
873
1091
  }
874
1092
  function patchedLookup(original) {
875
1093
  return function patchedDnsLookup(hostname, options, callback) {
@@ -898,6 +1116,7 @@ function patchedPromisesLookup(original) {
898
1116
 
899
1117
  /** The port a net/tls connect names (`connect(port, host)`, `connect({ port, host })`), or undefined. */
900
1118
  function socketTargetPort(args) {
1119
+ if (Array.isArray(args[0])) args = args[0]; // Node factories pass their normalized argument tuple to the instance.
901
1120
  const [first] = args;
902
1121
  const raw = first !== null && typeof first === 'object' ? first.port : first;
903
1122
  const port = Number(raw);
@@ -910,7 +1129,19 @@ function socketRefusal(hostname, port, isTls) {
910
1129
  return `[twin-inject] refused a socket to ${host}, the address this World's DNS gives the names it routes: `
911
1130
  + 'nothing listens there; connect by the name, which the World routes to its twin or its application.';
912
1131
  }
1132
+ if (localGuestProxy() && isLocalOrPrivateHost(host)) {
1133
+ const endpoints = [localGuestProxy(), worldBase() && new URL(worldBase().origin), ...Object.values(MAP).map(origin => { try { return new URL(origin); } catch { return null; } })].filter(Boolean);
1134
+ if (!endpoints.some(endpoint => endpoint.hostname.replace(/^\[|\]$/g, '') === host.replace(/^\[|\]$/g, '') && Number(endpoint.port || (endpoint.protocol === 'https:' ? 443 : 80)) === port)) {
1135
+ return `[twin-inject] local guest private socket to ${host}:${port} requires its guest proxy`;
1136
+ }
1137
+ }
913
1138
  if (!host || isLocalOrPrivateHost(host) || isTwinOriginHost(host)) return null;
1139
+ // the destination as an origin, its port and its protocol included: a policy that lists https://host allows TLS to
1140
+ // host:443, never another port or a plaintext connection to it
1141
+ const authority = `${isIP(host) === 6 ? `[${host}]` : host}${port !== undefined && !(isTls && port === 443) ? `:${port}` : ''}`;
1142
+ const url = (() => { try { return new URL(`${isTls ? 'https' : 'http'}://${authority}/`); } catch { return null; } })();
1143
+ const policy = worldNetworkPolicyFromEnv(process.env);
1144
+ if (url && policy && !strictEgressEnabled() && worldNetworkUsesPassthrough(policy, url)) return null;
914
1145
  const vendors = twinnedHostVendors(host, MAP);
915
1146
  if (vendors.length > 0) {
916
1147
  const doors = vendors.map(endpointEnvFor).filter(Boolean);
@@ -924,10 +1155,7 @@ function socketRefusal(hostname, port, isTls) {
924
1155
  return `[twin-inject] refused a raw socket to ${host}, which this world routes to the application (volter-world app-url --host): `
925
1156
  + 'the client opened its own connection instead of going through http/https or fetch, so it could not be routed.';
926
1157
  }
927
- // the destination as an origin, its port and its protocol included: a policy that lists https://host allows TLS to
928
- // host:443, never another port or a plaintext connection to it
929
- const authority = `${isIP(host) === 6 ? `[${host}]` : host}${port !== undefined && !(isTls && port === 443) ? `:${port}` : ''}`;
930
- const url = (() => { try { return new URL(`${isTls ? 'https' : 'http'}://${authority}/`); } catch { return null; } })();
1158
+ if (url && policy && !strictEgressEnabled() && worldNetworkNeedsPathInspection(policy, url)) return `[twin-inject] inspected egress requires a mediated HTTP request to ${authority}`;
931
1159
  if (url && shouldBlockUntwinned(url)) return `[twin-inject] blocked untwinned external socket to ${authority}`;
932
1160
  return null;
933
1161
  }
@@ -938,8 +1166,22 @@ function refusedSocket(message) {
938
1166
  return socket;
939
1167
  }
940
1168
 
1169
+ // tls.connect over an existing World proxy socket creates no external connection. Verify the
1170
+ // actual socket peer, never its caller-supplied host/servername, before permitting that SNI.
1171
+ function isWorldProxySocket(options) {
1172
+ if (!options || !(options.socket instanceof net.Socket) || options.socket.destroyed) return false;
1173
+ let proxy;
1174
+ try { proxy = new URL(process.env.VOLTER_WORLD_PROXY || ''); } catch { return false; }
1175
+ const local = ['localhost', '127.0.0.1', '[::1]', '::1'];
1176
+ if (!local.includes(proxy.hostname)) return false;
1177
+ const peer = options.socket.remoteAddress;
1178
+ if (!['127.0.0.1', '::1', '::ffff:127.0.0.1'].includes(peer)) return false;
1179
+ return options.socket.remotePort === Number(proxy.port || (proxy.protocol === 'https:' ? 443 : 80));
1180
+ }
1181
+
941
1182
  function patchedConnect(originalConnect, isTls) {
942
1183
  return function patchedSocketConnect(...args) {
1184
+ if (isTls && isWorldProxySocket(args.find((a) => a && typeof a === 'object' && a.socket))) return originalConnect.apply(this, args);
943
1185
  const port = socketTargetPort(args);
944
1186
  for (const host of socketTargetHosts(args, isTls)) {
945
1187
  const refusal = socketRefusal(host, port, isTls);
@@ -949,6 +1191,40 @@ function patchedConnect(originalConnect, isTls) {
949
1191
  };
950
1192
  }
951
1193
 
1194
+ function patchedInstanceConnect(originalConnect) {
1195
+ return function guardedInstanceConnect(...args) {
1196
+ const target = Array.isArray(args[0]) ? args[0] : args;
1197
+ const isTls = this instanceof tls.TLSSocket;
1198
+ for (const host of socketTargetHosts(target, isTls)) {
1199
+ const refusal = socketRefusal(host, socketTargetPort(target), isTls);
1200
+ if (refusal) {
1201
+ // Preserve the instance and its error listeners; never return a different socket.
1202
+ process.nextTick(() => this.destroy(Object.assign(new Error(refusal), { code: 'ECONNREFUSED' })));
1203
+ return this;
1204
+ }
1205
+ }
1206
+ return originalConnect.apply(this, args);
1207
+ };
1208
+ }
1209
+
1210
+ function patchedDatagram(original, connecting) {
1211
+ return function guardedDatagram(...args) {
1212
+ let peer;
1213
+ if (!connecting) { try { peer = this.remoteAddress(); } catch { /* unconnected UDP socket */ } }
1214
+ const tail = connecting ? args : args.slice(1);
1215
+ const host = peer?.address ?? tail.findLast(arg => typeof arg === 'string') ?? 'localhost';
1216
+ const port = peer?.port ?? tail.findLast(arg => typeof arg === 'number');
1217
+ const refusal = socketRefusal(host, port, false);
1218
+ if (refusal) {
1219
+ const error = Object.assign(new Error(refusal), { code: 'ECONNREFUSED' });
1220
+ const callback = !connecting && typeof args.at(-1) === 'function' ? args.at(-1) : null;
1221
+ process.nextTick(() => callback ? callback(error) : this.emit('error', error));
1222
+ return;
1223
+ }
1224
+ return original.apply(this, args);
1225
+ };
1226
+ }
1227
+
952
1228
  let MAP = {};
953
1229
  let installed = false;
954
1230
 
@@ -981,8 +1257,14 @@ function upgradeToTwin(origin, url, method, headers, args, callback) {
981
1257
 
982
1258
  function patchedFactory(originalRequest, defaultProtocol) {
983
1259
  return function patchedRequest(...args) {
984
- const { url, method, headers, callback } = describe(args, defaultProtocol);
985
- const twin = url && resolveTwin(url.hostname, MAP, url.pathname);
1260
+ const { url, method, headers, callback, options: given, invalid } = describe(args, defaultProtocol);
1261
+ if (invalid && worldNetworkPolicyFromEnv(process.env)) return blockedClientRequest('[twin-inject] request has an invalid effective authority or path');
1262
+ const guest = mediateLocalGuest(url);
1263
+ if (guest) {
1264
+ if (isUpgrade(headers) || method === 'CONNECT') return blockedClientRequest('[twin-inject] local guest upgrade requires a mediated transport');
1265
+ return requestApp({ proxy: guest }, url, method, headers, { signal: given.signal, timeout: given.timeout }, callback);
1266
+ }
1267
+ const twin = url && !publicBuildRequest(url) && resolveTwin(url.hostname, MAP, url.pathname);
986
1268
  if (twin && twin.vendor === 'app') {
987
1269
  const given = args.find((a) => a && typeof a === 'object' && !(a instanceof URL)) || {};
988
1270
  return requestApp(twin, url, method, headers, { signal: given.signal, timeout: given.timeout }, callback);
@@ -996,10 +1278,17 @@ function patchedFactory(originalRequest, defaultProtocol) {
996
1278
  // No twin claims this path — but if configured twins claim the HOST, the path is an unclaimed
997
1279
  // slice of a shared, virtualized host (the Cal.com /calendar/v3 case): refuse loudly, in every
998
1280
  // mode, before the strict-egress check gets a chance to phrase it as an ordinary untwinned host.
999
- if (url && twinnedHostVendors(url.hostname, MAP).length > 0) {
1281
+ if (url && !publicBuildRequest(url) && twinnedHostVendors(url.hostname, MAP).length > 0) {
1000
1282
  return blockedClientRequest(unclaimedTwinnedHostPathMessage(url.hostname, url.pathname, MAP));
1001
1283
  }
1002
1284
  if (shouldBlockUntwinned(url)) return blockedClientRequest(`[twin-inject] blocked untwinned external request to ${url.hostname}`);
1285
+ const policy = worldNetworkPolicyFromEnv(process.env);
1286
+ if (url && policy && worldNetworkNeedsPathInspection(policy, url)) {
1287
+ if (isUpgrade(headers) || method === 'CONNECT') return blockedClientRequest('[twin-inject] inspected egress cannot upgrade its transport');
1288
+ try {
1289
+ return requestApp({ proxy: pathGrantProxy() }, url, method, headers, { signal: given.signal, timeout: given.timeout }, callback);
1290
+ } catch (error) { return blockedClientRequest(`[twin-inject] ${error.message}`); }
1291
+ }
1003
1292
  const request = originalRequest.apply(this, addressedToWorld(url) ? withWorldKey(args) : args);
1004
1293
  // Local mode, untwinned external host, world CA in force: the TLS handshake against the
1005
1294
  // real vendor is about to fail with a bare OpenSSL message — annotate it in-place via an
@@ -1035,6 +1324,9 @@ function install(mapOverride) {
1035
1324
  fetch: typeof globalThis.fetch === 'function' ? globalThis.fetch : null,
1036
1325
  netConnect: net.connect,
1037
1326
  netCreateConnection: net.createConnection,
1327
+ socketConnect: net.Socket.prototype.connect,
1328
+ datagramConnect: dgram.Socket.prototype.connect,
1329
+ datagramSend: dgram.Socket.prototype.send,
1038
1330
  tlsConnect: tls.connect,
1039
1331
  dnsLookup: dns.lookup,
1040
1332
  dnsPromisesLookup: dns.promises.lookup,
@@ -1047,18 +1339,27 @@ function install(mapOverride) {
1047
1339
  https.get = function patchedHttpsGet(...args) { const req = https.request(...args); req.end(); return req; };
1048
1340
  net.connect = patchedConnect(ORIGINALS.netConnect, false);
1049
1341
  net.createConnection = patchedConnect(ORIGINALS.netCreateConnection, false);
1342
+ net.Socket.prototype.connect = patchedInstanceConnect(ORIGINALS.socketConnect);
1343
+ dgram.Socket.prototype.connect = patchedDatagram(ORIGINALS.datagramConnect, true);
1344
+ dgram.Socket.prototype.send = patchedDatagram(ORIGINALS.datagramSend, false);
1050
1345
  tls.connect = patchedConnect(ORIGINALS.tlsConnect, true);
1051
1346
  dns.lookup = patchedLookup(ORIGINALS.dnsLookup);
1052
1347
  dns.promises.lookup = patchedPromisesLookup(ORIGINALS.dnsPromisesLookup);
1053
1348
  keyUndiciDispatches();
1349
+ routeImportedUndici();
1054
1350
  // gRPC (architecture, "Other wires": gRPC): an HTTP/2 session to a host a twin serves (grpc-js on google-gax: TLS to
1055
1351
  // webrisk.googleapis.com) connects to that twin's origin in plain text, where the kernel's HTTP/2 server answers it;
1056
1352
  // the client carries on unchanged. Any other authority is the real one's.
1057
1353
  http2.connect = function patchedHttp2Connect(authority, options, listener) {
1058
1354
  let url = null;
1059
1355
  try { url = new URL(typeof authority === 'string' ? authority : authority && authority.href ? authority.href : `https://${authority.hostname || authority.host}`); } catch { url = null; }
1060
- const twin = url && resolveTwin(url.hostname, MAP, '/');
1061
- if (!twin) return ORIGINALS.http2Connect.apply(this, arguments);
1356
+ const twin = url && !publicBuildRequest(url) && resolveTwin(url.hostname, MAP, '/');
1357
+ if (!twin) {
1358
+ if (mediateLocalGuest(url)) throw new Error('[twin-inject] local guest HTTP/2 requires a mediated transport');
1359
+ const policy = worldNetworkPolicyFromEnv(process.env);
1360
+ if (url && policy && !isLocalOrPrivateHost(url.hostname) && worldNetworkNeedsPathInspection(policy, url)) throw new Error('[twin-inject] HTTP/2 cannot use inspected egress');
1361
+ return ORIGINALS.http2Connect.apply(this, arguments);
1362
+ }
1062
1363
  const cb = typeof options === 'function' ? options : listener;
1063
1364
  const opts = typeof options === 'function' || !options ? {} : Object.fromEntries(Object.entries(options).filter(([k]) => !['secureContext', 'servername', 'ca', 'cert', 'key', 'checkServerIdentity', 'ALPNProtocols', 'createConnection', 'rejectUnauthorized'].includes(k)));
1064
1365
  return ORIGINALS.http2Connect.call(this, new URL(twin.origin).origin, opts, cb);
@@ -1066,27 +1367,32 @@ function install(mapOverride) {
1066
1367
 
1067
1368
  if (ORIGINALS.fetch) {
1068
1369
  const originalFetch = ORIGINALS.fetch;
1069
- globalThis.fetch = function patchedFetch(input, init, claimsWaited) {
1370
+ globalThis.fetch = patchedWorldFetch = function patchedFetch(input, init, claimsWaited, knownChain) {
1371
+ const chain = redirectChain(knownChain);
1070
1372
  let urlStr;
1071
1373
  try { urlStr = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input && input.url; } catch { urlStr = undefined; }
1072
1374
  let url = null;
1073
- if (urlStr) { try { url = new URL(urlStr); } catch { url = null; } }
1074
- const twin = url && resolveTwin(url.hostname, MAP, url.pathname);
1375
+ if (urlStr) {
1376
+ if (/\\/.test(String(urlStr).split(/[?#]/, 1)[0]) && worldNetworkPolicyFromEnv(process.env)) return Promise.reject(new Error('[twin-inject] backslash in fetch target'));
1377
+ try { url = new URL(urlStr); } catch { url = null; }
1378
+ }
1379
+ const guest = mediateLocalGuest(url);
1380
+ if (guest) return fetchApp({ proxy: guest }, url, input, init, chain);
1381
+ // Resolve refreshed claims before evaluating the public build-artifact exception.
1382
+ const claimed = url && resolveTwin(url.hostname, MAP, url.pathname);
1383
+ const twin = claimed && !publicBuildRequest(url) ? claimed : null;
1075
1384
  if (!twin) {
1076
- // a host a twin may claim is decided once its door has been read (HOSTS A TWIN CLAIMS, above)
1077
- // waited once at most: a retry after the wait goes on with what is known
1078
- const pending = !claimsWaited && url && !isLocalOrPrivateHost(url.hostname) && !isTwinOriginHost(url.hostname) ? claimsPendingBriefly() : null;
1079
- if (pending) return pending.then(() => patchedFetch(input, init, true));
1385
+ // A host may have been claimed since startup (including by a child process).
1386
+ // Refresh once at most: a retry goes on with the claims actually returned.
1387
+ const pending = !claimsWaited && url && !isLocalOrPrivateHost(url.hostname) && !isTwinOriginHost(url.hostname) ? claimsRefreshBriefly() : null;
1388
+ if (pending) return pending.then(() => patchedFetch(input, init, true, chain));
1080
1389
  // Same shared-host rule as patchedFactory: an unclaimed path on a twinned host refuses
1081
1390
  // loudly in every mode — never mis-routed, never leaked to the real vendor.
1082
- if (url && twinnedHostVendors(url.hostname, MAP).length > 0) {
1391
+ if (url && !publicBuildRequest(url) && twinnedHostVendors(url.hostname, MAP).length > 0) {
1083
1392
  return Promise.reject(new Error(unclaimedTwinnedHostPathMessage(url.hostname, url.pathname, MAP)));
1084
1393
  }
1085
1394
  if (shouldBlockUntwinned(url)) return Promise.reject(new Error(`[twin-inject] blocked untwinned external fetch to ${url.hostname}`));
1086
- // Automatic redirects run inside native fetch, past this interception
1087
- // point. Under an explicit World policy they must not open an unchecked
1088
- // second origin. Manual redirects remain readable for an SDK to follow
1089
- // through this same authorization door.
1395
+ // Each followed redirect re-enters authorization; native fetch only receives manual hops under a policy.
1090
1396
  if (addressedToWorld(url) && twinsKey()) {
1091
1397
  const request = typeof input === 'object' && input !== null ? input : undefined;
1092
1398
  const headers = new Headers(init?.headers ?? request?.headers ?? {});
@@ -1095,16 +1401,23 @@ function install(mapOverride) {
1095
1401
  if (!keyed.method && request?.method) keyed.method = request.method;
1096
1402
  const redirect = keyed.redirect ?? request?.redirect ?? 'follow';
1097
1403
  if (redirect === 'follow') {
1098
- if (worldNetworkPolicyFromEnv(process.env)) return originalFetch(input, Object.assign(keyed, { redirect: 'error' }));
1099
1404
  keyed.redirect = 'manual';
1100
- return followKeyless(() => originalFetch(input, keyed), url, keyed);
1405
+ return followKeyless(() => originalFetch(input, keyed), url, keyed, chain);
1101
1406
  }
1102
1407
  return originalFetch(input, keyed);
1103
1408
  }
1104
1409
  const policy = worldNetworkPolicyFromEnv(process.env);
1105
1410
  const redirect = init?.redirect ?? (typeof input === 'object' && input?.redirect) ?? 'follow';
1106
- const passthrough = originalFetch(input, policy && redirect === 'follow'
1107
- ? { ...init, redirect: 'error' } : init);
1411
+ if (url && policy && worldNetworkNeedsPathInspection(policy, url)) {
1412
+ try { return fetchApp({ proxy: pathGrantProxy() }, url, input, init, chain); }
1413
+ catch (error) { return Promise.reject(error); }
1414
+ }
1415
+ const passthrough = policy && redirect === 'follow'
1416
+ ? followKeyless(() => originalFetch(input, { ...init, redirect: 'manual' }), url, {
1417
+ method: init?.method ?? input?.method, headers: init?.headers ?? input?.headers,
1418
+ body: init?.body ?? input?.body, signal: init?.signal ?? input?.signal,
1419
+ }, chain)
1420
+ : originalFetch(input, init);
1108
1421
  // Same untwinned-host TLS naming as patchedFactory: fetch wraps the issuer error in
1109
1422
  // "fetch failed" with the code down the `cause` chain — annotate the surfaced error.
1110
1423
  if (url && !isLocalOrPrivateHost(url.hostname) && worldCaIsConfigured() && passthrough && typeof passthrough.catch === 'function') {
@@ -1116,7 +1429,7 @@ function install(mapOverride) {
1116
1429
  }
1117
1430
  return passthrough;
1118
1431
  }
1119
- if (twin.vendor === 'app') return fetchApp(twin, url, input, init);
1432
+ if (twin.vendor === 'app') return fetchApp(twin, url, input, init, chain);
1120
1433
  const target = `${twin.origin.replace(/\/$/, '')}${url.pathname}${url.search}`;
1121
1434
  const reqInit = Object.assign({}, init);
1122
1435
  if (!reqInit.method && typeof input === 'object' && input && input.method) reqInit.method = input.method;
@@ -1128,7 +1441,6 @@ function install(mapOverride) {
1128
1441
  if (reqInit.duplex === undefined) reqInit.duplex = 'half';
1129
1442
  }
1130
1443
  if (reqInit.redirect === undefined && typeof input === 'object' && input?.redirect) reqInit.redirect = input.redirect;
1131
- if (worldNetworkPolicyFromEnv(process.env) && (reqInit.redirect === undefined || reqInit.redirect === 'follow')) reqInit.redirect = 'error';
1132
1444
  // Preserve the vendor destination for twins that serve more than one registry flavor from
1133
1445
  // one local origin. Host is honored by node:http; this explicit internal header is the
1134
1446
  // fetch-safe fallback when a runtime normalizes the forbidden Host header itself.
@@ -1141,9 +1453,9 @@ function install(mapOverride) {
1141
1453
  // through this same patch, so its next host is routed on its own.
1142
1454
  // Redirects are followed by followKeyless, so the key never reaches another origin. The
1143
1455
  // node path already behaves this way (the SDK sees the 30x and retries through the patch).
1144
- if (twinsKey() && (reqInit.redirect === undefined || reqInit.redirect === 'follow')) {
1456
+ if ((twinsKey() || /^http:/i.test(twin.origin)) && (reqInit.redirect === undefined || reqInit.redirect === 'follow')) {
1145
1457
  reqInit.redirect = 'manual';
1146
- return followKeyless(() => originalFetch(target, reqInit), target, reqInit);
1458
+ return followKeyless(() => originalFetch(target, reqInit), url, reqInit, chain);
1147
1459
  }
1148
1460
  // an https twin origin (a served world's door): undici cannot use a
1149
1461
  // session CA set after process start, so route through forward() — the
@@ -1161,10 +1473,10 @@ function install(mapOverride) {
1161
1473
  // branch used to prefix too, doubling the namespace on pathed https skins).
1162
1474
  const fullPath = `${url.pathname}${url.search}`;
1163
1475
  return forward(twin.origin, String(reqInit.method || 'GET').toUpperCase(), fullPath, headerObj, bodyValue === undefined ? undefined : Buffer.from(bodyValue))
1164
- .then((res) => new Response(res.body, { status: res.statusCode, headers: res.headers }));
1476
+ .then((res) => fetchLocation(new Response(res.body, { status: res.statusCode, headers: res.headers }), url));
1165
1477
  }
1166
1478
  }
1167
- return originalFetch(target, reqInit);
1479
+ return originalFetch(target, reqInit).then((response) => fetchLocation(response, url));
1168
1480
  };
1169
1481
  }
1170
1482
 
@@ -1207,6 +1519,9 @@ function restore() {
1207
1519
  if (ORIGINALS.fetch) globalThis.fetch = ORIGINALS.fetch;
1208
1520
  net.connect = ORIGINALS.netConnect;
1209
1521
  net.createConnection = ORIGINALS.netCreateConnection;
1522
+ net.Socket.prototype.connect = ORIGINALS.socketConnect;
1523
+ dgram.Socket.prototype.connect = ORIGINALS.datagramConnect;
1524
+ dgram.Socket.prototype.send = ORIGINALS.datagramSend;
1210
1525
  tls.connect = ORIGINALS.tlsConnect;
1211
1526
  dns.lookup = ORIGINALS.dnsLookup;
1212
1527
  dns.promises.lookup = ORIGINALS.dnsPromisesLookup;
@@ -1262,7 +1577,71 @@ function installWorldTime() {
1262
1577
  globalThis.Date = WorldDate;
1263
1578
  }
1264
1579
 
1580
+ // A Cloudflare Worker started through miniflare (`wrangler dev`, Vitest's Workers pool) makes its outbound requests from
1581
+ // workerd, which neither the patched fetch nor the proxy sees. Each miniflare Worker hands them to its
1582
+ // `outboundService`; one that names none is given the patched fetch here, at construction and at setOptions, so its
1583
+ // vendor calls reach their twins as the process's do (architecture, the injector). Its own outbound service stands.
1584
+ function installMiniflareOutbound() {
1585
+ if (Module.__volterMiniflareOutboundInstalled) return;
1586
+ Module.__volterMiniflareOutboundInstalled = true;
1587
+ // A host this process maps to a twin goes through the patched fetch. Any other goes where the World routes it: through
1588
+ // its proxy (HTTPS_PROXY, trusting the World's CA), which Node's own fetch does not read, so through miniflare's undici
1589
+ // with the environment's proxy agent; loopback stays direct.
1590
+ const outboundFor = (miniflarePath) => {
1591
+ let proxied = null;
1592
+ try {
1593
+ if (process.env.HTTPS_PROXY || process.env.HTTP_PROXY) {
1594
+ const undici = Module.createRequire(miniflarePath)('undici');
1595
+ const dispatcher = new undici.EnvHttpProxyAgent({ noProxy: localGuestProxy() ? '' : process.env.NO_PROXY || process.env.no_proxy || 'localhost,127.0.0.1,::1' });
1596
+ proxied = (request) => undici.fetch(request, { dispatcher });
1597
+ }
1598
+ } catch { proxied = null; }
1599
+ const mapped = (request) => { try { const url = new URL(request.url); return Boolean(resolveTwin(url.hostname, MAP, url.pathname)); } catch { return false; } };
1600
+ // The Request itself is handed on: its method, headers and body are getters, which a copy of it as `init` loses.
1601
+ return (request) => (!proxied || (installed && mapped(request)) ? globalThis.fetch(request) : proxied(request));
1602
+ };
1603
+ let outbound = null;
1604
+ const routed = (options) => {
1605
+ if (!options || typeof options !== 'object') return options;
1606
+ // miniflare 5's Worker is `{ config, legacy, dev }`, its outbound a fetcher under `dev` (wrangler's own ProxyWorker, the
1607
+ // one that turns the Cache API off, forwards to the app's Worker and keeps its own path); miniflare 4's names it inline.
1608
+ const worker = (w) => {
1609
+ if (!w || typeof w !== 'object') return w;
1610
+ if ('config' in w) {
1611
+ const dev = w.dev && typeof w.dev === 'object' ? w.dev : {};
1612
+ return dev.outboundService === undefined && dev.cacheAPI !== false ? { ...w, dev: { ...dev, outboundService: { type: 'fetcher', handler: outbound } } } : w;
1613
+ }
1614
+ return w.outboundService === undefined ? { ...w, outboundService: outbound } : w;
1615
+ };
1616
+ return Array.isArray(options.workers) ? { ...options, workers: options.workers.map(worker) } : worker(options);
1617
+ };
1618
+ const originalLoad = Module._load;
1619
+ const seen = new WeakMap();
1620
+ Module._load = function volterMiniflareLoad(request, parent) {
1621
+ const exports = originalLoad.apply(this, arguments);
1622
+ if (request !== 'miniflare' || !exports || typeof exports.Miniflare !== 'function' || exports.Miniflare.__volterRouted) return exports;
1623
+ if (seen.has(exports)) return seen.get(exports);
1624
+ if (!outbound) {
1625
+ let at = null;
1626
+ try { at = Module._resolveFilename(request, parent); } catch { at = null; }
1627
+ outbound = at ? outboundFor(at) : (request) => globalThis.fetch(request);
1628
+ }
1629
+ const Original = exports.Miniflare;
1630
+ class Miniflare extends Original {
1631
+ constructor(options) { super(routed(options)); }
1632
+ setOptions(options) { return super.setOptions(routed(options)); }
1633
+ }
1634
+ Miniflare.__volterRouted = true;
1635
+ // miniflare's build exports `Miniflare` as a getter that can be neither assigned nor redefined: its importer is given
1636
+ // a view of the module whose `Miniflare` is this one, the same view each time it is required.
1637
+ const view = new Proxy(exports, { get: (target, key, receiver) => (key === 'Miniflare' ? Miniflare : Reflect.get(target, key, receiver)) });
1638
+ seen.set(exports, view);
1639
+ return view;
1640
+ };
1641
+ }
1642
+
1265
1643
  // Auto-install as a preload (`--require`); strict refusal also applies without twin URLs.
1266
1644
  install();
1267
1645
  installWorldTime();
1268
1646
  installPrismaAdapterShim();
1647
+ installMiniflareOutbound();