@sockethub/util 1.0.0-alpha.9 → 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,3 +9,7 @@ export declare function toError(value: unknown): Error;
9
9
  * Returns the `Error.message` for `Error` instances, otherwise `String(value)`.
10
10
  */
11
11
  export declare function errorMessage(value: unknown): string;
12
+ /** Mark an error as an expected operational failure. Returns the same instance. */
13
+ export declare function markExpectedError<T extends Error>(err: T): T;
14
+ /** Whether `value` is an Error marked via {@link markExpectedError}. */
15
+ export declare function isExpectedError(value: unknown): boolean;
@@ -1,39 +1,3 @@
1
- import { createRequire } from "node:module";
2
- var __create = Object.create;
3
- var __getProtoOf = Object.getPrototypeOf;
4
- var __defProp = Object.defineProperty;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- function __accessProp(key) {
8
- return this[key];
9
- }
10
- var __toESMCache_node;
11
- var __toESMCache_esm;
12
- var __toESM = (mod, isNodeMode, target) => {
13
- var canCache = mod != null && typeof mod === "object";
14
- if (canCache) {
15
- var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
16
- var cached = cache.get(mod);
17
- if (cached)
18
- return cached;
19
- }
20
- target = mod != null ? __create(__getProtoOf(mod)) : {};
21
- const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
22
- if (mod && typeof mod === "object" || typeof mod === "function") {
23
- for (let key of __getOwnPropNames(mod))
24
- if (!__hasOwnProp.call(to, key))
25
- __defProp(to, key, {
26
- get: __accessProp.bind(mod, key),
27
- enumerable: true
28
- });
29
- }
30
- if (canCache)
31
- cache.set(mod, to);
32
- return to;
33
- };
34
- var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
35
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
36
-
37
1
  // src/error/index.ts
38
2
  function toError(value) {
39
3
  if (value instanceof Error) {
@@ -47,9 +11,19 @@ function errorMessage(value) {
47
11
  }
48
12
  return String(value);
49
13
  }
14
+ var EXPECTED_ERROR = Symbol.for("sockethub.expectedError");
15
+ function markExpectedError(err) {
16
+ err[EXPECTED_ERROR] = true;
17
+ return err;
18
+ }
19
+ function isExpectedError(value) {
20
+ return value instanceof Error && value[EXPECTED_ERROR] === true;
21
+ }
50
22
  export {
51
23
  errorMessage,
24
+ isExpectedError,
25
+ markExpectedError,
52
26
  toError
53
27
  };
54
28
 
55
- //# debugId=214BB1762F3503E364756E2164756E21
29
+ //# debugId=39286EFBA2F056D364756E2164756E21
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/error/index.ts"],
4
4
  "sourcesContent": [
5
- "/**\n * Normalize an unknown thrown/rejected value into an `Error`. Replaces the\n * repeated `x instanceof Error ? x : new Error(String(x))` idiom so error\n * handling is consistent across packages.\n */\nexport function toError(value: unknown): Error {\n if (value instanceof Error) {\n return value;\n }\n return new Error(errorMessage(value));\n}\n\n/**\n * Extract a human-readable message from an unknown thrown/rejected value.\n * Returns the `Error.message` for `Error` instances, otherwise `String(value)`.\n */\nexport function errorMessage(value: unknown): string {\n if (value instanceof Error) {\n return value.message;\n }\n return String(value);\n}\n"
5
+ "/**\n * Normalize an unknown thrown/rejected value into an `Error`. Replaces the\n * repeated `x instanceof Error ? x : new Error(String(x))` idiom so error\n * handling is consistent across packages.\n */\nexport function toError(value: unknown): Error {\n if (value instanceof Error) {\n return value;\n }\n return new Error(errorMessage(value));\n}\n\n/**\n * Extract a human-readable message from an unknown thrown/rejected value.\n * Returns the `Error.message` for `Error` instances, otherwise `String(value)`.\n */\nexport function errorMessage(value: unknown): string {\n if (value instanceof Error) {\n return value.message;\n }\n return String(value);\n}\n\n/**\n * Marker for errors that represent expected operational outcomes rather than\n * server defects — e.g. a user-supplied URL timing out, a remote site\n * refusing a scrape, or a page with no extractable metadata. The platform\n * job handler skips Sentry error capture for marked errors (counting them as\n * a metric instead), keeping production error tracking focused on real bugs.\n *\n * The mark is a `Symbol.for` property so it survives duplicated copies of\n * this module in a dependency tree. It is process-local: it does not survive\n * serialization across IPC or the job queue.\n */\nconst EXPECTED_ERROR = Symbol.for(\"sockethub.expectedError\");\n\ntype MarkableError = Error & { [EXPECTED_ERROR]?: boolean };\n\n/** Mark an error as an expected operational failure. Returns the same instance. */\nexport function markExpectedError<T extends Error>(err: T): T {\n (err as MarkableError)[EXPECTED_ERROR] = true;\n return err;\n}\n\n/** Whether `value` is an Error marked via {@link markExpectedError}. */\nexport function isExpectedError(value: unknown): boolean {\n return (\n value instanceof Error &&\n (value as MarkableError)[EXPECTED_ERROR] === true\n );\n}\n"
6
6
  ],
7
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAKO,SAAS,OAAO,CAAC,OAAuB;AAAA,EAC3C,IAAI,iBAAiB,OAAO;AAAA,IACxB,OAAO;AAAA,EACX;AAAA,EACA,OAAO,IAAI,MAAM,aAAa,KAAK,CAAC;AAAA;AAOjC,SAAS,YAAY,CAAC,OAAwB;AAAA,EACjD,IAAI,iBAAiB,OAAO;AAAA,IACxB,OAAO,MAAM;AAAA,EACjB;AAAA,EACA,OAAO,OAAO,KAAK;AAAA;",
8
- "debugId": "214BB1762F3503E364756E2164756E21",
7
+ "mappings": ";AAKO,SAAS,OAAO,CAAC,OAAuB;AAAA,EAC3C,IAAI,iBAAiB,OAAO;AAAA,IACxB,OAAO;AAAA,EACX;AAAA,EACA,OAAO,IAAI,MAAM,aAAa,KAAK,CAAC;AAAA;AAOjC,SAAS,YAAY,CAAC,OAAwB;AAAA,EACjD,IAAI,iBAAiB,OAAO;AAAA,IACxB,OAAO,MAAM;AAAA,EACjB;AAAA,EACA,OAAO,OAAO,KAAK;AAAA;AAcvB,IAAM,iBAAiB,OAAO,IAAI,yBAAyB;AAKpD,SAAS,iBAAkC,CAAC,KAAW;AAAA,EACzD,IAAsB,kBAAkB;AAAA,EACzC,OAAO;AAAA;AAIJ,SAAS,eAAe,CAAC,OAAyB;AAAA,EACrD,OACI,iBAAiB,SAChB,MAAwB,oBAAoB;AAAA;",
8
+ "debugId": "39286EFBA2F056D364756E2164756E21",
9
9
  "names": []
10
10
  }
package/dist/index.js CHANGED
@@ -1,39 +1,3 @@
1
- import { createRequire } from "node:module";
2
- var __create = Object.create;
3
- var __getProtoOf = Object.getPrototypeOf;
4
- var __defProp = Object.defineProperty;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- function __accessProp(key) {
8
- return this[key];
9
- }
10
- var __toESMCache_node;
11
- var __toESMCache_esm;
12
- var __toESM = (mod, isNodeMode, target) => {
13
- var canCache = mod != null && typeof mod === "object";
14
- if (canCache) {
15
- var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
16
- var cached = cache.get(mod);
17
- if (cached)
18
- return cached;
19
- }
20
- target = mod != null ? __create(__getProtoOf(mod)) : {};
21
- const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
22
- if (mod && typeof mod === "object" || typeof mod === "function") {
23
- for (let key of __getOwnPropNames(mod))
24
- if (!__hasOwnProp.call(to, key))
25
- __defProp(to, key, {
26
- get: __accessProp.bind(mod, key),
27
- enumerable: true
28
- });
29
- }
30
- if (canCache)
31
- cache.set(mod, to);
32
- return to;
33
- };
34
- var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
35
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
36
-
37
1
  // src/error/index.ts
38
2
  function toError(value) {
39
3
  if (value instanceof Error) {
@@ -47,7 +11,14 @@ function errorMessage(value) {
47
11
  }
48
12
  return String(value);
49
13
  }
50
-
14
+ var EXPECTED_ERROR = Symbol.for("sockethub.expectedError");
15
+ function markExpectedError(err) {
16
+ err[EXPECTED_ERROR] = true;
17
+ return err;
18
+ }
19
+ function isExpectedError(value) {
20
+ return value instanceof Error && value[EXPECTED_ERROR] === true;
21
+ }
51
22
  // src/net/address.ts
52
23
  function isBlockedAddress(ip) {
53
24
  const addr = ip.trim().toLowerCase();
@@ -120,6 +91,10 @@ function extractEmbeddedIpv4(groups) {
120
91
  return null;
121
92
  return `${g >> 8}.${g & 255}.${h >> 8}.${h & 255}`;
122
93
  }
94
+ function isLocalUseNat64(groups) {
95
+ const [a, b, c] = groups;
96
+ return a === 100 && b === 65435 && c === 1;
97
+ }
123
98
  function isBlockedIpv6(ip) {
124
99
  const addr = ip.split("%")[0];
125
100
  const groups = expandIpv6(addr);
@@ -130,6 +105,9 @@ function isBlockedIpv6(ip) {
130
105
  if (embedded) {
131
106
  return isBlockedIpv4(embedded);
132
107
  }
108
+ if (isLocalUseNat64(groups)) {
109
+ return true;
110
+ }
133
111
  if ((groups[0] & 65024) === 64512)
134
112
  return true;
135
113
  if ((groups[0] & 65472) === 65152)
@@ -193,6 +171,9 @@ function createGuardedDispatcher(options = {}) {
193
171
  connect: createGuardedConnector(allowPrivateAddresses)
194
172
  });
195
173
  }
174
+ // src/net/fetch.ts
175
+ import { fetch as undiciFetch } from "undici";
176
+
196
177
  // src/net/url.ts
197
178
  function redactUrl(url) {
198
179
  try {
@@ -242,7 +223,7 @@ async function safeFetch(url, options = {}) {
242
223
  if (options.headers) {
243
224
  init.headers = options.headers;
244
225
  }
245
- const res = await fetch(url, init);
226
+ const res = await undiciFetch(url, init);
246
227
  if (!res.ok) {
247
228
  await res.body?.cancel().catch(() => {});
248
229
  const statusText = res.statusText ? ` ${res.statusText}` : "";
@@ -254,12 +235,15 @@ export {
254
235
  DEFAULT_MAX_RESPONSE_BYTES,
255
236
  assertHttpUrl,
256
237
  createGuardedDispatcher,
238
+ createGuardedLookup,
257
239
  errorMessage,
258
240
  isBlockedAddress,
241
+ isExpectedError,
259
242
  isHttpUrl,
243
+ markExpectedError,
260
244
  redactUrl,
261
245
  safeFetch,
262
246
  toError
263
247
  };
264
248
 
265
- //# debugId=62F7147C5620734764756E2164756E21
249
+ //# debugId=E3E0587F56B8165864756E2164756E21
package/dist/index.js.map CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/error/index.ts", "../src/net/address.ts", "../src/net/dispatcher.ts", "../src/net/url.ts", "../src/net/fetch.ts"],
3
+ "sources": ["../src/error/index.ts", "../src/net/address.ts", "../src/net/dispatcher.ts", "../src/net/fetch.ts", "../src/net/url.ts"],
4
4
  "sourcesContent": [
5
- "/**\n * Normalize an unknown thrown/rejected value into an `Error`. Replaces the\n * repeated `x instanceof Error ? x : new Error(String(x))` idiom so error\n * handling is consistent across packages.\n */\nexport function toError(value: unknown): Error {\n if (value instanceof Error) {\n return value;\n }\n return new Error(errorMessage(value));\n}\n\n/**\n * Extract a human-readable message from an unknown thrown/rejected value.\n * Returns the `Error.message` for `Error` instances, otherwise `String(value)`.\n */\nexport function errorMessage(value: unknown): string {\n if (value instanceof Error) {\n return value.message;\n }\n return String(value);\n}\n",
6
- "/**\n * IP address classification for SSRF defense. Used to decide whether a server\n * fetching client-supplied URLs may connect to a given resolved address.\n */\n\n/**\n * Returns true if `ip` is a loopback, private, link-local, or otherwise\n * non-public address that should not be reachable by a server fetching\n * client-supplied URLs (SSRF defense). Handles IPv4, IPv6, and IPv4-mapped\n * IPv6 addresses.\n */\nexport function isBlockedAddress(ip: string): boolean {\n const addr = ip.trim().toLowerCase();\n\n if (addr.includes(\":\")) {\n return isBlockedIpv6(addr);\n }\n\n return isBlockedIpv4(addr);\n}\n\nfunction isBlockedIpv4(ip: string): boolean {\n const parts = ip.split(\".\").map((p) => Number(p));\n if (\n parts.length !== 4 ||\n parts.some((p) => !Number.isInteger(p) || p < 0 || p > 255)\n ) {\n // Not a parseable IPv4 literal; block conservatively.\n return true;\n }\n const [a, b] = parts;\n // 0.0.0.0/8 (\"this network\", commonly routes to localhost)\n if (a === 0) return true;\n // 127.0.0.0/8 loopback\n if (a === 127) return true;\n // 10.0.0.0/8 private\n if (a === 10) return true;\n // 172.16.0.0/12 private\n if (a === 172 && b >= 16 && b <= 31) return true;\n // 192.168.0.0/16 private\n if (a === 192 && b === 168) return true;\n // 169.254.0.0/16 link-local (incl. 169.254.169.254 cloud metadata)\n if (a === 169 && b === 254) return true;\n // 100.64.0.0/10 carrier-grade NAT shared address space\n if (a === 100 && b >= 64 && b <= 127) return true;\n return false;\n}\n\n/**\n * Expand an IPv6 literal (lowercased, zone index already stripped) to its\n * eight 16-bit groups, or null if unparseable. A trailing dotted-quad\n * (e.g. ::ffff:127.0.0.1) is converted to two hex groups first, so every\n * notation of the same address normalizes identically.\n */\nfunction expandIpv6(address: string): Array<number> | null {\n let addr = address;\n const dotted = addr.match(/^(.+:)(\\d{1,3}(?:\\.\\d{1,3}){3})$/);\n if (dotted) {\n const octets = dotted[2].split(\".\").map(Number);\n if (octets.some((o) => !Number.isInteger(o) || o < 0 || o > 255)) {\n return null;\n }\n const hi = ((octets[0] << 8) | octets[1]).toString(16);\n const lo = ((octets[2] << 8) | octets[3]).toString(16);\n addr = `${dotted[1]}${hi}:${lo}`;\n }\n\n const halves = addr.split(\"::\");\n if (halves.length > 2) return null;\n const head = halves[0] ? halves[0].split(\":\") : [];\n const tail = halves.length === 2 && halves[1] ? halves[1].split(\":\") : [];\n if (halves.length === 2 ? head.length + tail.length > 8 : head.length !== 8)\n return null;\n const groupsStr =\n halves.length === 2\n ? [\n ...head,\n ...new Array(8 - head.length - tail.length).fill(\"0\"),\n ...tail,\n ]\n : head;\n\n const groups: Array<number> = [];\n for (const group of groupsStr) {\n if (!/^[0-9a-f]{1,4}$/.test(group)) return null;\n groups.push(Number.parseInt(group, 16));\n }\n return groups;\n}\n\n/**\n * If the expanded IPv6 groups embed an IPv4 address — IPv4-mapped\n * (::ffff:0:0/96), deprecated IPv4-compatible (::/96, which also covers\n * `::`/`::1`), or the NAT64 well-known prefix (64:ff9b::/96) — return it in\n * dotted-quad form so IPv4 rules apply; otherwise return null.\n */\nfunction extractEmbeddedIpv4(groups: Array<number>): string | null {\n const [a, b, c, d, e, f, g, h] = groups;\n const zeroPrefix = a === 0 && b === 0 && c === 0 && d === 0 && e === 0;\n const mapped = zeroPrefix && f === 0xffff;\n const compat = zeroPrefix && f === 0;\n const nat64 =\n a === 0x64 && b === 0xff9b && c === 0 && d === 0 && e === 0 && f === 0;\n if (!mapped && !compat && !nat64) return null;\n return `${g >> 8}.${g & 0xff}.${h >> 8}.${h & 0xff}`;\n}\n\nfunction isBlockedIpv6(ip: string): boolean {\n // Strip zone index (e.g. fe80::1%eth0).\n const addr = ip.split(\"%\")[0];\n // Normalize to 8 groups so hex/dotted/compressed/uppercase spellings of\n // the same address are classified identically (e.g. ::ffff:7f00:1 and\n // ::ffff:127.0.0.1 are both IPv4-mapped loopback).\n const groups = expandIpv6(addr);\n if (!groups) {\n // Not a parseable IPv6 literal; block conservatively.\n return true;\n }\n // Embedded IPv4 (mapped/compat/NAT64) -> apply IPv4 rules. This also\n // covers :: (0.0.0.0) and ::1 (0.0.0.1), both within blocked 0.0.0.0/8.\n const embedded = extractEmbeddedIpv4(groups);\n if (embedded) {\n return isBlockedIpv4(embedded);\n }\n // fc00::/7 unique-local.\n if ((groups[0] & 0xfe00) === 0xfc00) return true;\n // fe80::/10 link-local.\n if ((groups[0] & 0xffc0) === 0xfe80) return true;\n return false;\n}\n",
7
- "import { lookup as dnsLookup, type LookupAddress } from \"node:dns\";\nimport { isIP, type LookupFunction } from \"node:net\";\nimport { Agent, buildConnector } from \"undici\";\nimport { isBlockedAddress } from \"./address.js\";\n\n/** Default response-body cap (5 MiB) applied by a guarded dispatcher. */\nexport const DEFAULT_MAX_RESPONSE_BYTES = 5 * 1024 * 1024;\n\nexport interface GuardedDispatcherOptions {\n /**\n * When true, skip the private/loopback destination checks. Dev/test escape\n * hatch only — never enable on a server that accepts untrusted requests.\n */\n allowPrivateAddresses?: boolean;\n /** Cap on response body size in bytes. Defaults to 5 MiB. */\n maxResponseBytes?: number;\n}\n\ntype LookupOptions = { all?: boolean; family?: number } & Record<\n string,\n unknown\n>;\ntype LookupCallback = (\n err: NodeJS.ErrnoException | null,\n address?: string | Array<LookupAddress>,\n family?: number,\n) => void;\n\n/**\n * A DNS lookup that resolves a hostname and rejects the connection if any\n * resolved address is private/loopback/blocked. Because this runs at the\n * connection layer, it validates the address actually being connected to —\n * including on every redirect hop — closing the check-then-fetch (TOCTOU)\n * rebinding gap that a pre-fetch URL check leaves open.\n *\n * Exported for direct unit testing: the Bun test runner intercepts `fetch` and\n * ignores undici dispatchers, so the lookup decision is verified here rather\n * than through an end-to-end fetch.\n */\nexport function createGuardedLookup(\n allowPrivateAddresses: boolean,\n): LookupFunction {\n return (\n hostname: string,\n // net.connect passes a LookupOptions object; the legacy dns.lookup\n // signature allows a numeric `family`, which we preserve.\n options: LookupOptions | number,\n callback: LookupCallback,\n ): void => {\n const opts: LookupOptions =\n typeof options === \"number\" ? { family: options } : (options ?? {});\n dnsLookup(hostname, { ...opts, all: true }, (err, addresses) => {\n if (err) {\n callback(err);\n return;\n }\n if (!addresses || addresses.length === 0) {\n callback(\n Object.assign(new Error(`could not resolve ${hostname}`), {\n code: \"ENOTFOUND\",\n }),\n );\n return;\n }\n if (!allowPrivateAddresses) {\n for (const { address } of addresses) {\n if (isBlockedAddress(address)) {\n callback(\n new Error(\n `blocked non-public address ${address} for ${hostname}`,\n ),\n );\n return;\n }\n }\n }\n if (opts.all) {\n callback(null, addresses);\n } else {\n callback(null, addresses[0].address, addresses[0].family);\n }\n });\n };\n}\n\ntype Connector = ReturnType<typeof buildConnector>;\n\n/**\n * Wrap Undici's connector so IP-literal hosts are checked before Node opens a\n * socket. Node bypasses custom DNS lookup functions for literals, so the\n * lookup guard alone does not cover these destinations.\n *\n * Exported for direct unit testing with an injected connector.\n */\nexport function createGuardedConnector(\n allowPrivateAddresses: boolean,\n connector: Connector = buildConnector({\n lookup: createGuardedLookup(allowPrivateAddresses),\n }),\n): Connector {\n return (options, callback): void => {\n const hostname = options.hostname.replace(/^\\[|\\]$/g, \"\");\n if (\n !allowPrivateAddresses &&\n isIP(hostname) !== 0 &&\n isBlockedAddress(hostname)\n ) {\n callback(new Error(`blocked non-public address ${hostname}`), null);\n return;\n }\n connector(options, callback);\n };\n}\n\n/**\n * Build an undici {@link Agent} that defends a server fetching client-supplied\n * URLs against SSRF and memory exhaustion:\n *\n * - Every connection (including each redirect hop) resolves DNS and refuses to\n * connect to a private/loopback/link-local/metadata address (unless\n * `allowPrivateAddresses` is set).\n * - Responses larger than `maxResponseBytes` are aborted.\n *\n * Pass the returned dispatcher to `fetch(url, { dispatcher })`, or to any\n * library that forwards fetch options (e.g. open-graph-scraper's\n * `fetchOptions`).\n */\nexport function createGuardedDispatcher(\n options: GuardedDispatcherOptions = {},\n): Agent {\n const {\n allowPrivateAddresses = false,\n maxResponseBytes = DEFAULT_MAX_RESPONSE_BYTES,\n } = options;\n return new Agent({\n maxResponseSize: maxResponseBytes,\n connect: createGuardedConnector(allowPrivateAddresses),\n });\n}\n",
8
- "/**\n * Strip any `user:password@` userinfo from a URL so it is safe to include in\n * error messages, logs, or telemetry. Best-effort for unparseable input.\n */\nexport function redactUrl(url: string): string {\n try {\n const parsed = new URL(url);\n if (parsed.username || parsed.password) {\n parsed.username = \"\";\n parsed.password = \"\";\n return parsed.toString();\n }\n return url;\n } catch {\n return url.replace(/\\/\\/[^/@\\s]*@/, \"//***@\");\n }\n}\n\n/**\n * Parse `url` and require an `http:`/`https:` scheme, returning the parsed\n * `URL`. Throws a descriptive `Error` (with credentials redacted) on a\n * malformed URL or unsupported scheme. Used to gate outbound fetches of\n * client-supplied URLs.\n */\nexport function assertHttpUrl(url: string): URL {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new Error(`invalid URL: ${redactUrl(url)}`);\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new Error(\n `unsupported scheme ${parsed.protocol} for ${redactUrl(url)}`,\n );\n }\n return parsed;\n}\n\n/** Returns true if `url` parses and uses an `http:`/`https:` scheme. */\nexport function isHttpUrl(url: string): boolean {\n try {\n assertHttpUrl(url);\n return true;\n } catch {\n return false;\n }\n}\n",
9
- "import type { Agent } from \"undici\";\nimport { createGuardedDispatcher } from \"./dispatcher.js\";\nimport { assertHttpUrl, redactUrl } from \"./url.js\";\n\nexport interface SafeFetchOptions {\n /**\n * Skip the private/loopback destination checks. Dev/test escape hatch only.\n * Ignored when an explicit `dispatcher` is provided.\n */\n allowPrivateAddresses?: boolean;\n /** Response body cap in bytes. Ignored when `dispatcher` is provided. */\n maxResponseBytes?: number;\n /** Per-request timeout (applied via AbortSignal.timeout). */\n timeoutMs?: number;\n /**\n * A pre-built guarded dispatcher to reuse (for connection pooling across\n * requests). When omitted, a guarded dispatcher is created per call from\n * `allowPrivateAddresses`/`maxResponseBytes`.\n */\n dispatcher?: Agent;\n /** Extra request headers. */\n headers?: Record<string, string>;\n}\n\n/**\n * SSRF- and resource-guarded outbound fetch for client-supplied URLs:\n *\n * - rejects non-`http(s)` schemes and malformed URLs before connecting;\n * - routes the request through a guarded undici dispatcher that refuses\n * private/loopback/metadata destinations (every redirect hop) and caps the\n * response body;\n * - applies an optional timeout and throws on a non-2xx response.\n *\n * Returns the `Response` (read the body with `.text()`/`.json()`/etc.).\n */\nexport async function safeFetch(\n url: string,\n options: SafeFetchOptions = {},\n): Promise<Response> {\n assertHttpUrl(url);\n\n const dispatcher =\n options.dispatcher ??\n createGuardedDispatcher({\n allowPrivateAddresses: options.allowPrivateAddresses,\n maxResponseBytes: options.maxResponseBytes,\n });\n\n const init: RequestInit & { dispatcher?: unknown } = { dispatcher };\n if (options.timeoutMs !== undefined && options.timeoutMs > 0) {\n init.signal = AbortSignal.timeout(options.timeoutMs);\n }\n if (options.headers) {\n init.headers = options.headers;\n }\n\n const res = await fetch(url, init as RequestInit);\n if (!res.ok) {\n // Drain the unused body so the keep-alive connection is returned to the\n // pool instead of leaking until GC.\n await res.body?.cancel().catch(() => {});\n // HTTP/2 has no reason phrases, so statusText may be empty.\n const statusText = res.statusText ? ` ${res.statusText}` : \"\";\n throw new Error(\n `request failed: ${res.status}${statusText} for ${redactUrl(url)}`,\n );\n }\n return res;\n}\n"
5
+ "/**\n * Normalize an unknown thrown/rejected value into an `Error`. Replaces the\n * repeated `x instanceof Error ? x : new Error(String(x))` idiom so error\n * handling is consistent across packages.\n */\nexport function toError(value: unknown): Error {\n if (value instanceof Error) {\n return value;\n }\n return new Error(errorMessage(value));\n}\n\n/**\n * Extract a human-readable message from an unknown thrown/rejected value.\n * Returns the `Error.message` for `Error` instances, otherwise `String(value)`.\n */\nexport function errorMessage(value: unknown): string {\n if (value instanceof Error) {\n return value.message;\n }\n return String(value);\n}\n\n/**\n * Marker for errors that represent expected operational outcomes rather than\n * server defects — e.g. a user-supplied URL timing out, a remote site\n * refusing a scrape, or a page with no extractable metadata. The platform\n * job handler skips Sentry error capture for marked errors (counting them as\n * a metric instead), keeping production error tracking focused on real bugs.\n *\n * The mark is a `Symbol.for` property so it survives duplicated copies of\n * this module in a dependency tree. It is process-local: it does not survive\n * serialization across IPC or the job queue.\n */\nconst EXPECTED_ERROR = Symbol.for(\"sockethub.expectedError\");\n\ntype MarkableError = Error & { [EXPECTED_ERROR]?: boolean };\n\n/** Mark an error as an expected operational failure. Returns the same instance. */\nexport function markExpectedError<T extends Error>(err: T): T {\n (err as MarkableError)[EXPECTED_ERROR] = true;\n return err;\n}\n\n/** Whether `value` is an Error marked via {@link markExpectedError}. */\nexport function isExpectedError(value: unknown): boolean {\n return (\n value instanceof Error &&\n (value as MarkableError)[EXPECTED_ERROR] === true\n );\n}\n",
6
+ "/**\n * IP address classification for SSRF defense. Used to decide whether a server\n * fetching client-supplied URLs may connect to a given resolved address.\n */\n\n/**\n * Returns true if `ip` is a loopback, private, link-local, or otherwise\n * non-public address that should not be reachable by a server fetching\n * client-supplied URLs (SSRF defense). Handles IPv4, IPv6, and IPv4-mapped\n * IPv6 addresses.\n */\nexport function isBlockedAddress(ip: string): boolean {\n const addr = ip.trim().toLowerCase();\n\n if (addr.includes(\":\")) {\n return isBlockedIpv6(addr);\n }\n\n return isBlockedIpv4(addr);\n}\n\nfunction isBlockedIpv4(ip: string): boolean {\n const parts = ip.split(\".\").map((p) => Number(p));\n if (\n parts.length !== 4 ||\n parts.some((p) => !Number.isInteger(p) || p < 0 || p > 255)\n ) {\n // Not a parseable IPv4 literal; block conservatively.\n return true;\n }\n const [a, b] = parts;\n // 0.0.0.0/8 (\"this network\", commonly routes to localhost)\n if (a === 0) return true;\n // 127.0.0.0/8 loopback\n if (a === 127) return true;\n // 10.0.0.0/8 private\n if (a === 10) return true;\n // 172.16.0.0/12 private\n if (a === 172 && b >= 16 && b <= 31) return true;\n // 192.168.0.0/16 private\n if (a === 192 && b === 168) return true;\n // 169.254.0.0/16 link-local (incl. 169.254.169.254 cloud metadata)\n if (a === 169 && b === 254) return true;\n // 100.64.0.0/10 carrier-grade NAT shared address space\n if (a === 100 && b >= 64 && b <= 127) return true;\n return false;\n}\n\n/**\n * Expand an IPv6 literal (lowercased, zone index already stripped) to its\n * eight 16-bit groups, or null if unparseable. A trailing dotted-quad\n * (e.g. ::ffff:127.0.0.1) is converted to two hex groups first, so every\n * notation of the same address normalizes identically.\n */\nfunction expandIpv6(address: string): Array<number> | null {\n let addr = address;\n const dotted = addr.match(/^(.+:)(\\d{1,3}(?:\\.\\d{1,3}){3})$/);\n if (dotted) {\n const octets = dotted[2].split(\".\").map(Number);\n if (octets.some((o) => !Number.isInteger(o) || o < 0 || o > 255)) {\n return null;\n }\n const hi = ((octets[0] << 8) | octets[1]).toString(16);\n const lo = ((octets[2] << 8) | octets[3]).toString(16);\n addr = `${dotted[1]}${hi}:${lo}`;\n }\n\n const halves = addr.split(\"::\");\n if (halves.length > 2) return null;\n const head = halves[0] ? halves[0].split(\":\") : [];\n const tail = halves.length === 2 && halves[1] ? halves[1].split(\":\") : [];\n if (halves.length === 2 ? head.length + tail.length > 8 : head.length !== 8)\n return null;\n const groupsStr =\n halves.length === 2\n ? [\n ...head,\n ...new Array(8 - head.length - tail.length).fill(\"0\"),\n ...tail,\n ]\n : head;\n\n const groups: Array<number> = [];\n for (const group of groupsStr) {\n if (!/^[0-9a-f]{1,4}$/.test(group)) return null;\n groups.push(Number.parseInt(group, 16));\n }\n return groups;\n}\n\n/**\n * If the expanded IPv6 groups embed an IPv4 address — IPv4-mapped\n * (::ffff:0:0/96), deprecated IPv4-compatible (::/96, which also covers\n * `::`/`::1`), or the NAT64 well-known prefix (64:ff9b::/96) — return it in\n * dotted-quad form so IPv4 rules apply; otherwise return null.\n */\nfunction extractEmbeddedIpv4(groups: Array<number>): string | null {\n const [a, b, c, d, e, f, g, h] = groups;\n const zeroPrefix = a === 0 && b === 0 && c === 0 && d === 0 && e === 0;\n const mapped = zeroPrefix && f === 0xffff;\n const compat = zeroPrefix && f === 0;\n const nat64 =\n a === 0x64 && b === 0xff9b && c === 0 && d === 0 && e === 0 && f === 0;\n if (!mapped && !compat && !nat64) return null;\n return `${g >> 8}.${g & 0xff}.${h >> 8}.${h & 0xff}`;\n}\n\n/**\n * True when `groups` fall under the RFC 8215 local-use NAT64 prefix\n * `64:ff9b:1::/48`.\n *\n * The whole prefix is blocked. Operators may translate it at any longer\n * RFC 6052 length (/48, /56, /64, or /96), and those layouts overlap: the\n * same bits are a public IPv4 under one length and a private IPv4 under\n * another. Decoding one length therefore leaves a bypass, and decoding\n * every length blocks public addresses such as 1.0.0.1. This prefix is not\n * globally routed — public IPv4 uses the well-known prefix `64:ff9b::/96`,\n * which is decoded above — so refusing the entire local-use range closes\n * every embedding without a configured prefix length.\n */\nfunction isLocalUseNat64(groups: Array<number>): boolean {\n const [a, b, c] = groups;\n return a === 0x64 && b === 0xff9b && c === 0x0001;\n}\n\nfunction isBlockedIpv6(ip: string): boolean {\n // Strip zone index (e.g. fe80::1%eth0).\n const addr = ip.split(\"%\")[0];\n // Normalize to 8 groups so hex/dotted/compressed/uppercase spellings of\n // the same address are classified identically (e.g. ::ffff:7f00:1 and\n // ::ffff:127.0.0.1 are both IPv4-mapped loopback).\n const groups = expandIpv6(addr);\n if (!groups) {\n // Not a parseable IPv6 literal; block conservatively.\n return true;\n }\n // Embedded IPv4 (mapped, compatible, or the NAT64 well-known prefix) ->\n // apply IPv4 rules. This also covers :: (0.0.0.0) and ::1 (0.0.0.1),\n // both within blocked 0.0.0.0/8.\n const embedded = extractEmbeddedIpv4(groups);\n if (embedded) {\n return isBlockedIpv4(embedded);\n }\n // Local-use NAT64 (RFC 8215). See isLocalUseNat64.\n if (isLocalUseNat64(groups)) {\n return true;\n }\n // fc00::/7 unique-local.\n if ((groups[0] & 0xfe00) === 0xfc00) return true;\n // fe80::/10 link-local.\n if ((groups[0] & 0xffc0) === 0xfe80) return true;\n return false;\n}\n",
7
+ "import { lookup as dnsLookup, type LookupAddress } from \"node:dns\";\nimport { isIP, type LookupFunction } from \"node:net\";\nimport { Agent, buildConnector } from \"undici\";\nimport { isBlockedAddress } from \"./address.js\";\n\n/** Default response-body cap (5 MiB) applied by a guarded dispatcher. */\nexport const DEFAULT_MAX_RESPONSE_BYTES = 5 * 1024 * 1024;\n\nexport interface GuardedDispatcherOptions {\n /**\n * When true, skip the private/loopback destination checks. Dev/test escape\n * hatch only — never enable on a server that accepts untrusted requests.\n */\n allowPrivateAddresses?: boolean;\n /** Cap on response body size in bytes. Defaults to 5 MiB. */\n maxResponseBytes?: number;\n}\n\ntype LookupOptions = { all?: boolean; family?: number } & Record<\n string,\n unknown\n>;\ntype LookupCallback = (\n err: NodeJS.ErrnoException | null,\n address?: string | Array<LookupAddress>,\n family?: number,\n) => void;\n\n/**\n * A DNS lookup that resolves a hostname and rejects the connection if any\n * resolved address is private/loopback/blocked. Because this runs at the\n * connection layer, it validates the address actually being connected to —\n * including on every redirect hop — closing the check-then-fetch (TOCTOU)\n * rebinding gap that a pre-fetch URL check leaves open.\n *\n * Exported for direct unit testing: the Bun test runner intercepts `fetch` and\n * ignores undici dispatchers, so the lookup decision is verified here rather\n * than through an end-to-end fetch.\n */\nexport function createGuardedLookup(\n allowPrivateAddresses: boolean,\n): LookupFunction {\n return (\n hostname: string,\n // net.connect passes a LookupOptions object; the legacy dns.lookup\n // signature allows a numeric `family`, which we preserve.\n options: LookupOptions | number,\n callback: LookupCallback,\n ): void => {\n const opts: LookupOptions =\n typeof options === \"number\" ? { family: options } : (options ?? {});\n dnsLookup(hostname, { ...opts, all: true }, (err, addresses) => {\n if (err) {\n callback(err);\n return;\n }\n if (!addresses || addresses.length === 0) {\n callback(\n Object.assign(new Error(`could not resolve ${hostname}`), {\n code: \"ENOTFOUND\",\n }),\n );\n return;\n }\n if (!allowPrivateAddresses) {\n for (const { address } of addresses) {\n if (isBlockedAddress(address)) {\n callback(\n new Error(\n `blocked non-public address ${address} for ${hostname}`,\n ),\n );\n return;\n }\n }\n }\n if (opts.all) {\n callback(null, addresses);\n } else {\n callback(null, addresses[0].address, addresses[0].family);\n }\n });\n };\n}\n\ntype Connector = ReturnType<typeof buildConnector>;\n\n/**\n * Wrap Undici's connector so IP-literal hosts are checked before Node opens a\n * socket. Node bypasses custom DNS lookup functions for literals, so the\n * lookup guard alone does not cover these destinations.\n *\n * Exported for direct unit testing with an injected connector.\n */\nexport function createGuardedConnector(\n allowPrivateAddresses: boolean,\n connector: Connector = buildConnector({\n lookup: createGuardedLookup(allowPrivateAddresses),\n }),\n): Connector {\n return (options, callback): void => {\n const hostname = options.hostname.replace(/^\\[|\\]$/g, \"\");\n if (\n !allowPrivateAddresses &&\n isIP(hostname) !== 0 &&\n isBlockedAddress(hostname)\n ) {\n callback(new Error(`blocked non-public address ${hostname}`), null);\n return;\n }\n connector(options, callback);\n };\n}\n\n/**\n * Build an undici {@link Agent} that defends a server fetching client-supplied\n * URLs against SSRF and memory exhaustion:\n *\n * - Every connection (including each redirect hop) resolves DNS and refuses to\n * connect to a private/loopback/link-local/metadata address (unless\n * `allowPrivateAddresses` is set).\n * - Responses larger than `maxResponseBytes` are aborted.\n *\n * Pass the returned dispatcher to undici's own `fetch` (`import { fetch } from\n * \"undici\"`) as `{ dispatcher }`, or to any library that forwards fetch\n * options to undici (e.g. open-graph-scraper's `fetchOptions`). Do not hand\n * it to the runtime's global `fetch`: Node bundles its own undici, and when\n * that major differs from ours (Node 26 ships undici 8) the response comes\n * back with empty headers. `safeFetch` already does the right thing.\n */\nexport function createGuardedDispatcher(\n options: GuardedDispatcherOptions = {},\n): Agent {\n const {\n allowPrivateAddresses = false,\n maxResponseBytes = DEFAULT_MAX_RESPONSE_BYTES,\n } = options;\n return new Agent({\n maxResponseSize: maxResponseBytes,\n connect: createGuardedConnector(allowPrivateAddresses),\n });\n}\n",
8
+ "import { type Agent, fetch as undiciFetch } from \"undici\";\nimport { createGuardedDispatcher } from \"./dispatcher.js\";\nimport { assertHttpUrl, redactUrl } from \"./url.js\";\n\nexport interface SafeFetchOptions {\n /**\n * Skip the private/loopback destination checks. Dev/test escape hatch only.\n * Ignored when an explicit `dispatcher` is provided.\n */\n allowPrivateAddresses?: boolean;\n /** Response body cap in bytes. Ignored when `dispatcher` is provided. */\n maxResponseBytes?: number;\n /** Per-request timeout (applied via AbortSignal.timeout). */\n timeoutMs?: number;\n /**\n * A pre-built guarded dispatcher to reuse (for connection pooling across\n * requests). When omitted, a guarded dispatcher is created per call from\n * `allowPrivateAddresses`/`maxResponseBytes`.\n */\n dispatcher?: Agent;\n /** Extra request headers. */\n headers?: Record<string, string>;\n}\n\n/**\n * SSRF- and resource-guarded outbound fetch for client-supplied URLs:\n *\n * - rejects non-`http(s)` schemes and malformed URLs before connecting;\n * - routes the request through a guarded undici dispatcher that refuses\n * private/loopback/metadata destinations (every redirect hop) and caps the\n * response body;\n * - applies an optional timeout and throws on a non-2xx response.\n *\n * Returns the `Response` (read the body with `.text()`/`.json()`/etc.).\n */\nexport async function safeFetch(\n url: string,\n options: SafeFetchOptions = {},\n): Promise<Response> {\n assertHttpUrl(url);\n\n const dispatcher =\n options.dispatcher ??\n createGuardedDispatcher({\n allowPrivateAddresses: options.allowPrivateAddresses,\n maxResponseBytes: options.maxResponseBytes,\n });\n\n const init: RequestInit & { dispatcher?: Agent } = { dispatcher };\n if (options.timeoutMs !== undefined && options.timeoutMs > 0) {\n init.signal = AbortSignal.timeout(options.timeoutMs);\n }\n if (options.headers) {\n init.headers = options.headers;\n }\n\n // Use undici's own `fetch` rather than the runtime global. The dispatcher\n // is an undici Agent from *our* undici dependency; handing it to a global\n // `fetch` backed by a different undici major (e.g. Node 26 ships undici 8)\n // yields responses with empty headers. Keeping request and dispatcher in\n // the same undici build avoids that mismatch on every Node release.\n const res = (await undiciFetch(url, init)) as unknown as Response;\n if (!res.ok) {\n // Drain the unused body so the keep-alive connection is returned to the\n // pool instead of leaking until GC.\n await res.body?.cancel().catch(() => {});\n // HTTP/2 has no reason phrases, so statusText may be empty.\n const statusText = res.statusText ? ` ${res.statusText}` : \"\";\n throw new Error(\n `request failed: ${res.status}${statusText} for ${redactUrl(url)}`,\n );\n }\n return res;\n}\n",
9
+ "/**\n * Strip any `user:password@` userinfo from a URL so it is safe to include in\n * error messages, logs, or telemetry. Best-effort for unparseable input.\n */\nexport function redactUrl(url: string): string {\n try {\n const parsed = new URL(url);\n if (parsed.username || parsed.password) {\n parsed.username = \"\";\n parsed.password = \"\";\n return parsed.toString();\n }\n return url;\n } catch {\n return url.replace(/\\/\\/[^/@\\s]*@/, \"//***@\");\n }\n}\n\n/**\n * Parse `url` and require an `http:`/`https:` scheme, returning the parsed\n * `URL`. Throws a descriptive `Error` (with credentials redacted) on a\n * malformed URL or unsupported scheme. Used to gate outbound fetches of\n * client-supplied URLs.\n */\nexport function assertHttpUrl(url: string): URL {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new Error(`invalid URL: ${redactUrl(url)}`);\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new Error(\n `unsupported scheme ${parsed.protocol} for ${redactUrl(url)}`,\n );\n }\n return parsed;\n}\n\n/** Returns true if `url` parses and uses an `http:`/`https:` scheme. */\nexport function isHttpUrl(url: string): boolean {\n try {\n assertHttpUrl(url);\n return true;\n } catch {\n return false;\n }\n}\n"
10
10
  ],
11
- "mappings": "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",
12
- "debugId": "62F7147C5620734764756E2164756E21",
11
+ "mappings": "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",
12
+ "debugId": "E3E0587F56B8165864756E2164756E21",
13
13
  "names": []
14
14
  }
@@ -41,9 +41,12 @@ export declare function createGuardedConnector(allowPrivateAddresses: boolean, c
41
41
  * `allowPrivateAddresses` is set).
42
42
  * - Responses larger than `maxResponseBytes` are aborted.
43
43
  *
44
- * Pass the returned dispatcher to `fetch(url, { dispatcher })`, or to any
45
- * library that forwards fetch options (e.g. open-graph-scraper's
46
- * `fetchOptions`).
44
+ * Pass the returned dispatcher to undici's own `fetch` (`import { fetch } from
45
+ * "undici"`) as `{ dispatcher }`, or to any library that forwards fetch
46
+ * options to undici (e.g. open-graph-scraper's `fetchOptions`). Do not hand
47
+ * it to the runtime's global `fetch`: Node bundles its own undici, and when
48
+ * that major differs from ours (Node 26 ships undici 8) the response comes
49
+ * back with empty headers. `safeFetch` already does the right thing.
47
50
  */
48
51
  export declare function createGuardedDispatcher(options?: GuardedDispatcherOptions): Agent;
49
52
  export {};
@@ -1,4 +1,4 @@
1
- import type { Agent } from "undici";
1
+ import { type Agent } from "undici";
2
2
  export interface SafeFetchOptions {
3
3
  /**
4
4
  * Skip the private/loopback destination checks. Dev/test escape hatch only.
@@ -1,4 +1,4 @@
1
1
  export { isBlockedAddress } from "./address.js";
2
- export { createGuardedDispatcher, DEFAULT_MAX_RESPONSE_BYTES, type GuardedDispatcherOptions, } from "./dispatcher.js";
2
+ export { createGuardedDispatcher, createGuardedLookup, DEFAULT_MAX_RESPONSE_BYTES, type GuardedDispatcherOptions, } from "./dispatcher.js";
3
3
  export { type SafeFetchOptions, safeFetch } from "./fetch.js";
4
4
  export { assertHttpUrl, isHttpUrl, redactUrl } from "./url.js";
package/dist/net/index.js CHANGED
@@ -1,39 +1,3 @@
1
- import { createRequire } from "node:module";
2
- var __create = Object.create;
3
- var __getProtoOf = Object.getPrototypeOf;
4
- var __defProp = Object.defineProperty;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- function __accessProp(key) {
8
- return this[key];
9
- }
10
- var __toESMCache_node;
11
- var __toESMCache_esm;
12
- var __toESM = (mod, isNodeMode, target) => {
13
- var canCache = mod != null && typeof mod === "object";
14
- if (canCache) {
15
- var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
16
- var cached = cache.get(mod);
17
- if (cached)
18
- return cached;
19
- }
20
- target = mod != null ? __create(__getProtoOf(mod)) : {};
21
- const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
22
- if (mod && typeof mod === "object" || typeof mod === "function") {
23
- for (let key of __getOwnPropNames(mod))
24
- if (!__hasOwnProp.call(to, key))
25
- __defProp(to, key, {
26
- get: __accessProp.bind(mod, key),
27
- enumerable: true
28
- });
29
- }
30
- if (canCache)
31
- cache.set(mod, to);
32
- return to;
33
- };
34
- var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
35
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
36
-
37
1
  // src/net/address.ts
38
2
  function isBlockedAddress(ip) {
39
3
  const addr = ip.trim().toLowerCase();
@@ -106,6 +70,10 @@ function extractEmbeddedIpv4(groups) {
106
70
  return null;
107
71
  return `${g >> 8}.${g & 255}.${h >> 8}.${h & 255}`;
108
72
  }
73
+ function isLocalUseNat64(groups) {
74
+ const [a, b, c] = groups;
75
+ return a === 100 && b === 65435 && c === 1;
76
+ }
109
77
  function isBlockedIpv6(ip) {
110
78
  const addr = ip.split("%")[0];
111
79
  const groups = expandIpv6(addr);
@@ -116,6 +84,9 @@ function isBlockedIpv6(ip) {
116
84
  if (embedded) {
117
85
  return isBlockedIpv4(embedded);
118
86
  }
87
+ if (isLocalUseNat64(groups)) {
88
+ return true;
89
+ }
119
90
  if ((groups[0] & 65024) === 64512)
120
91
  return true;
121
92
  if ((groups[0] & 65472) === 65152)
@@ -179,6 +150,9 @@ function createGuardedDispatcher(options = {}) {
179
150
  connect: createGuardedConnector(allowPrivateAddresses)
180
151
  });
181
152
  }
153
+ // src/net/fetch.ts
154
+ import { fetch as undiciFetch } from "undici";
155
+
182
156
  // src/net/url.ts
183
157
  function redactUrl(url) {
184
158
  try {
@@ -228,7 +202,7 @@ async function safeFetch(url, options = {}) {
228
202
  if (options.headers) {
229
203
  init.headers = options.headers;
230
204
  }
231
- const res = await fetch(url, init);
205
+ const res = await undiciFetch(url, init);
232
206
  if (!res.ok) {
233
207
  await res.body?.cancel().catch(() => {});
234
208
  const statusText = res.statusText ? ` ${res.statusText}` : "";
@@ -240,10 +214,11 @@ export {
240
214
  DEFAULT_MAX_RESPONSE_BYTES,
241
215
  assertHttpUrl,
242
216
  createGuardedDispatcher,
217
+ createGuardedLookup,
243
218
  isBlockedAddress,
244
219
  isHttpUrl,
245
220
  redactUrl,
246
221
  safeFetch
247
222
  };
248
223
 
249
- //# debugId=B14C9452A8082A2064756E2164756E21
224
+ //# debugId=FC93FACCBD6A33AF64756E2164756E21
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/net/address.ts", "../src/net/dispatcher.ts", "../src/net/url.ts", "../src/net/fetch.ts"],
3
+ "sources": ["../src/net/address.ts", "../src/net/dispatcher.ts", "../src/net/fetch.ts", "../src/net/url.ts"],
4
4
  "sourcesContent": [
5
- "/**\n * IP address classification for SSRF defense. Used to decide whether a server\n * fetching client-supplied URLs may connect to a given resolved address.\n */\n\n/**\n * Returns true if `ip` is a loopback, private, link-local, or otherwise\n * non-public address that should not be reachable by a server fetching\n * client-supplied URLs (SSRF defense). Handles IPv4, IPv6, and IPv4-mapped\n * IPv6 addresses.\n */\nexport function isBlockedAddress(ip: string): boolean {\n const addr = ip.trim().toLowerCase();\n\n if (addr.includes(\":\")) {\n return isBlockedIpv6(addr);\n }\n\n return isBlockedIpv4(addr);\n}\n\nfunction isBlockedIpv4(ip: string): boolean {\n const parts = ip.split(\".\").map((p) => Number(p));\n if (\n parts.length !== 4 ||\n parts.some((p) => !Number.isInteger(p) || p < 0 || p > 255)\n ) {\n // Not a parseable IPv4 literal; block conservatively.\n return true;\n }\n const [a, b] = parts;\n // 0.0.0.0/8 (\"this network\", commonly routes to localhost)\n if (a === 0) return true;\n // 127.0.0.0/8 loopback\n if (a === 127) return true;\n // 10.0.0.0/8 private\n if (a === 10) return true;\n // 172.16.0.0/12 private\n if (a === 172 && b >= 16 && b <= 31) return true;\n // 192.168.0.0/16 private\n if (a === 192 && b === 168) return true;\n // 169.254.0.0/16 link-local (incl. 169.254.169.254 cloud metadata)\n if (a === 169 && b === 254) return true;\n // 100.64.0.0/10 carrier-grade NAT shared address space\n if (a === 100 && b >= 64 && b <= 127) return true;\n return false;\n}\n\n/**\n * Expand an IPv6 literal (lowercased, zone index already stripped) to its\n * eight 16-bit groups, or null if unparseable. A trailing dotted-quad\n * (e.g. ::ffff:127.0.0.1) is converted to two hex groups first, so every\n * notation of the same address normalizes identically.\n */\nfunction expandIpv6(address: string): Array<number> | null {\n let addr = address;\n const dotted = addr.match(/^(.+:)(\\d{1,3}(?:\\.\\d{1,3}){3})$/);\n if (dotted) {\n const octets = dotted[2].split(\".\").map(Number);\n if (octets.some((o) => !Number.isInteger(o) || o < 0 || o > 255)) {\n return null;\n }\n const hi = ((octets[0] << 8) | octets[1]).toString(16);\n const lo = ((octets[2] << 8) | octets[3]).toString(16);\n addr = `${dotted[1]}${hi}:${lo}`;\n }\n\n const halves = addr.split(\"::\");\n if (halves.length > 2) return null;\n const head = halves[0] ? halves[0].split(\":\") : [];\n const tail = halves.length === 2 && halves[1] ? halves[1].split(\":\") : [];\n if (halves.length === 2 ? head.length + tail.length > 8 : head.length !== 8)\n return null;\n const groupsStr =\n halves.length === 2\n ? [\n ...head,\n ...new Array(8 - head.length - tail.length).fill(\"0\"),\n ...tail,\n ]\n : head;\n\n const groups: Array<number> = [];\n for (const group of groupsStr) {\n if (!/^[0-9a-f]{1,4}$/.test(group)) return null;\n groups.push(Number.parseInt(group, 16));\n }\n return groups;\n}\n\n/**\n * If the expanded IPv6 groups embed an IPv4 address — IPv4-mapped\n * (::ffff:0:0/96), deprecated IPv4-compatible (::/96, which also covers\n * `::`/`::1`), or the NAT64 well-known prefix (64:ff9b::/96) — return it in\n * dotted-quad form so IPv4 rules apply; otherwise return null.\n */\nfunction extractEmbeddedIpv4(groups: Array<number>): string | null {\n const [a, b, c, d, e, f, g, h] = groups;\n const zeroPrefix = a === 0 && b === 0 && c === 0 && d === 0 && e === 0;\n const mapped = zeroPrefix && f === 0xffff;\n const compat = zeroPrefix && f === 0;\n const nat64 =\n a === 0x64 && b === 0xff9b && c === 0 && d === 0 && e === 0 && f === 0;\n if (!mapped && !compat && !nat64) return null;\n return `${g >> 8}.${g & 0xff}.${h >> 8}.${h & 0xff}`;\n}\n\nfunction isBlockedIpv6(ip: string): boolean {\n // Strip zone index (e.g. fe80::1%eth0).\n const addr = ip.split(\"%\")[0];\n // Normalize to 8 groups so hex/dotted/compressed/uppercase spellings of\n // the same address are classified identically (e.g. ::ffff:7f00:1 and\n // ::ffff:127.0.0.1 are both IPv4-mapped loopback).\n const groups = expandIpv6(addr);\n if (!groups) {\n // Not a parseable IPv6 literal; block conservatively.\n return true;\n }\n // Embedded IPv4 (mapped/compat/NAT64) -> apply IPv4 rules. This also\n // covers :: (0.0.0.0) and ::1 (0.0.0.1), both within blocked 0.0.0.0/8.\n const embedded = extractEmbeddedIpv4(groups);\n if (embedded) {\n return isBlockedIpv4(embedded);\n }\n // fc00::/7 unique-local.\n if ((groups[0] & 0xfe00) === 0xfc00) return true;\n // fe80::/10 link-local.\n if ((groups[0] & 0xffc0) === 0xfe80) return true;\n return false;\n}\n",
6
- "import { lookup as dnsLookup, type LookupAddress } from \"node:dns\";\nimport { isIP, type LookupFunction } from \"node:net\";\nimport { Agent, buildConnector } from \"undici\";\nimport { isBlockedAddress } from \"./address.js\";\n\n/** Default response-body cap (5 MiB) applied by a guarded dispatcher. */\nexport const DEFAULT_MAX_RESPONSE_BYTES = 5 * 1024 * 1024;\n\nexport interface GuardedDispatcherOptions {\n /**\n * When true, skip the private/loopback destination checks. Dev/test escape\n * hatch only — never enable on a server that accepts untrusted requests.\n */\n allowPrivateAddresses?: boolean;\n /** Cap on response body size in bytes. Defaults to 5 MiB. */\n maxResponseBytes?: number;\n}\n\ntype LookupOptions = { all?: boolean; family?: number } & Record<\n string,\n unknown\n>;\ntype LookupCallback = (\n err: NodeJS.ErrnoException | null,\n address?: string | Array<LookupAddress>,\n family?: number,\n) => void;\n\n/**\n * A DNS lookup that resolves a hostname and rejects the connection if any\n * resolved address is private/loopback/blocked. Because this runs at the\n * connection layer, it validates the address actually being connected to —\n * including on every redirect hop — closing the check-then-fetch (TOCTOU)\n * rebinding gap that a pre-fetch URL check leaves open.\n *\n * Exported for direct unit testing: the Bun test runner intercepts `fetch` and\n * ignores undici dispatchers, so the lookup decision is verified here rather\n * than through an end-to-end fetch.\n */\nexport function createGuardedLookup(\n allowPrivateAddresses: boolean,\n): LookupFunction {\n return (\n hostname: string,\n // net.connect passes a LookupOptions object; the legacy dns.lookup\n // signature allows a numeric `family`, which we preserve.\n options: LookupOptions | number,\n callback: LookupCallback,\n ): void => {\n const opts: LookupOptions =\n typeof options === \"number\" ? { family: options } : (options ?? {});\n dnsLookup(hostname, { ...opts, all: true }, (err, addresses) => {\n if (err) {\n callback(err);\n return;\n }\n if (!addresses || addresses.length === 0) {\n callback(\n Object.assign(new Error(`could not resolve ${hostname}`), {\n code: \"ENOTFOUND\",\n }),\n );\n return;\n }\n if (!allowPrivateAddresses) {\n for (const { address } of addresses) {\n if (isBlockedAddress(address)) {\n callback(\n new Error(\n `blocked non-public address ${address} for ${hostname}`,\n ),\n );\n return;\n }\n }\n }\n if (opts.all) {\n callback(null, addresses);\n } else {\n callback(null, addresses[0].address, addresses[0].family);\n }\n });\n };\n}\n\ntype Connector = ReturnType<typeof buildConnector>;\n\n/**\n * Wrap Undici's connector so IP-literal hosts are checked before Node opens a\n * socket. Node bypasses custom DNS lookup functions for literals, so the\n * lookup guard alone does not cover these destinations.\n *\n * Exported for direct unit testing with an injected connector.\n */\nexport function createGuardedConnector(\n allowPrivateAddresses: boolean,\n connector: Connector = buildConnector({\n lookup: createGuardedLookup(allowPrivateAddresses),\n }),\n): Connector {\n return (options, callback): void => {\n const hostname = options.hostname.replace(/^\\[|\\]$/g, \"\");\n if (\n !allowPrivateAddresses &&\n isIP(hostname) !== 0 &&\n isBlockedAddress(hostname)\n ) {\n callback(new Error(`blocked non-public address ${hostname}`), null);\n return;\n }\n connector(options, callback);\n };\n}\n\n/**\n * Build an undici {@link Agent} that defends a server fetching client-supplied\n * URLs against SSRF and memory exhaustion:\n *\n * - Every connection (including each redirect hop) resolves DNS and refuses to\n * connect to a private/loopback/link-local/metadata address (unless\n * `allowPrivateAddresses` is set).\n * - Responses larger than `maxResponseBytes` are aborted.\n *\n * Pass the returned dispatcher to `fetch(url, { dispatcher })`, or to any\n * library that forwards fetch options (e.g. open-graph-scraper's\n * `fetchOptions`).\n */\nexport function createGuardedDispatcher(\n options: GuardedDispatcherOptions = {},\n): Agent {\n const {\n allowPrivateAddresses = false,\n maxResponseBytes = DEFAULT_MAX_RESPONSE_BYTES,\n } = options;\n return new Agent({\n maxResponseSize: maxResponseBytes,\n connect: createGuardedConnector(allowPrivateAddresses),\n });\n}\n",
7
- "/**\n * Strip any `user:password@` userinfo from a URL so it is safe to include in\n * error messages, logs, or telemetry. Best-effort for unparseable input.\n */\nexport function redactUrl(url: string): string {\n try {\n const parsed = new URL(url);\n if (parsed.username || parsed.password) {\n parsed.username = \"\";\n parsed.password = \"\";\n return parsed.toString();\n }\n return url;\n } catch {\n return url.replace(/\\/\\/[^/@\\s]*@/, \"//***@\");\n }\n}\n\n/**\n * Parse `url` and require an `http:`/`https:` scheme, returning the parsed\n * `URL`. Throws a descriptive `Error` (with credentials redacted) on a\n * malformed URL or unsupported scheme. Used to gate outbound fetches of\n * client-supplied URLs.\n */\nexport function assertHttpUrl(url: string): URL {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new Error(`invalid URL: ${redactUrl(url)}`);\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new Error(\n `unsupported scheme ${parsed.protocol} for ${redactUrl(url)}`,\n );\n }\n return parsed;\n}\n\n/** Returns true if `url` parses and uses an `http:`/`https:` scheme. */\nexport function isHttpUrl(url: string): boolean {\n try {\n assertHttpUrl(url);\n return true;\n } catch {\n return false;\n }\n}\n",
8
- "import type { Agent } from \"undici\";\nimport { createGuardedDispatcher } from \"./dispatcher.js\";\nimport { assertHttpUrl, redactUrl } from \"./url.js\";\n\nexport interface SafeFetchOptions {\n /**\n * Skip the private/loopback destination checks. Dev/test escape hatch only.\n * Ignored when an explicit `dispatcher` is provided.\n */\n allowPrivateAddresses?: boolean;\n /** Response body cap in bytes. Ignored when `dispatcher` is provided. */\n maxResponseBytes?: number;\n /** Per-request timeout (applied via AbortSignal.timeout). */\n timeoutMs?: number;\n /**\n * A pre-built guarded dispatcher to reuse (for connection pooling across\n * requests). When omitted, a guarded dispatcher is created per call from\n * `allowPrivateAddresses`/`maxResponseBytes`.\n */\n dispatcher?: Agent;\n /** Extra request headers. */\n headers?: Record<string, string>;\n}\n\n/**\n * SSRF- and resource-guarded outbound fetch for client-supplied URLs:\n *\n * - rejects non-`http(s)` schemes and malformed URLs before connecting;\n * - routes the request through a guarded undici dispatcher that refuses\n * private/loopback/metadata destinations (every redirect hop) and caps the\n * response body;\n * - applies an optional timeout and throws on a non-2xx response.\n *\n * Returns the `Response` (read the body with `.text()`/`.json()`/etc.).\n */\nexport async function safeFetch(\n url: string,\n options: SafeFetchOptions = {},\n): Promise<Response> {\n assertHttpUrl(url);\n\n const dispatcher =\n options.dispatcher ??\n createGuardedDispatcher({\n allowPrivateAddresses: options.allowPrivateAddresses,\n maxResponseBytes: options.maxResponseBytes,\n });\n\n const init: RequestInit & { dispatcher?: unknown } = { dispatcher };\n if (options.timeoutMs !== undefined && options.timeoutMs > 0) {\n init.signal = AbortSignal.timeout(options.timeoutMs);\n }\n if (options.headers) {\n init.headers = options.headers;\n }\n\n const res = await fetch(url, init as RequestInit);\n if (!res.ok) {\n // Drain the unused body so the keep-alive connection is returned to the\n // pool instead of leaking until GC.\n await res.body?.cancel().catch(() => {});\n // HTTP/2 has no reason phrases, so statusText may be empty.\n const statusText = res.statusText ? ` ${res.statusText}` : \"\";\n throw new Error(\n `request failed: ${res.status}${statusText} for ${redactUrl(url)}`,\n );\n }\n return res;\n}\n"
5
+ "/**\n * IP address classification for SSRF defense. Used to decide whether a server\n * fetching client-supplied URLs may connect to a given resolved address.\n */\n\n/**\n * Returns true if `ip` is a loopback, private, link-local, or otherwise\n * non-public address that should not be reachable by a server fetching\n * client-supplied URLs (SSRF defense). Handles IPv4, IPv6, and IPv4-mapped\n * IPv6 addresses.\n */\nexport function isBlockedAddress(ip: string): boolean {\n const addr = ip.trim().toLowerCase();\n\n if (addr.includes(\":\")) {\n return isBlockedIpv6(addr);\n }\n\n return isBlockedIpv4(addr);\n}\n\nfunction isBlockedIpv4(ip: string): boolean {\n const parts = ip.split(\".\").map((p) => Number(p));\n if (\n parts.length !== 4 ||\n parts.some((p) => !Number.isInteger(p) || p < 0 || p > 255)\n ) {\n // Not a parseable IPv4 literal; block conservatively.\n return true;\n }\n const [a, b] = parts;\n // 0.0.0.0/8 (\"this network\", commonly routes to localhost)\n if (a === 0) return true;\n // 127.0.0.0/8 loopback\n if (a === 127) return true;\n // 10.0.0.0/8 private\n if (a === 10) return true;\n // 172.16.0.0/12 private\n if (a === 172 && b >= 16 && b <= 31) return true;\n // 192.168.0.0/16 private\n if (a === 192 && b === 168) return true;\n // 169.254.0.0/16 link-local (incl. 169.254.169.254 cloud metadata)\n if (a === 169 && b === 254) return true;\n // 100.64.0.0/10 carrier-grade NAT shared address space\n if (a === 100 && b >= 64 && b <= 127) return true;\n return false;\n}\n\n/**\n * Expand an IPv6 literal (lowercased, zone index already stripped) to its\n * eight 16-bit groups, or null if unparseable. A trailing dotted-quad\n * (e.g. ::ffff:127.0.0.1) is converted to two hex groups first, so every\n * notation of the same address normalizes identically.\n */\nfunction expandIpv6(address: string): Array<number> | null {\n let addr = address;\n const dotted = addr.match(/^(.+:)(\\d{1,3}(?:\\.\\d{1,3}){3})$/);\n if (dotted) {\n const octets = dotted[2].split(\".\").map(Number);\n if (octets.some((o) => !Number.isInteger(o) || o < 0 || o > 255)) {\n return null;\n }\n const hi = ((octets[0] << 8) | octets[1]).toString(16);\n const lo = ((octets[2] << 8) | octets[3]).toString(16);\n addr = `${dotted[1]}${hi}:${lo}`;\n }\n\n const halves = addr.split(\"::\");\n if (halves.length > 2) return null;\n const head = halves[0] ? halves[0].split(\":\") : [];\n const tail = halves.length === 2 && halves[1] ? halves[1].split(\":\") : [];\n if (halves.length === 2 ? head.length + tail.length > 8 : head.length !== 8)\n return null;\n const groupsStr =\n halves.length === 2\n ? [\n ...head,\n ...new Array(8 - head.length - tail.length).fill(\"0\"),\n ...tail,\n ]\n : head;\n\n const groups: Array<number> = [];\n for (const group of groupsStr) {\n if (!/^[0-9a-f]{1,4}$/.test(group)) return null;\n groups.push(Number.parseInt(group, 16));\n }\n return groups;\n}\n\n/**\n * If the expanded IPv6 groups embed an IPv4 address — IPv4-mapped\n * (::ffff:0:0/96), deprecated IPv4-compatible (::/96, which also covers\n * `::`/`::1`), or the NAT64 well-known prefix (64:ff9b::/96) — return it in\n * dotted-quad form so IPv4 rules apply; otherwise return null.\n */\nfunction extractEmbeddedIpv4(groups: Array<number>): string | null {\n const [a, b, c, d, e, f, g, h] = groups;\n const zeroPrefix = a === 0 && b === 0 && c === 0 && d === 0 && e === 0;\n const mapped = zeroPrefix && f === 0xffff;\n const compat = zeroPrefix && f === 0;\n const nat64 =\n a === 0x64 && b === 0xff9b && c === 0 && d === 0 && e === 0 && f === 0;\n if (!mapped && !compat && !nat64) return null;\n return `${g >> 8}.${g & 0xff}.${h >> 8}.${h & 0xff}`;\n}\n\n/**\n * True when `groups` fall under the RFC 8215 local-use NAT64 prefix\n * `64:ff9b:1::/48`.\n *\n * The whole prefix is blocked. Operators may translate it at any longer\n * RFC 6052 length (/48, /56, /64, or /96), and those layouts overlap: the\n * same bits are a public IPv4 under one length and a private IPv4 under\n * another. Decoding one length therefore leaves a bypass, and decoding\n * every length blocks public addresses such as 1.0.0.1. This prefix is not\n * globally routed — public IPv4 uses the well-known prefix `64:ff9b::/96`,\n * which is decoded above — so refusing the entire local-use range closes\n * every embedding without a configured prefix length.\n */\nfunction isLocalUseNat64(groups: Array<number>): boolean {\n const [a, b, c] = groups;\n return a === 0x64 && b === 0xff9b && c === 0x0001;\n}\n\nfunction isBlockedIpv6(ip: string): boolean {\n // Strip zone index (e.g. fe80::1%eth0).\n const addr = ip.split(\"%\")[0];\n // Normalize to 8 groups so hex/dotted/compressed/uppercase spellings of\n // the same address are classified identically (e.g. ::ffff:7f00:1 and\n // ::ffff:127.0.0.1 are both IPv4-mapped loopback).\n const groups = expandIpv6(addr);\n if (!groups) {\n // Not a parseable IPv6 literal; block conservatively.\n return true;\n }\n // Embedded IPv4 (mapped, compatible, or the NAT64 well-known prefix) ->\n // apply IPv4 rules. This also covers :: (0.0.0.0) and ::1 (0.0.0.1),\n // both within blocked 0.0.0.0/8.\n const embedded = extractEmbeddedIpv4(groups);\n if (embedded) {\n return isBlockedIpv4(embedded);\n }\n // Local-use NAT64 (RFC 8215). See isLocalUseNat64.\n if (isLocalUseNat64(groups)) {\n return true;\n }\n // fc00::/7 unique-local.\n if ((groups[0] & 0xfe00) === 0xfc00) return true;\n // fe80::/10 link-local.\n if ((groups[0] & 0xffc0) === 0xfe80) return true;\n return false;\n}\n",
6
+ "import { lookup as dnsLookup, type LookupAddress } from \"node:dns\";\nimport { isIP, type LookupFunction } from \"node:net\";\nimport { Agent, buildConnector } from \"undici\";\nimport { isBlockedAddress } from \"./address.js\";\n\n/** Default response-body cap (5 MiB) applied by a guarded dispatcher. */\nexport const DEFAULT_MAX_RESPONSE_BYTES = 5 * 1024 * 1024;\n\nexport interface GuardedDispatcherOptions {\n /**\n * When true, skip the private/loopback destination checks. Dev/test escape\n * hatch only — never enable on a server that accepts untrusted requests.\n */\n allowPrivateAddresses?: boolean;\n /** Cap on response body size in bytes. Defaults to 5 MiB. */\n maxResponseBytes?: number;\n}\n\ntype LookupOptions = { all?: boolean; family?: number } & Record<\n string,\n unknown\n>;\ntype LookupCallback = (\n err: NodeJS.ErrnoException | null,\n address?: string | Array<LookupAddress>,\n family?: number,\n) => void;\n\n/**\n * A DNS lookup that resolves a hostname and rejects the connection if any\n * resolved address is private/loopback/blocked. Because this runs at the\n * connection layer, it validates the address actually being connected to —\n * including on every redirect hop — closing the check-then-fetch (TOCTOU)\n * rebinding gap that a pre-fetch URL check leaves open.\n *\n * Exported for direct unit testing: the Bun test runner intercepts `fetch` and\n * ignores undici dispatchers, so the lookup decision is verified here rather\n * than through an end-to-end fetch.\n */\nexport function createGuardedLookup(\n allowPrivateAddresses: boolean,\n): LookupFunction {\n return (\n hostname: string,\n // net.connect passes a LookupOptions object; the legacy dns.lookup\n // signature allows a numeric `family`, which we preserve.\n options: LookupOptions | number,\n callback: LookupCallback,\n ): void => {\n const opts: LookupOptions =\n typeof options === \"number\" ? { family: options } : (options ?? {});\n dnsLookup(hostname, { ...opts, all: true }, (err, addresses) => {\n if (err) {\n callback(err);\n return;\n }\n if (!addresses || addresses.length === 0) {\n callback(\n Object.assign(new Error(`could not resolve ${hostname}`), {\n code: \"ENOTFOUND\",\n }),\n );\n return;\n }\n if (!allowPrivateAddresses) {\n for (const { address } of addresses) {\n if (isBlockedAddress(address)) {\n callback(\n new Error(\n `blocked non-public address ${address} for ${hostname}`,\n ),\n );\n return;\n }\n }\n }\n if (opts.all) {\n callback(null, addresses);\n } else {\n callback(null, addresses[0].address, addresses[0].family);\n }\n });\n };\n}\n\ntype Connector = ReturnType<typeof buildConnector>;\n\n/**\n * Wrap Undici's connector so IP-literal hosts are checked before Node opens a\n * socket. Node bypasses custom DNS lookup functions for literals, so the\n * lookup guard alone does not cover these destinations.\n *\n * Exported for direct unit testing with an injected connector.\n */\nexport function createGuardedConnector(\n allowPrivateAddresses: boolean,\n connector: Connector = buildConnector({\n lookup: createGuardedLookup(allowPrivateAddresses),\n }),\n): Connector {\n return (options, callback): void => {\n const hostname = options.hostname.replace(/^\\[|\\]$/g, \"\");\n if (\n !allowPrivateAddresses &&\n isIP(hostname) !== 0 &&\n isBlockedAddress(hostname)\n ) {\n callback(new Error(`blocked non-public address ${hostname}`), null);\n return;\n }\n connector(options, callback);\n };\n}\n\n/**\n * Build an undici {@link Agent} that defends a server fetching client-supplied\n * URLs against SSRF and memory exhaustion:\n *\n * - Every connection (including each redirect hop) resolves DNS and refuses to\n * connect to a private/loopback/link-local/metadata address (unless\n * `allowPrivateAddresses` is set).\n * - Responses larger than `maxResponseBytes` are aborted.\n *\n * Pass the returned dispatcher to undici's own `fetch` (`import { fetch } from\n * \"undici\"`) as `{ dispatcher }`, or to any library that forwards fetch\n * options to undici (e.g. open-graph-scraper's `fetchOptions`). Do not hand\n * it to the runtime's global `fetch`: Node bundles its own undici, and when\n * that major differs from ours (Node 26 ships undici 8) the response comes\n * back with empty headers. `safeFetch` already does the right thing.\n */\nexport function createGuardedDispatcher(\n options: GuardedDispatcherOptions = {},\n): Agent {\n const {\n allowPrivateAddresses = false,\n maxResponseBytes = DEFAULT_MAX_RESPONSE_BYTES,\n } = options;\n return new Agent({\n maxResponseSize: maxResponseBytes,\n connect: createGuardedConnector(allowPrivateAddresses),\n });\n}\n",
7
+ "import { type Agent, fetch as undiciFetch } from \"undici\";\nimport { createGuardedDispatcher } from \"./dispatcher.js\";\nimport { assertHttpUrl, redactUrl } from \"./url.js\";\n\nexport interface SafeFetchOptions {\n /**\n * Skip the private/loopback destination checks. Dev/test escape hatch only.\n * Ignored when an explicit `dispatcher` is provided.\n */\n allowPrivateAddresses?: boolean;\n /** Response body cap in bytes. Ignored when `dispatcher` is provided. */\n maxResponseBytes?: number;\n /** Per-request timeout (applied via AbortSignal.timeout). */\n timeoutMs?: number;\n /**\n * A pre-built guarded dispatcher to reuse (for connection pooling across\n * requests). When omitted, a guarded dispatcher is created per call from\n * `allowPrivateAddresses`/`maxResponseBytes`.\n */\n dispatcher?: Agent;\n /** Extra request headers. */\n headers?: Record<string, string>;\n}\n\n/**\n * SSRF- and resource-guarded outbound fetch for client-supplied URLs:\n *\n * - rejects non-`http(s)` schemes and malformed URLs before connecting;\n * - routes the request through a guarded undici dispatcher that refuses\n * private/loopback/metadata destinations (every redirect hop) and caps the\n * response body;\n * - applies an optional timeout and throws on a non-2xx response.\n *\n * Returns the `Response` (read the body with `.text()`/`.json()`/etc.).\n */\nexport async function safeFetch(\n url: string,\n options: SafeFetchOptions = {},\n): Promise<Response> {\n assertHttpUrl(url);\n\n const dispatcher =\n options.dispatcher ??\n createGuardedDispatcher({\n allowPrivateAddresses: options.allowPrivateAddresses,\n maxResponseBytes: options.maxResponseBytes,\n });\n\n const init: RequestInit & { dispatcher?: Agent } = { dispatcher };\n if (options.timeoutMs !== undefined && options.timeoutMs > 0) {\n init.signal = AbortSignal.timeout(options.timeoutMs);\n }\n if (options.headers) {\n init.headers = options.headers;\n }\n\n // Use undici's own `fetch` rather than the runtime global. The dispatcher\n // is an undici Agent from *our* undici dependency; handing it to a global\n // `fetch` backed by a different undici major (e.g. Node 26 ships undici 8)\n // yields responses with empty headers. Keeping request and dispatcher in\n // the same undici build avoids that mismatch on every Node release.\n const res = (await undiciFetch(url, init)) as unknown as Response;\n if (!res.ok) {\n // Drain the unused body so the keep-alive connection is returned to the\n // pool instead of leaking until GC.\n await res.body?.cancel().catch(() => {});\n // HTTP/2 has no reason phrases, so statusText may be empty.\n const statusText = res.statusText ? ` ${res.statusText}` : \"\";\n throw new Error(\n `request failed: ${res.status}${statusText} for ${redactUrl(url)}`,\n );\n }\n return res;\n}\n",
8
+ "/**\n * Strip any `user:password@` userinfo from a URL so it is safe to include in\n * error messages, logs, or telemetry. Best-effort for unparseable input.\n */\nexport function redactUrl(url: string): string {\n try {\n const parsed = new URL(url);\n if (parsed.username || parsed.password) {\n parsed.username = \"\";\n parsed.password = \"\";\n return parsed.toString();\n }\n return url;\n } catch {\n return url.replace(/\\/\\/[^/@\\s]*@/, \"//***@\");\n }\n}\n\n/**\n * Parse `url` and require an `http:`/`https:` scheme, returning the parsed\n * `URL`. Throws a descriptive `Error` (with credentials redacted) on a\n * malformed URL or unsupported scheme. Used to gate outbound fetches of\n * client-supplied URLs.\n */\nexport function assertHttpUrl(url: string): URL {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new Error(`invalid URL: ${redactUrl(url)}`);\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new Error(\n `unsupported scheme ${parsed.protocol} for ${redactUrl(url)}`,\n );\n }\n return parsed;\n}\n\n/** Returns true if `url` parses and uses an `http:`/`https:` scheme. */\nexport function isHttpUrl(url: string): boolean {\n try {\n assertHttpUrl(url);\n return true;\n } catch {\n return false;\n }\n}\n"
9
9
  ],
10
- "mappings": "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",
11
- "debugId": "B14C9452A8082A2064756E2164756E21",
10
+ "mappings": "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",
11
+ "debugId": "FC93FACCBD6A33AF64756E2164756E21",
12
12
  "names": []
13
13
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sockethub/util",
3
3
  "description": "Shared utilities for Sockethub (networking/SSRF helpers, etc.)",
4
- "version": "1.0.0-alpha.9",
4
+ "version": "1.0.0-beta.1",
5
5
  "type": "module",
6
6
  "private": false,
7
7
  "author": "Nick Jennings <nick@silverbucket.net>",
@@ -60,7 +60,7 @@
60
60
  "dependencies": {
61
61
  "object-hash": "^3.0.0",
62
62
  "secure-store-redis": "^4.2.0",
63
- "undici": "^7.19.0"
63
+ "undici": "^7.30.0"
64
64
  },
65
65
  "devDependencies": {
66
66
  "@types/bun": "latest",
@@ -68,5 +68,5 @@
68
68
  "@types/sinon": "^17.0.4",
69
69
  "sinon": "^17.0.2"
70
70
  },
71
- "gitHead": "1d87ef9d415159ef624e86e929ef2a2fb7a4b50e"
71
+ "gitHead": "928f0c480e4828f492fb29a97712474a8f1bcaef"
72
72
  }