@sockethub/util 1.0.0-alpha.7 → 1.0.0-alpha.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -136,7 +136,8 @@ function isBlockedIpv6(ip) {
136
136
  }
137
137
  // src/net/dispatcher.ts
138
138
  import { lookup as dnsLookup } from "node:dns";
139
- import { Agent } from "undici";
139
+ import { isIP } from "node:net";
140
+ import { Agent, buildConnector } from "undici";
140
141
  var DEFAULT_MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
141
142
  function createGuardedLookup(allowPrivateAddresses) {
142
143
  return (hostname, options, callback) => {
@@ -168,6 +169,18 @@ function createGuardedLookup(allowPrivateAddresses) {
168
169
  });
169
170
  };
170
171
  }
172
+ function createGuardedConnector(allowPrivateAddresses, connector = buildConnector({
173
+ lookup: createGuardedLookup(allowPrivateAddresses)
174
+ })) {
175
+ return (options, callback) => {
176
+ const hostname = options.hostname.replace(/^\[|\]$/g, "");
177
+ if (!allowPrivateAddresses && isIP(hostname) !== 0 && isBlockedAddress(hostname)) {
178
+ callback(new Error(`blocked non-public address ${hostname}`), null);
179
+ return;
180
+ }
181
+ connector(options, callback);
182
+ };
183
+ }
171
184
  function createGuardedDispatcher(options = {}) {
172
185
  const {
173
186
  allowPrivateAddresses = false,
@@ -175,9 +188,7 @@ function createGuardedDispatcher(options = {}) {
175
188
  } = options;
176
189
  return new Agent({
177
190
  maxResponseSize: maxResponseBytes,
178
- connect: {
179
- lookup: createGuardedLookup(allowPrivateAddresses)
180
- }
191
+ connect: createGuardedConnector(allowPrivateAddresses)
181
192
  });
182
193
  }
183
194
  // src/net/url.ts
@@ -249,4 +260,4 @@ export {
249
260
  DEFAULT_MAX_RESPONSE_BYTES
250
261
  };
251
262
 
252
- //# debugId=FDAE0275EA985B4364756E2164756E21
263
+ //# debugId=F9B45AF6B37619AE64756E2164756E21
package/dist/index.js.map CHANGED
@@ -4,11 +4,11 @@
4
4
  "sourcesContent": [
5
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
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 type { LookupFunction } from \"node:net\";\nimport { Agent } 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\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: {\n lookup: createGuardedLookup(allowPrivateAddresses),\n },\n });\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
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
  "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"
10
10
  ],
11
- "mappings": "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",
12
- "debugId": "FDAE0275EA985B4364756E2164756E21",
11
+ "mappings": "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",
12
+ "debugId": "F9B45AF6B37619AE64756E2164756E21",
13
13
  "names": []
14
14
  }
@@ -1,5 +1,5 @@
1
- import type { LookupFunction } from "node:net";
2
- import { Agent } from "undici";
1
+ import { type LookupFunction } from "node:net";
2
+ import { Agent, buildConnector } from "undici";
3
3
  /** Default response-body cap (5 MiB) applied by a guarded dispatcher. */
4
4
  export declare const DEFAULT_MAX_RESPONSE_BYTES: number;
5
5
  export interface GuardedDispatcherOptions {
@@ -23,6 +23,15 @@ export interface GuardedDispatcherOptions {
23
23
  * than through an end-to-end fetch.
24
24
  */
25
25
  export declare function createGuardedLookup(allowPrivateAddresses: boolean): LookupFunction;
26
+ type Connector = ReturnType<typeof buildConnector>;
27
+ /**
28
+ * Wrap Undici's connector so IP-literal hosts are checked before Node opens a
29
+ * socket. Node bypasses custom DNS lookup functions for literals, so the
30
+ * lookup guard alone does not cover these destinations.
31
+ *
32
+ * Exported for direct unit testing with an injected connector.
33
+ */
34
+ export declare function createGuardedConnector(allowPrivateAddresses: boolean, connector?: Connector): Connector;
26
35
  /**
27
36
  * Build an undici {@link Agent} that defends a server fetching client-supplied
28
37
  * URLs against SSRF and memory exhaustion:
@@ -37,3 +46,4 @@ export declare function createGuardedLookup(allowPrivateAddresses: boolean): Loo
37
46
  * `fetchOptions`).
38
47
  */
39
48
  export declare function createGuardedDispatcher(options?: GuardedDispatcherOptions): Agent;
49
+ export {};
package/dist/net/index.js CHANGED
@@ -122,7 +122,8 @@ function isBlockedIpv6(ip) {
122
122
  }
123
123
  // src/net/dispatcher.ts
124
124
  import { lookup as dnsLookup } from "node:dns";
125
- import { Agent } from "undici";
125
+ import { isIP } from "node:net";
126
+ import { Agent, buildConnector } from "undici";
126
127
  var DEFAULT_MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
127
128
  function createGuardedLookup(allowPrivateAddresses) {
128
129
  return (hostname, options, callback) => {
@@ -154,6 +155,18 @@ function createGuardedLookup(allowPrivateAddresses) {
154
155
  });
155
156
  };
156
157
  }
158
+ function createGuardedConnector(allowPrivateAddresses, connector = buildConnector({
159
+ lookup: createGuardedLookup(allowPrivateAddresses)
160
+ })) {
161
+ return (options, callback) => {
162
+ const hostname = options.hostname.replace(/^\[|\]$/g, "");
163
+ if (!allowPrivateAddresses && isIP(hostname) !== 0 && isBlockedAddress(hostname)) {
164
+ callback(new Error(`blocked non-public address ${hostname}`), null);
165
+ return;
166
+ }
167
+ connector(options, callback);
168
+ };
169
+ }
157
170
  function createGuardedDispatcher(options = {}) {
158
171
  const {
159
172
  allowPrivateAddresses = false,
@@ -161,9 +174,7 @@ function createGuardedDispatcher(options = {}) {
161
174
  } = options;
162
175
  return new Agent({
163
176
  maxResponseSize: maxResponseBytes,
164
- connect: {
165
- lookup: createGuardedLookup(allowPrivateAddresses)
166
- }
177
+ connect: createGuardedConnector(allowPrivateAddresses)
167
178
  });
168
179
  }
169
180
  // src/net/url.ts
@@ -233,4 +244,4 @@ export {
233
244
  DEFAULT_MAX_RESPONSE_BYTES
234
245
  };
235
246
 
236
- //# debugId=1EC350DEADE3199364756E2164756E21
247
+ //# debugId=8E1F566D6E4AE88564756E2164756E21
@@ -3,11 +3,11 @@
3
3
  "sources": ["../src/net/address.ts", "../src/net/dispatcher.ts", "../src/net/url.ts", "../src/net/fetch.ts"],
4
4
  "sourcesContent": [
5
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 type { LookupFunction } from \"node:net\";\nimport { Agent } 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\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: {\n lookup: createGuardedLookup(allowPrivateAddresses),\n },\n });\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
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
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"
9
9
  ],
10
- "mappings": "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",
11
- "debugId": "1EC350DEADE3199364756E2164756E21",
10
+ "mappings": "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",
11
+ "debugId": "8E1F566D6E4AE88564756E2164756E21",
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.7",
4
+ "version": "1.0.0-alpha.8",
5
5
  "type": "module",
6
6
  "private": false,
7
7
  "author": "Nick Jennings <nick@silverbucket.net>",
@@ -68,5 +68,5 @@
68
68
  "@types/sinon": "^17.0.4",
69
69
  "sinon": "^17.0.2"
70
70
  },
71
- "gitHead": "8e315bd27fbf9324079f2a5a4a09f3c6057c6500"
71
+ "gitHead": "d76d6df18bc0dade5ab4fd6a16f5664dc5b45c33"
72
72
  }