{"version":3,"file":"index.mjs","names":["undiciFetch"],"sources":["../src/errors.ts","../src/ip-address.ts","../src/url-blocked.ts","../src/hostname-safety.ts","../src/assert-url-safe.ts","../src/guarded-lookup.ts","../src/guarded-dispatcher.ts","../src/blocked-headers.ts","../src/sanitize-headers.ts","../src/guarded-fetch.ts","../src/read-body.ts","../src/guarded-fetch-helpers.ts"],"sourcesContent":["/**\n * Error codes for {@link GuardedFetchError}.\n *\n * Codes are stable strings so they can be switched on in callers and logged.\n */\nexport const GuardedFetchErrorCode = {\n  /** The URL string could not be parsed. */\n  INVALID_URL: 'invalid_url',\n  /** The URL's protocol is not allowed (`http:`/`https:` by default; `https:` only when `httpsOnly: true`). */\n  PROTOCOL_NOT_ALLOWED: 'protocol_not_allowed',\n  /** The URL's hostname is not in the caller-provided allowlist. */\n  HOST_NOT_ALLOWED: 'host_not_allowed',\n  /**\n   * The hostname DNS-resolves to a private, loopback, or link-local address\n   * (or is `localhost` / `.local`). This is the primary SSRF guard.\n   */\n  HOSTNAME_UNSAFE: 'hostname_unsafe',\n  /** The request was aborted because it exceeded `timeoutMs`. */\n  TIMEOUT: 'timeout',\n  /** A redirect chain exceeded `maxRedirects`. */\n  TOO_MANY_REDIRECTS: 'too_many_redirects',\n  /** A redirect `Location` header was missing, invalid, or used a disallowed protocol. */\n  REDIRECT_INVALID: 'redirect_invalid',\n  /**\n   * A redirect `Location` pointed at an unsafe host (private/loopback/link-local,\n   * or outside the allowlist). Distinct from `HOSTNAME_UNSAFE` so callers can\n   * tell whether the original URL or a redirect was the problem.\n   */\n  REDIRECT_TO_UNSAFE_HOST: 'redirect_to_unsafe_host',\n  /** The response body exceeded `maxResponseBytes`. Only raised by convenience readers. */\n  RESPONSE_TOO_LARGE: 'response_too_large',\n  /** A network-level failure occurred (DNS, connection, TLS, socket). */\n  NETWORK_ERROR: 'network_error',\n} as const;\n\nexport type GuardedFetchErrorCode =\n  (typeof GuardedFetchErrorCode)[keyof typeof GuardedFetchErrorCode];\n\n/**\n * Error thrown by {@link guardedFetch} and related helpers.\n *\n * All failure modes — SSRF rejection, timeout, oversized body, redirect abuse —\n * surface as `GuardedFetchError` with a stable `code`. Callers should switch on\n * `code` rather than parsing `message`.\n */\nexport class GuardedFetchError extends Error {\n  readonly code: GuardedFetchErrorCode;\n  readonly hostname?: string;\n  readonly url?: string;\n  readonly status?: number;\n\n  constructor(\n    code: GuardedFetchErrorCode,\n    message: string,\n    details: {\n      hostname?: string;\n      url?: string;\n      status?: number;\n      cause?: unknown;\n    } = {},\n  ) {\n    super(message, details.cause !== undefined ? { cause: details.cause } : {});\n    this.name = 'GuardedFetchError';\n    this.code = code;\n    this.hostname = details.hostname;\n    this.url = details.url;\n    this.status = details.status;\n  }\n}\n\n/**\n * Type guard for {@link GuardedFetchError}. Resilient to multiple package copies\n * (e.g. in tests that bundle the module twice) because it also accepts any\n * `Error` with `name === 'GuardedFetchError'` and a string `code`.\n */\nexport function isGuardedFetchError(\n  value: unknown,\n): value is GuardedFetchError {\n  if (value instanceof GuardedFetchError) {\n    return true;\n  }\n  return (\n    value instanceof Error &&\n    value.name === 'GuardedFetchError' &&\n    typeof (value as { code?: unknown }).code === 'string'\n  );\n}\n\n/**\n * Generic opaque message used when `opaqueErrors: true`. Avoids leaking DNS\n * resolution, port, or redirect details that could be probed via timing or\n * error comparison (e.g. SSRF reconnaissance through error messages that\n * reach an attacker-visible log drain).\n */\nexport const OPAQUE_ERROR_MESSAGE =\n  'The URL could not be fetched: either it is not reachable or the request was rejected by policy.';\n\n/**\n * Failure codes that cannot succeed on retry: the URL itself is unfetchable\n * (SSRF-blocked, malformed, redirect abuse) or the response exceeded a hard\n * limit. Timeouts and network-level failures are excluded — they may succeed\n * on retry.\n */\nconst PERMANENT_FAILURE_CODES: ReadonlySet<GuardedFetchErrorCode> = new Set([\n  GuardedFetchErrorCode.INVALID_URL,\n  GuardedFetchErrorCode.PROTOCOL_NOT_ALLOWED,\n  GuardedFetchErrorCode.HOST_NOT_ALLOWED,\n  GuardedFetchErrorCode.HOSTNAME_UNSAFE,\n  GuardedFetchErrorCode.TOO_MANY_REDIRECTS,\n  GuardedFetchErrorCode.REDIRECT_INVALID,\n  GuardedFetchErrorCode.REDIRECT_TO_UNSAFE_HOST,\n  GuardedFetchErrorCode.RESPONSE_TOO_LARGE,\n]);\n\n/**\n * True when `error` is a {@link GuardedFetchError} whose failure is permanent —\n * retrying the same URL cannot succeed. Callers deciding between \"retry\n * later\" and \"record a permanent failure\" should treat everything else\n * (timeouts, network errors, non-GuardedFetch errors) as potentially transient.\n */\nexport function isPermanentGuardedFetchError(error: unknown): boolean {\n  return isGuardedFetchError(error) && PERMANENT_FAILURE_CODES.has(error.code);\n}\n","import net from 'node:net';\n\nimport ipaddr from 'ipaddr.js';\n\n/**\n * Fallback classifier: anything `ipaddr.js` does not consider plain global\n * `unicast` (private, loopback, link-local, unique-local, multicast,\n * unspecified, broadcast, CGNAT, reserved, documentation, …) is unsafe.\n * Unparseable input is unsafe.\n */\nfunction isNonUnicastAddress(address: string): boolean {\n  try {\n    return ipaddr.parse(address).range() !== 'unicast';\n  } catch {\n    return true;\n  }\n}\n\n/**\n * IPv4 ranges checked explicitly so classification does not depend on any\n * one library's range table: `0.0.0.0/8` (Linux routes `0.0.0.0` to\n * loopback), `100.64.0.0/10` CGNAT, `192.0.0.0/24` (IETF protocol\n * assignments), `198.18.0.0/15` (benchmark), `224.0.0.0/4` multicast, and\n * `240.0.0.0/4` reserved (which includes `255.255.255.255`).\n */\nfunction isUnsafeIpv4Address(v4: string): boolean {\n  const parts = v4.split('.');\n  if (parts.length !== 4) {\n    return false;\n  }\n  const octets = parts.map((p) => Number.parseInt(p, 10));\n  if (octets.some((n) => !Number.isInteger(n) || n < 0 || n > 255)) {\n    return false;\n  }\n  const a = octets[0] as number;\n  const b = octets[1] as number;\n  const c = octets[2] as number;\n  if (a === 0) {\n    return true;\n  }\n  if (a === 100 && b >= 64 && b <= 127) {\n    return true;\n  }\n  if (a === 192 && b === 0 && c === 0) {\n    return true;\n  }\n  if (a === 198 && b >= 18 && b <= 19) {\n    return true;\n  }\n  if (a >= 224) {\n    return true;\n  }\n  return isNonUnicastAddress(v4);\n}\n\nfunction normalizeIpv6Literal(address: string): string {\n  if (net.isIP(address) !== 6) {\n    return address.toLowerCase();\n  }\n  try {\n    return new URL(`https://[${address}]/`).hostname.slice(1, -1).toLowerCase();\n  } catch {\n    return address.toLowerCase();\n  }\n}\n\nfunction expandIpv6Hextets(v6: string): number[] | null {\n  const withoutZone = normalizeIpv6Literal(v6).split('%')[0] ?? '';\n  if (!withoutZone.includes('::')) {\n    const parts = withoutZone.split(':');\n    if (parts.length !== 8) {\n      return null;\n    }\n    return parts.map((part) => Number.parseInt(part, 16));\n  }\n\n  const [head, tail] = withoutZone.split('::');\n  const headParts = head ? head.split(':') : [];\n  const tailParts = tail ? tail.split(':') : [];\n  const missing = 8 - headParts.length - tailParts.length;\n  if (missing < 0) {\n    return null;\n  }\n  const all = [...headParts, ...Array(missing).fill('0'), ...tailParts];\n  if (all.length !== 8) {\n    return null;\n  }\n  return all.map((part) => Number.parseInt(part || '0', 16));\n}\n\nfunction ipv4FromHextetPair(hi: number, lo: number): string {\n  return `${(hi >> 8) & 0xff}.${hi & 0xff}.${(lo >> 8) & 0xff}.${lo & 0xff}`;\n}\n\n/**\n * Decodes IPv4 addresses embedded in 6to4 (`2002::/16`) and the NAT64\n * well-known prefix (`64:ff9b::/96`) so they can be checked with the same\n * IPv4 classifier as `::ffff:` mapped forms.\n *\n * The RFC 8215 local-use prefix `64:ff9b:1::/48` is not decoded here — it is\n * rejected wholesale by {@link isUnsafeIpv6Address}. Its translation-prefix\n * length is chosen by the deployment, so the embedded IPv4 could sit at any\n * of the RFC 6052 §2.2 offsets and there is no way to tell which from the\n * literal alone.\n */\nfunction tryDecodeEmbeddedIpv4FromIpv6(v6: string): string | null {\n  const hextets = expandIpv6Hextets(v6);\n  if (!hextets) {\n    return null;\n  }\n\n  const [h0, h1, h2, h3, h4, h5, h6, h7] = hextets;\n\n  // 6to4 — RFC 3056: 2002:<ipv4-high>:<ipv4-low>:...\n  if (h0 === 0x2002) {\n    return ipv4FromHextetPair(h1 as number, h2 as number);\n  }\n\n  // NAT64 well-known prefix — RFC 6052: 64:ff9b::<ipv4>\n  if (\n    h0 === 0x0064 &&\n    h1 === 0xff9b &&\n    h2 === 0 &&\n    h3 === 0 &&\n    h4 === 0 &&\n    h5 === 0\n  ) {\n    return ipv4FromHextetPair(h6 as number, h7 as number);\n  }\n\n  return null;\n}\n\n/**\n * IPv6 ranges checked explicitly: unique local `fc00::/7`, link-local\n * `fe80::/10`, deprecated site-local `fec0::/10`, multicast `ff00::/8`,\n * documentation `2001:db8::/32`, and the NAT64 local-use prefix\n * `64:ff9b:1::/48`.\n */\nfunction isUnsafeIpv6Address(v6: string): boolean {\n  const lower = normalizeIpv6Literal(v6);\n\n  if (lower === '::' || lower === '::1') {\n    return true;\n  }\n\n  // 64:ff9b:1::/48 — RFC 8215 local-use NAT64. `ipaddr.js` classifies this as\n  // ordinary unicast, but a translator on the path rewrites it to whatever\n  // IPv4 address is encoded in the low bits — including private and metadata\n  // space. The prefix length is deployment-defined, so the embedded IPv4 can\n  // sit at any RFC 6052 §2.2 offset and cannot be recovered reliably from the\n  // literal. Reject the range outright: it addresses a local translator and\n  // is never a legitimate target for an outbound fetch.\n  if (/^64:ff9b:1(?::|$)/i.test(lower)) {\n    return true;\n  }\n\n  // fc00::/7 — unique local\n  if (lower.startsWith('fc') || lower.startsWith('fd')) {\n    return true;\n  }\n\n  // fe80::/10 — link-local\n  if (/^fe[89ab]/i.test(lower)) {\n    return true;\n  }\n\n  // fec0::/10 — deprecated site-local\n  if (/^fe[c-f]/i.test(lower)) {\n    return true;\n  }\n\n  // ff00::/8 — multicast\n  if (lower.startsWith('ff')) {\n    return true;\n  }\n\n  // 2001:db8::/32 — documentation\n  if (/^2001:0?db8(?::|$)/i.test(lower)) {\n    return true;\n  }\n\n  return isNonUnicastAddress(v6);\n}\n\n/**\n * IP-classification primitive for SSRF guards. Explicitly handles ranges\n * unsafe for server-side fetches (`0.0.0.0/8`, CGNAT, multicast,\n * reserved/broadcast, benchmark, IETF assignments), the WHATWG-normalized\n * IPv4-mapped IPv6 forms (dotted `::ffff:a.b.c.d` and hex `::ffff:a9fe:a9fe`),\n * and 6to4 (`2002::/16`) / NAT64 (`64:ff9b::/96`) embedded IPv4 literals,\n * with `ipaddr.js` non-unicast classification as the fallback.\n */\nexport function isAddressUnsafeForServerSideFetch(address: string): boolean {\n  if (!address) {\n    return true;\n  }\n\n  if (net.isIP(address) === 4) {\n    return isUnsafeIpv4Address(address);\n  }\n\n  const lower = address.toLowerCase();\n\n  const dottedMappedIpv4 = lower.match(\n    /^::ffff:(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})$/,\n  );\n  if (dottedMappedIpv4) {\n    return isUnsafeIpv4Address(dottedMappedIpv4[1] as string);\n  }\n\n  const hexMappedIpv4 = lower.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);\n  if (hexMappedIpv4) {\n    const hi = Number.parseInt(hexMappedIpv4[1] ?? '0', 16);\n    const lo = Number.parseInt(hexMappedIpv4[2] ?? '0', 16);\n    const v4 = `${(hi >> 8) & 0xff}.${hi & 0xff}.${(lo >> 8) & 0xff}.${\n      lo & 0xff\n    }`;\n    return isUnsafeIpv4Address(v4);\n  }\n\n  if (net.isIP(address) === 6) {\n    const embeddedIpv4 = tryDecodeEmbeddedIpv4FromIpv6(address);\n    if (embeddedIpv4 !== null) {\n      return isUnsafeIpv4Address(embeddedIpv4);\n    }\n    return isUnsafeIpv6Address(address);\n  }\n\n  return isNonUnicastAddress(address);\n}\n\n/**\n * Returns `true` if `address` is safe to connect to from server-side code\n * (i.e. not loopback, not private, not link-local).\n */\nexport function isSafeIpAddress(address: string): boolean {\n  return !isAddressUnsafeForServerSideFetch(address);\n}\n","import { GuardedFetchErrorCode } from './errors';\n\n/** Suggested log message for {@link UrlBlockedHandler} implementations. */\nexport const URL_BLOCKED_LOG_MESSAGE = 'guarded-fetch blocked URL' as const;\n\n/**\n * Sub-reasons for {@link GuardedFetchErrorCode.HOSTNAME_UNSAFE}. Public\n * {@link GuardedFetchError.code} stays `hostname_unsafe`; use `subReason` in\n * block events to distinguish block paths.\n */\nexport const HostnameUnsafeSubReason = {\n  INVALID: 'hostname_invalid',\n  LOCALHOST: 'hostname_localhost',\n  LOCAL_DOMAIN: 'hostname_local_domain',\n  IP_LITERAL_UNSAFE: 'ip_literal_unsafe',\n  DNS_UNSAFE_ADDRESS: 'dns_unsafe_address',\n  DNS_RESOLUTION_FAILED: 'dns_resolution_failed',\n  CONNECT_TIME_IP_REJECTED: 'connect_time_ip_rejected',\n} as const;\n\nexport type HostnameUnsafeSubReason =\n  (typeof HostnameUnsafeSubReason)[keyof typeof HostnameUnsafeSubReason];\n\n/** Block-event `subReason`: public error code or hostname-unsafe detail. */\nexport type UrlBlockedSubReason =\n  | GuardedFetchErrorCode\n  | HostnameUnsafeSubReason;\n\n/** GuardedFetch error codes that emit block events (SSRF, allowlist, protocol, redirect abuse). */\nexport const URL_BLOCKED_REASONS: ReadonlySet<GuardedFetchErrorCode> = new Set([\n  GuardedFetchErrorCode.INVALID_URL,\n  GuardedFetchErrorCode.PROTOCOL_NOT_ALLOWED,\n  GuardedFetchErrorCode.HOST_NOT_ALLOWED,\n  GuardedFetchErrorCode.HOSTNAME_UNSAFE,\n  GuardedFetchErrorCode.REDIRECT_TO_UNSAFE_HOST,\n  GuardedFetchErrorCode.TOO_MANY_REDIRECTS,\n  GuardedFetchErrorCode.REDIRECT_INVALID,\n]);\n\n/** Payload for {@link UrlBlockedHandler} and {@link setUrlBlockedHandler}. */\nexport type UrlBlockedEvent = {\n  reason: GuardedFetchErrorCode;\n  domain: string;\n  subReason?: UrlBlockedSubReason;\n};\n\n/** Optional logging hook for security block events. Must not throw. */\nexport type UrlBlockedHandler = (event: UrlBlockedEvent) => void;\n\nlet globalUrlBlockedHandler: UrlBlockedHandler | undefined;\n\n/**\n * Registers a process-wide handler for security block events. Call once at\n * service/app startup (e.g. next to logger init). Per-call overrides via\n * {@link AssertUrlSafeOptions.onUrlBlocked} take precedence.\n */\nexport function setUrlBlockedHandler(\n  handler: UrlBlockedHandler | undefined,\n): void {\n  globalUrlBlockedHandler = handler;\n}\n\n/**\n * Lower-cased hostname for block events. Falls back to parsing `url`, then\n * `'unknown'` when the input is not a valid URL string.\n */\nexport function resolveBlockedDomain(details: {\n  hostname?: string;\n  url?: string;\n}): string {\n  if (details.hostname) {\n    return details.hostname.toLowerCase();\n  }\n  if (details.url) {\n    try {\n      return new URL(details.url).hostname.toLowerCase();\n    } catch {\n      return 'unknown';\n    }\n  }\n  return 'unknown';\n}\n\n/**\n * Dispatches a block event with `reason`, `domain`, and optional `subReason`\n * to `onUrlBlocked` when set, otherwise to the module-level handler from\n * {@link setUrlBlockedHandler}. Never throws.\n */\nexport function recordUrlBlocked(\n  reason: GuardedFetchErrorCode,\n  domain: string,\n  subReason?: UrlBlockedSubReason,\n  onUrlBlocked?: UrlBlockedHandler,\n): void {\n  if (!URL_BLOCKED_REASONS.has(reason)) {\n    return;\n  }\n\n  const normalizedDomain = domain || 'unknown';\n\n  const handler = onUrlBlocked ?? globalUrlBlockedHandler;\n  if (!handler) {\n    return;\n  }\n\n  try {\n    handler({\n      reason,\n      domain: normalizedDomain,\n      ...(subReason !== undefined ? { subReason } : {}),\n    });\n  } catch {\n    // Hooks must not affect fetch behavior.\n  }\n}\n","import type { LookupAddress } from 'node:dns';\nimport { lookup } from 'node:dns/promises';\nimport net from 'node:net';\n\nimport { isAddressUnsafeForServerSideFetch } from './ip-address';\nimport {\n  HostnameUnsafeSubReason,\n  type HostnameUnsafeSubReason as HostnameUnsafeSubReasonType,\n} from './url-blocked';\n\n/**\n * Normalizes a host or `https://host:port`-style value to a hostname for DNS\n * lookup. Kept package-local so guarded-fetch does not rely on monorepo helpers.\n */\nexport function parseHostnameForOutboundLookup(raw: string): string {\n  const trimmed = raw.trim();\n  if (!trimmed) {\n    return '';\n  }\n  try {\n    if (trimmed.includes('://')) {\n      return new URL(trimmed).hostname;\n    }\n    return new URL(`https://${trimmed}`).hostname;\n  } catch {\n    return trimmed.split(':')[0] ?? '';\n  }\n}\n\nexport type HostnameSafetyResult =\n  | { safe: true }\n  | { safe: false; subReason: HostnameUnsafeSubReasonType };\n\n/**\n * Classifies whether `hostname` is safe for server-side outbound HTTP(S).\n * Uses the same OS resolver path that undici's connect-time lookup uses.\n */\nexport async function checkHostnameSafetyForServerSideFetch(\n  hostname: string,\n): Promise<HostnameSafetyResult> {\n  const host = hostname.toLowerCase().replace(/^\\[/, '').replace(/\\]$/, '');\n  if (!host) {\n    return { safe: false, subReason: HostnameUnsafeSubReason.INVALID };\n  }\n  if (host === 'localhost' || host.endsWith('.localhost')) {\n    return { safe: false, subReason: HostnameUnsafeSubReason.LOCALHOST };\n  }\n  if (host.endsWith('.local')) {\n    return { safe: false, subReason: HostnameUnsafeSubReason.LOCAL_DOMAIN };\n  }\n\n  if (net.isIP(host)) {\n    return isAddressUnsafeForServerSideFetch(host)\n      ? { safe: false, subReason: HostnameUnsafeSubReason.IP_LITERAL_UNSAFE }\n      : { safe: true };\n  }\n\n  let addresses: LookupAddress[];\n  try {\n    addresses = await lookup(host, { all: true, verbatim: true });\n  } catch {\n    return {\n      safe: false,\n      subReason: HostnameUnsafeSubReason.DNS_RESOLUTION_FAILED,\n    };\n  }\n\n  if (addresses.length === 0) {\n    return {\n      safe: false,\n      subReason: HostnameUnsafeSubReason.DNS_RESOLUTION_FAILED,\n    };\n  }\n\n  if (\n    addresses.some(({ address }) => isAddressUnsafeForServerSideFetch(address))\n  ) {\n    return {\n      safe: false,\n      subReason: HostnameUnsafeSubReason.DNS_UNSAFE_ADDRESS,\n    };\n  }\n\n  return { safe: true };\n}\n","import {\n  OPAQUE_ERROR_MESSAGE,\n  GuardedFetchError,\n  GuardedFetchErrorCode,\n} from './errors';\nimport {\n  checkHostnameSafetyForServerSideFetch,\n  parseHostnameForOutboundLookup,\n} from './hostname-safety';\nimport {\n  HostnameUnsafeSubReason,\n  recordUrlBlocked,\n  resolveBlockedDomain,\n  type UrlBlockedHandler,\n  type UrlBlockedSubReason,\n} from './url-blocked';\n\n/**\n * Options controlling {@link assertUrlIsSafeToFetch}.\n */\nexport interface AssertUrlSafeOptions {\n  /**\n   * When `true`, only `https:` URLs are allowed. When `false` (the default),\n   * both `http:` and `https:` are allowed.\n   *\n   * @default false\n   */\n  httpsOnly?: boolean;\n\n  /**\n   * Optional hostname allowlist. If provided, the URL's hostname must either\n   * match an entry exactly (case-insensitive) or be a subdomain of one\n   * (`*.<entry>`). Passing an empty array treats every host as disallowed.\n   *\n   * If omitted, any publicly-resolving host passes.\n   */\n  allowedHosts?: readonly string[];\n\n  /**\n   * When a host matches {@link allowedHosts}, skip the DNS/SSRF check. Only\n   * safe for hosts under full organizational control (e.g. `*.example.com`\n   * when you own `example.com`).\n   *\n   * @default false\n   */\n  skipSsrfCheckForAllowedHosts?: boolean;\n\n  /**\n   * Return a generic error message for every failure mode to avoid leaking\n   * DNS, port, or reachability information to callers who can see error\n   * messages (e.g. via a user-facing log drain). The `code` field on the\n   * thrown error is preserved for internal logging.\n   *\n   * @default false\n   */\n  opaqueErrors?: boolean;\n\n  /**\n   * Called when this validation blocks a URL for security reasons. Overrides\n   * the module-level handler from {@link setUrlBlockedHandler} for this call\n   * only. Use for per-request context (team ID, webhook ID, etc.).\n   */\n  onUrlBlocked?: UrlBlockedHandler;\n}\n\nexport interface ValidatedFetchTarget {\n  /** The parsed, validated URL. Guaranteed to have an allowed protocol and safe host. */\n  url: URL;\n  /** Lower-cased hostname extracted from `url`. */\n  hostname: string;\n}\n\nconst HTTP_AND_HTTPS_PROTOCOLS: readonly string[] = ['http:', 'https:'];\nconst HTTPS_ONLY_PROTOCOLS: readonly string[] = ['https:'];\n\nfunction getAllowedProtocols(httpsOnly: boolean): readonly string[] {\n  return httpsOnly ? HTTPS_ONLY_PROTOCOLS : HTTP_AND_HTTPS_PROTOCOLS;\n}\n\n/**\n * Performs all pre-flight checks that {@link guardedFetch} runs internally,\n * **without** making an HTTP request. Useful for validating user-configured\n * URLs at write time (webhook endpoints, log drain destinations, OIDC\n * issuers, JWKS URIs, etc.).\n *\n * Throws {@link GuardedFetchError} on any failure.\n */\nexport async function assertUrlIsSafeToFetch(\n  rawUrl: string | URL,\n  options: AssertUrlSafeOptions = {},\n): Promise<ValidatedFetchTarget> {\n  const {\n    httpsOnly = false,\n    allowedHosts,\n    skipSsrfCheckForAllowedHosts = false,\n    opaqueErrors = false,\n    onUrlBlocked,\n  } = options;\n\n  const allowedProtocols = getAllowedProtocols(httpsOnly);\n\n  const fail = (\n    code: (typeof GuardedFetchErrorCode)[keyof typeof GuardedFetchErrorCode],\n    publicMessage: string,\n    details: { hostname?: string; url?: string; cause?: unknown } = {},\n    subReason?: UrlBlockedSubReason,\n  ): never => {\n    recordUrlBlocked(\n      code,\n      resolveBlockedDomain(details),\n      subReason,\n      onUrlBlocked,\n    );\n    throw new GuardedFetchError(\n      code,\n      opaqueErrors ? OPAQUE_ERROR_MESSAGE : publicMessage,\n      details,\n    );\n  };\n\n  let url: URL;\n  try {\n    url = rawUrl instanceof URL ? rawUrl : new URL(rawUrl);\n  } catch (cause) {\n    return fail(GuardedFetchErrorCode.INVALID_URL, 'The URL is not valid.', {\n      url: String(rawUrl),\n      cause,\n    });\n  }\n\n  const normalizedProtocols = allowedProtocols.map((p) => p.toLowerCase());\n  if (!normalizedProtocols.includes(url.protocol.toLowerCase())) {\n    return fail(\n      GuardedFetchErrorCode.PROTOCOL_NOT_ALLOWED,\n      `Protocol \"${url.protocol}\" is not allowed. Allowed: ${normalizedProtocols.join(', ')}.`,\n      { url: url.toString() },\n    );\n  }\n\n  const hostname = url.hostname.toLowerCase();\n\n  if (allowedHosts !== undefined) {\n    if (!isHostInAllowlist(hostname, allowedHosts)) {\n      return fail(\n        GuardedFetchErrorCode.HOST_NOT_ALLOWED,\n        `Host \"${hostname}\" is not in the allowlist.`,\n        { hostname, url: url.toString() },\n      );\n    }\n\n    if (skipSsrfCheckForAllowedHosts) {\n      return { url, hostname };\n    }\n  }\n\n  const lookupHost = parseHostnameForOutboundLookup(hostname);\n  if (!lookupHost) {\n    return fail(\n      GuardedFetchErrorCode.HOSTNAME_UNSAFE,\n      `Hostname \"${hostname}\" is not a valid outbound target.`,\n      { hostname, url: url.toString() },\n      HostnameUnsafeSubReason.INVALID,\n    );\n  }\n\n  const safety = await checkHostnameSafetyForServerSideFetch(lookupHost);\n  if (!safety.safe) {\n    return fail(\n      GuardedFetchErrorCode.HOSTNAME_UNSAFE,\n      `Refusing to connect to \"${hostname}\": resolves to a private, loopback, or link-local address.`,\n      { hostname, url: url.toString() },\n      safety.subReason,\n    );\n  }\n\n  return { url, hostname };\n}\n\n/**\n * Returns true if `hostname` equals any entry in `allowedHosts` exactly, or\n * is a subdomain of one. All comparisons are case-insensitive.\n *\n * Examples (with allowlist `['example.com']`):\n * - `example.com`          → true (exact)\n * - `api.example.com`      → true (subdomain)\n * - `notexample.com`       → false (would only match `.example.com` suffix)\n * - `example.com.evil.net` → false (right-anchored match only)\n */\nexport function isHostInAllowlist(\n  hostname: string,\n  allowedHosts: readonly string[],\n): boolean {\n  const host = hostname.toLowerCase();\n  for (const entry of allowedHosts) {\n    const normalized = entry.toLowerCase();\n    if (!normalized) {\n      continue;\n    }\n    if (host === normalized || host.endsWith(`.${normalized}`)) {\n      return true;\n    }\n  }\n  return false;\n}\n","import dns from 'node:dns';\n\nimport {\n  OPAQUE_ERROR_MESSAGE,\n  GuardedFetchError,\n  GuardedFetchErrorCode,\n} from './errors';\nimport { isSafeIpAddress } from './ip-address';\nimport { HostnameUnsafeSubReason, recordUrlBlocked } from './url-blocked';\n\nexport interface GuardedLookupOptions {\n  /**\n   * Return an opaque error message when the resolved IP is rejected.\n   * Mirrors the `opaqueErrors` flag on higher-level APIs to avoid leaking\n   * which private range was hit.\n   */\n  opaqueErrors?: boolean;\n}\n\n/**\n * Callback signature compatible with `dns.lookup`, Node's `net.connect`\n * `lookup` option, `https.Agent`'s `lookup` option, and undici's\n * `Agent.connect.lookup`. Using one function in all these places guarantees\n * the IP that the socket actually connects to is the same one we validated.\n */\ntype LookupCallback = (\n  err: NodeJS.ErrnoException | null,\n  address: string | dns.LookupAddress[],\n  family?: number,\n) => void;\n\n/**\n * Creates a DNS `lookup` function that validates the resolved IP against\n * {@link isSafeIpAddress} **inline** — the validation runs inside the same\n * call that returns the IP the socket will connect to, closing the\n * DNS-rebinding TOCTOU window that a two-step \"resolve → validate → resolve\n * again → connect\" sequence leaves open.\n *\n * The returned function:\n * - Delegates the actual resolution to Node's `dns.lookup`.\n * - In single-result mode (`all` unset/false), validates the one returned IP.\n * - In `all: true` mode, validates **every** returned IP.\n * - On any unsafe IP, invokes the callback with a {@link GuardedFetchError}\n *   whose `code` is `HOSTNAME_UNSAFE`. The socket `connect` then fails.\n *\n * **This only covers hosts that get resolved.** Node's socket APIs skip DNS\n * entirely when the host is already an IP literal, so a `lookup` function is\n * never called for `net.connect({ host: '127.0.0.1' })` — no `lookup`\n * implementation can guard that case, including this one. On its own this is\n * a rebinding guard, not a complete SSRF guard.\n *\n * For a complete guard, use {@link guardedFetch}, or\n * {@link getSharedGuardedDispatcher} / {@link createGuardedDispatcher}, which\n * pair this lookup with a pre-connect IP-literal check. If you are wiring\n * sockets by hand, run {@link assertUrlIsSafeToFetch} (or\n * {@link isSafeIpAddress} for a bare host) on the target first:\n *\n * ```ts\n * await assertUrlIsSafeToFetch(target);\n * net.connect({ host, port, lookup: createGuardedLookup() });\n * ```\n */\nexport function createGuardedLookup(\n  options: GuardedLookupOptions = {},\n): (\n  hostname: string,\n  opts: dns.LookupOneOptions | dns.LookupAllOptions | number | undefined,\n  callback: LookupCallback,\n) => void {\n  const { opaqueErrors = false } = options;\n\n  const reject = (\n    hostname: string,\n    address: string,\n    cb: LookupCallback,\n  ): void => {\n    recordUrlBlocked(\n      GuardedFetchErrorCode.HOSTNAME_UNSAFE,\n      hostname.toLowerCase(),\n      HostnameUnsafeSubReason.CONNECT_TIME_IP_REJECTED,\n    );\n    // We pass a `GuardedFetchError` up through the `dns.lookup` callback; this\n    // causes the socket `connect` to fail and the error to bubble up through\n    // undici as the `.cause` of a generic fetch error. `guardedFetch`'s cause\n    // walker then surfaces it to the caller with `code: HOSTNAME_UNSAFE`\n    // intact. We deliberately do NOT overwrite `err.code` with a POSIX-style\n    // string — doing so would shadow the GuardedFetchErrorCode on the class.\n    const err = new GuardedFetchError(\n      GuardedFetchErrorCode.HOSTNAME_UNSAFE,\n      opaqueErrors\n        ? OPAQUE_ERROR_MESSAGE\n        : `Refusing to connect to ${address}: private, loopback, or link-local address.`,\n      { hostname },\n    );\n    cb(err as unknown as NodeJS.ErrnoException, '', 0);\n  };\n\n  return function guardedLookup(hostname, opts, callback) {\n    dns.lookup(\n      hostname,\n      // `dns.lookup`'s overloads differ on the 2nd arg; passing through is\n      // safe because we never construct this value ourselves.\n      opts as dns.LookupOneOptions,\n      (err, address, family) => {\n        if (err) {\n          callback(err, address as string, family);\n          return;\n        }\n\n        if (Array.isArray(address)) {\n          for (const entry of address) {\n            if (!isSafeIpAddress(entry.address)) {\n              reject(hostname, entry.address, callback);\n              return;\n            }\n          }\n          callback(null, address);\n          return;\n        }\n\n        if (typeof address === 'string' && !isSafeIpAddress(address)) {\n          reject(hostname, address, callback);\n          return;\n        }\n\n        callback(null, address as string, family);\n      },\n    );\n  };\n}\n","import net, { type LookupFunction } from 'node:net';\n\nimport { Agent, buildConnector, type Dispatcher } from 'undici';\n\nimport {\n  OPAQUE_ERROR_MESSAGE,\n  GuardedFetchError,\n  GuardedFetchErrorCode,\n} from './errors';\nimport {\n  createGuardedLookup,\n  type GuardedLookupOptions,\n} from './guarded-lookup';\nimport { isSafeIpAddress } from './ip-address';\nimport { HostnameUnsafeSubReason, recordUrlBlocked } from './url-blocked';\n\nlet sharedDispatcher: Agent | undefined;\n\n/**\n * Builds the undici connector used by both dispatchers.\n *\n * Two checks, because `connect.lookup` alone is not enough: Node skips DNS\n * resolution entirely when the host is already an IP literal, so a `lookup`\n * function never runs for `http://127.0.0.1/`. The pre-connect check below\n * covers that case; {@link createGuardedLookup} covers hosts that do resolve.\n */\nfunction createGuardedConnector(\n  options: GuardedLookupOptions = {},\n): buildConnector.connector {\n  const { opaqueErrors = false } = options;\n\n  const connect = buildConnector({\n    lookup: createGuardedLookup(options) as unknown as LookupFunction,\n  });\n\n  return function guardedConnect(connectOptions, callback) {\n    const host = (connectOptions.hostname ?? '')\n      .replace(/^\\[/, '')\n      .replace(/\\]$/, '');\n\n    // IP literals never reach `lookup` — validate before the socket opens.\n    if (net.isIP(host) && !isSafeIpAddress(host)) {\n      recordUrlBlocked(\n        GuardedFetchErrorCode.HOSTNAME_UNSAFE,\n        host.toLowerCase(),\n        HostnameUnsafeSubReason.IP_LITERAL_UNSAFE,\n      );\n      callback(\n        new GuardedFetchError(\n          GuardedFetchErrorCode.HOSTNAME_UNSAFE,\n          opaqueErrors\n            ? OPAQUE_ERROR_MESSAGE\n            : `Refusing to connect to ${host}: private, loopback, or link-local address.`,\n          { hostname: host },\n        ),\n        null,\n      );\n      return;\n    }\n\n    connect(connectOptions, callback);\n  };\n}\n\n/**\n * Returns the process-wide shared safe dispatcher, constructing it lazily\n * on first use. Reuses the connection pool across calls for efficiency.\n *\n * Validates the connection target two ways: IP literals are rejected before\n * the socket opens, and resolved hostnames are re-validated inside the same\n * DNS call the socket connects with — see {@link createGuardedLookup}. The\n * latter closes the DNS-rebinding TOCTOU window.\n *\n * Prefer this over {@link createGuardedDispatcher} unless you need a\n * disposable agent (e.g. for `opaqueErrors: true`).\n */\nexport function getSharedGuardedDispatcher(): Dispatcher {\n  if (!sharedDispatcher) {\n    sharedDispatcher = new Agent({ connect: createGuardedConnector() });\n  }\n  return sharedDispatcher;\n}\n\n/**\n * Constructs a new undici {@link Agent} that validates and pins the\n * connection target: IP literals are rejected before the socket opens, and\n * resolved hostnames are re-validated inside the DNS call the socket\n * connects with. The caller **must** close the returned agent via\n * `agent.close()` when done to release socket resources — otherwise the\n * connection pool leaks for the lifetime of the process.\n *\n * Prefer {@link getSharedGuardedDispatcher} unless you specifically need a\n * per-request dispatcher (e.g. to carry `opaqueErrors: true` through the\n * rejection message).\n */\nexport function createGuardedDispatcher(\n  options: GuardedLookupOptions = {},\n): Agent {\n  return new Agent({ connect: createGuardedConnector(options) });\n}\n","/**\n * Headers that must never be forwarded on outbound requests driven by\n * user-supplied configuration (webhooks, log drains, OIDC, image registries,\n * HAR proxying, etc.).\n *\n * Forwarding these enables four classes of abuse:\n *\n * 1. **Virtual-host routing confusion** — a user-supplied `Host` header can\n *    cause a proxy or load balancer to dispatch the request to an unintended\n *    internal backend that would normally be unreachable.\n * 2. **Proxy / origin-IP spoofing** — `X-Forwarded-*`, `Forwarded`, `Via`, and\n *    `X-Real-IP` can be used to impersonate a trusted client IP to a downstream\n *    service that trusts those headers.\n * 3. **SSRF escalation via cloud metadata services** — `Metadata-Flavor: Google`\n *    and `X-aws-ec2-metadata-token` are required by GCP and AWS IMDSv2 to serve\n *    instance credentials. A user that can control headers on an outbound\n *    request can use them to exfiltrate cloud credentials if the SSRF check is\n *    ever bypassed.\n * 4. **Session / cookie hijacking** — `Cookie` and `Set-Cookie` may leak\n *    internal session state to the destination.\n *\n * Additionally, hop-by-hop headers are stripped because they apply only to a\n * single transport connection and forwarding them corrupts connection\n * semantics (RFC 7230 §6.1).\n *\n * All comparisons are case-insensitive.\n */\nexport const BLOCKED_REQUEST_HEADERS: ReadonlySet<string> = new Set([\n  // Hop-by-hop / transport (RFC 7230 §6.1)\n  'connection',\n  'keep-alive',\n  'te',\n  'trailer',\n  'transfer-encoding',\n  'upgrade',\n\n  // Host / virtual-host routing\n  'host',\n\n  // Proxy / origin spoofing\n  'forwarded',\n  'proxy-authorization',\n  'via',\n  'x-forwarded-for',\n  'x-forwarded-host',\n  'x-forwarded-proto',\n  'x-real-ip',\n\n  // Cloud metadata (GCP, AWS IMDSv1/v2, Azure, Alibaba, DigitalOcean)\n  'metadata',\n  'metadata-flavor',\n  'x-aws-ec2-metadata-token',\n  'x-metadata-token',\n\n  // Session / cookie\n  'cookie',\n  'set-cookie',\n]);\n","import { Headers } from 'undici';\n\nimport { BLOCKED_REQUEST_HEADERS } from './blocked-headers';\n\n/**\n * Supported input shapes, mirroring the DOM `HeadersInit` type but without\n * depending on DOM lib being enabled.\n */\nexport type HeadersLike =\n  | Headers\n  | globalThis.Headers\n  | Record<string, string | string[] | undefined>\n  | readonly (readonly [string, string])[];\n\n/**\n * Returns a new `Headers` instance with {@link BLOCKED_REQUEST_HEADERS}\n * removed. The input is never mutated.\n *\n * Accepts any shape that `new Headers(...)` would accept, plus a plain record\n * whose values may be `undefined` (ignored) or `string[]` (joined per\n * `Headers.append` semantics).\n */\nexport function sanitizeRequestHeaders(\n  input: HeadersLike | undefined,\n): Headers {\n  const out = new Headers();\n  if (!input) {\n    return out;\n  }\n\n  const append = (name: string, value: string) => {\n    if (BLOCKED_REQUEST_HEADERS.has(name.toLowerCase())) {\n      return;\n    }\n    out.append(name, value);\n  };\n\n  if (isHeadersObject(input)) {\n    input.forEach((value, name) => {\n      append(name, value);\n    });\n    return out;\n  }\n\n  if (Array.isArray(input)) {\n    for (const entry of input) {\n      const [name, value] = entry;\n      if (typeof name === 'string' && typeof value === 'string') {\n        append(name, value);\n      }\n    }\n    return out;\n  }\n\n  for (const [name, value] of Object.entries(\n    input as Record<string, string | string[] | undefined>,\n  )) {\n    if (value === undefined) {\n      continue;\n    }\n    if (Array.isArray(value)) {\n      for (const v of value) {\n        append(name, v);\n      }\n    } else {\n      append(name, value);\n    }\n  }\n\n  return out;\n}\n\nfunction isHeadersObject(\n  input: HeadersLike,\n): input is Headers | globalThis.Headers {\n  return (\n    !Array.isArray(input) &&\n    typeof (input as { forEach?: unknown }).forEach === 'function'\n  );\n}\n","import { fetch as undiciFetch, Headers, type Dispatcher } from 'undici';\n\nimport {\n  assertUrlIsSafeToFetch,\n  type AssertUrlSafeOptions,\n} from './assert-url-safe';\nimport {\n  isGuardedFetchError,\n  OPAQUE_ERROR_MESSAGE,\n  GuardedFetchError,\n  GuardedFetchErrorCode,\n} from './errors';\nimport { getSharedGuardedDispatcher } from './guarded-dispatcher';\nimport { sanitizeRequestHeaders } from './sanitize-headers';\nimport { recordUrlBlocked } from './url-blocked';\n\n/**\n * Subset of `RequestInit` fields we accept verbatim. We strip:\n * - `signal` / `redirect` — managed internally (timeout + manual redirect).\n * - `dispatcher` — re-typed on {@link GuardedFetchOptions} to allow `null` as\n *   an explicit opt-out.\n */\ntype PassthroughRequestInit = Omit<\n  RequestInit,\n  'signal' | 'redirect' | 'dispatcher'\n>;\n\ntype FetchBody = NonNullable<RequestInit['body']>;\ntype FetchImpl = typeof globalThis.fetch;\n\nexport interface GuardedFetchOptions\n  extends PassthroughRequestInit, AssertUrlSafeOptions {\n  /**\n   * Timeout in milliseconds for the entire request chain (including redirect\n   * hops). When exceeded, the underlying `AbortController` is aborted and a\n   * {@link GuardedFetchError} with code `TIMEOUT` is thrown.\n   *\n   * @default 10_000\n   */\n  timeoutMs?: number;\n\n  /**\n   * Whether to follow HTTP redirects. Every hop is independently validated\n   * with the same protocol, allowlist, and SSRF checks as the original URL.\n   *\n   * @default true\n   */\n  followRedirects?: boolean;\n\n  /**\n   * Maximum number of redirect hops. Has no effect when\n   * `followRedirects: false`.\n   *\n   * @default 5\n   */\n  maxRedirects?: number;\n\n  /**\n   * Strip SSRF/proxy/cookie-bearing headers from `init.headers` before\n   * sending. See {@link BLOCKED_REQUEST_HEADERS} for the full list.\n   *\n   * @default true\n   */\n  sanitizeHeaders?: boolean;\n\n  /**\n   * External abort signal. If it fires, the request is aborted. The internal\n   * timeout signal is combined with this one.\n   */\n  signal?: AbortSignal;\n\n  /**\n   * Injectable undici-compatible `fetch` implementation for tests.\n   *\n   * @default `undici.fetch`\n   */\n  fetch?: FetchImpl;\n\n  /**\n   * undici `Dispatcher` used for the outbound request. Defaults to a\n   * process-wide shared dispatcher whose `connect.lookup` validates and\n   * **pins** the resolved IP address — this is what closes the DNS\n   * rebinding TOCTOU window (see README).\n   *\n   * - Pass `null` to explicitly disable IP pinning (dangerous; only useful\n   *   when the upstream layer already guarantees the connection\n   *   target — e.g. a trusted HTTP proxy).\n   */\n  dispatcher?: Dispatcher | null;\n\n  /**\n   * Attaches the provided value as a `Cookie` request header on the initial\n   * request. Mirrors the existing `Authorization` semantics: sent on the\n   * first hop, stripped on cross-origin redirects, preserved on same-origin\n   * redirects.\n   *\n   * The opt-in is a separate, narrower surface than the generic `headers`\n   * input because `Cookie` is in {@link BLOCKED_REQUEST_HEADERS} (default\n   * deny) — callers that legitimately need to send a cookie must do so\n   * through this option.\n   *\n   * All other guarded-fetch protections (URL validation, protocol allowlist,\n   * IP pinning, response size limit) remain in force.\n   */\n  allowedCookie?: string;\n}\n\nexport const DEFAULT_TIMEOUT_MS = 10_000;\n\nconst DEFAULT_MAX_REDIRECTS = 5;\n\n/**\n * Performs an SSRF-safe HTTP(S) fetch.\n *\n * Guarantees on every outbound attempt (including redirects):\n *\n * 1. **Protocol allowlisted** — defaults to `http:` and `https:`; set\n *    `httpsOnly: true` to restrict to `https:`.\n * 2. **Host allowlisted** (when `allowedHosts` is provided).\n * 3. **Hostname is public** — DNS-resolved and checked against private,\n *    loopback, and link-local ranges via\n *    package-local hostname safety checks. Covers AWS/GCP metadata endpoints\n *    (`169.254.0.0/16`), container/k8s service CIDRs, and\n *    `.local`/`localhost`.\n * 4. **Request headers sanitized** — proxy-forwarding, hop-by-hop, cookie,\n *    and cloud-metadata headers are stripped unless `sanitizeHeaders: false`.\n * 5. **Redirects validated** — `redirect: 'manual'` is used internally so\n *    every hop runs checks 1-3 again; TOCTOU-style bypass via redirect-to-\n *    metadata is blocked.\n * 6. **Bounded time** — an `AbortController` enforces `timeoutMs` across the\n *    entire chain.\n *\n * On failure, throws {@link GuardedFetchError}. On success, returns the final\n * `Response` — callers are responsible for reading the body. Use\n * {@link guardedFetchJson} / {@link guardedFetchText} when a body-size limit is\n * desired.\n */\nexport async function guardedFetch(\n  rawUrl: string | URL,\n  options: GuardedFetchOptions = {},\n): Promise<Response> {\n  const {\n    timeoutMs = DEFAULT_TIMEOUT_MS,\n    followRedirects = true,\n    maxRedirects = DEFAULT_MAX_REDIRECTS,\n    sanitizeHeaders = true,\n    httpsOnly,\n    allowedHosts,\n    skipSsrfCheckForAllowedHosts,\n    opaqueErrors = false,\n    onUrlBlocked,\n    signal: externalSignal,\n    fetch: fetchImpl = undiciFetch as unknown as FetchImpl,\n    dispatcher: providedDispatcher,\n    allowedCookie,\n    headers,\n    body,\n    method,\n    ...rest\n  } = options;\n\n  // IP-pinning dispatcher closes the DNS rebinding window. `null` explicitly\n  // opts out.\n  const dispatcher =\n    providedDispatcher === null\n      ? undefined\n      : (providedDispatcher ?? getSharedGuardedDispatcher());\n\n  const assertOptions: AssertUrlSafeOptions = {\n    httpsOnly,\n    allowedHosts,\n    skipSsrfCheckForAllowedHosts,\n    opaqueErrors,\n    onUrlBlocked,\n  };\n\n  // The deadline starts before URL validation, not after it. The preflight\n  // DNS-resolves the hostname, and whoever supplied the URL may also control\n  // its DNS — so a slow resolver would otherwise stall the call for as long\n  // as it liked before `timeoutMs` began counting.\n  const controller = new AbortController();\n  let validatedUrl: URL | undefined;\n  const timer = setTimeout(() => {\n    const target = validatedUrl?.hostname ?? String(rawUrl);\n    controller.abort(\n      new GuardedFetchError(\n        GuardedFetchErrorCode.TIMEOUT,\n        opaqueErrors\n          ? OPAQUE_ERROR_MESSAGE\n          : `Request to \"${target}\" timed out after ${timeoutMs}ms.`,\n        {\n          hostname: validatedUrl?.hostname,\n          url: validatedUrl?.toString() ?? String(rawUrl),\n        },\n      ),\n    );\n  }, timeoutMs);\n\n  const onExternalAbort = () => {\n    controller.abort(externalSignal?.reason);\n  };\n  if (externalSignal) {\n    if (externalSignal.aborted) {\n      onExternalAbort();\n    } else {\n      externalSignal.addEventListener('abort', onExternalAbort, { once: true });\n    }\n  }\n\n  try {\n    const { url } = await withDeadline(\n      assertUrlIsSafeToFetch(rawUrl, assertOptions),\n      controller.signal,\n    );\n    validatedUrl = url;\n\n    const outboundHeaders = sanitizeHeaders\n      ? sanitizeRequestHeaders(\n          headers as Parameters<typeof sanitizeRequestHeaders>[0],\n        )\n      : new Headers(\n          headers as ConstructorParameters<typeof Headers>[0] | undefined,\n        );\n\n    if (allowedCookie !== undefined) {\n      outboundHeaders.set('cookie', allowedCookie);\n    }\n\n    return await followChain({\n      url,\n      method: method ?? 'GET',\n      body,\n      headers: outboundHeaders,\n      init: rest,\n      fetchImpl,\n      dispatcher,\n      signal: controller.signal,\n      followRedirects,\n      maxRedirects,\n      assertOptions,\n      opaqueErrors,\n    });\n  } finally {\n    clearTimeout(timer);\n    if (externalSignal) {\n      externalSignal.removeEventListener('abort', onExternalAbort);\n    }\n  }\n}\n\n/**\n * Races `promise` against `signal`, rejecting with the signal's reason as soon\n * as it aborts.\n *\n * Used for the hostname-safety checks, which resolve DNS through\n * `node:dns/promises` and take no `AbortSignal` of their own. The lookup keeps\n * running in the background — this bounds how long the caller waits on it, and\n * fails closed when the deadline passes.\n */\nasync function withDeadline<T>(\n  promise: Promise<T>,\n  signal: AbortSignal,\n): Promise<T> {\n  // The caller already started `promise`, and it keeps running whichever way\n  // the race goes. Observe its rejection here so a check that fails after the\n  // deadline cannot escape as an unhandled rejection — that would take down a\n  // process running with `--unhandled-rejections=strict`.\n  promise.catch(() => {\n    // Result is no longer wanted; the race outcome below is authoritative.\n  });\n\n  if (signal.aborted) {\n    throw signal.reason;\n  }\n\n  return await Promise.race([\n    promise,\n    new Promise<never>((_, reject) => {\n      signal.addEventListener('abort', () => reject(signal.reason), {\n        once: true,\n      });\n    }),\n  ]);\n}\n\nasync function followChain(params: {\n  url: URL;\n  method: string;\n  body: FetchBody | null | undefined;\n  headers: Headers;\n  init: Omit<PassthroughRequestInit, 'headers' | 'body' | 'method'>;\n  fetchImpl: FetchImpl;\n  dispatcher: Dispatcher | undefined;\n  signal: AbortSignal;\n  followRedirects: boolean;\n  maxRedirects: number;\n  assertOptions: AssertUrlSafeOptions;\n  opaqueErrors: boolean;\n}): Promise<Response> {\n  let {\n    url: currentUrl,\n    method: currentMethod,\n    body: currentBody,\n    headers: currentHeaders,\n  } = params;\n  const {\n    init,\n    fetchImpl,\n    dispatcher,\n    signal,\n    followRedirects,\n    maxRedirects,\n    assertOptions,\n    opaqueErrors,\n  } = params;\n\n  for (let hop = 0; hop <= maxRedirects; hop += 1) {\n    const response = await executeFetch({\n      url: currentUrl,\n      method: currentMethod,\n      body: currentBody,\n      headers: currentHeaders,\n      init,\n      fetchImpl,\n      dispatcher,\n      signal,\n      opaqueErrors,\n    });\n\n    if (!isRedirect(response.status)) {\n      return response;\n    }\n\n    if (!followRedirects) {\n      return response;\n    }\n\n    if (hop === maxRedirects) {\n      // Drain body to avoid socket leak when not returned to caller.\n      await drainBody(response);\n      recordUrlBlocked(\n        GuardedFetchErrorCode.TOO_MANY_REDIRECTS,\n        params.url.hostname.toLowerCase(),\n        undefined,\n        assertOptions.onUrlBlocked,\n      );\n      throw new GuardedFetchError(\n        GuardedFetchErrorCode.TOO_MANY_REDIRECTS,\n        opaqueErrors\n          ? OPAQUE_ERROR_MESSAGE\n          : `Exceeded ${maxRedirects} redirects starting from \"${params.url}\".`,\n        { url: params.url.toString() },\n      );\n    }\n\n    const locationHeader = response.headers.get('location');\n    if (!locationHeader) {\n      await drainBody(response);\n      recordUrlBlocked(\n        GuardedFetchErrorCode.REDIRECT_INVALID,\n        currentUrl.hostname.toLowerCase(),\n        undefined,\n        assertOptions.onUrlBlocked,\n      );\n      throw new GuardedFetchError(\n        GuardedFetchErrorCode.REDIRECT_INVALID,\n        opaqueErrors\n          ? OPAQUE_ERROR_MESSAGE\n          : `Redirect response from \"${currentUrl}\" is missing a Location header.`,\n        { url: currentUrl.toString(), status: response.status },\n      );\n    }\n\n    let nextUrl: URL;\n    try {\n      nextUrl = new URL(locationHeader, currentUrl);\n    } catch (cause) {\n      await drainBody(response);\n      recordUrlBlocked(\n        GuardedFetchErrorCode.REDIRECT_INVALID,\n        currentUrl.hostname.toLowerCase(),\n        undefined,\n        assertOptions.onUrlBlocked,\n      );\n      throw new GuardedFetchError(\n        GuardedFetchErrorCode.REDIRECT_INVALID,\n        opaqueErrors\n          ? OPAQUE_ERROR_MESSAGE\n          : `Redirect Location \"${locationHeader}\" from \"${currentUrl}\" is not a valid URL.`,\n        { url: currentUrl.toString(), status: response.status, cause },\n      );\n    }\n\n    // Drain the redirect response before issuing the next one.\n    await drainBody(response);\n\n    try {\n      // Same deadline as the initial check — a redirect target's DNS is just\n      // as attacker-influenced as the original URL's.\n      await withDeadline(\n        assertUrlIsSafeToFetch(nextUrl, assertOptions),\n        signal,\n      );\n    } catch (error) {\n      if (\n        error instanceof GuardedFetchError &&\n        (error.code === GuardedFetchErrorCode.HOSTNAME_UNSAFE ||\n          error.code === GuardedFetchErrorCode.HOST_NOT_ALLOWED ||\n          error.code === GuardedFetchErrorCode.PROTOCOL_NOT_ALLOWED ||\n          error.code === GuardedFetchErrorCode.INVALID_URL)\n      ) {\n        recordUrlBlocked(\n          GuardedFetchErrorCode.REDIRECT_TO_UNSAFE_HOST,\n          params.url.hostname.toLowerCase(),\n          undefined,\n          assertOptions.onUrlBlocked,\n        );\n        throw new GuardedFetchError(\n          GuardedFetchErrorCode.REDIRECT_TO_UNSAFE_HOST,\n          opaqueErrors\n            ? OPAQUE_ERROR_MESSAGE\n            : `Redirect to \"${nextUrl.toString()}\" was rejected: ${error.message}`,\n          {\n            hostname: nextUrl.hostname,\n            url: nextUrl.toString(),\n            cause: error,\n          },\n        );\n      }\n      throw error;\n    }\n\n    // Per RFC 7231 §6.4: 301/302/303 SHOULD change method to GET and drop body;\n    // 307/308 MUST preserve method and body.\n    const statusChangesMethod =\n      response.status === 301 ||\n      response.status === 302 ||\n      response.status === 303;\n\n    if (statusChangesMethod && currentMethod.toUpperCase() !== 'HEAD') {\n      currentMethod = 'GET';\n      currentBody = undefined;\n      // Drop content-* headers since the body is dropped.\n      currentHeaders = dropContentHeaders(currentHeaders);\n    }\n\n    // Drop credentials on cross-origin redirects to avoid leaking them to\n    // attacker-controlled hosts. Cookie is treated identically to\n    // Authorization because the `allowedCookie` option opts in\n    // a `Cookie` header that must not follow a redirect off-origin.\n    if (nextUrl.origin !== currentUrl.origin) {\n      currentHeaders = stripHeader(currentHeaders, 'authorization');\n      currentHeaders = stripHeader(currentHeaders, 'cookie');\n    }\n\n    currentUrl = nextUrl;\n  }\n\n  // Unreachable: loop always returns or throws before exhausting.\n  throw new GuardedFetchError(\n    GuardedFetchErrorCode.TOO_MANY_REDIRECTS,\n    opaqueErrors ? OPAQUE_ERROR_MESSAGE : 'Redirect chain exhausted.',\n    { url: params.url.toString() },\n  );\n}\n\nasync function executeFetch(params: {\n  url: URL;\n  method: string;\n  body: FetchBody | null | undefined;\n  headers: Headers;\n  init: Omit<PassthroughRequestInit, 'headers' | 'body' | 'method'>;\n  fetchImpl: FetchImpl;\n  dispatcher: Dispatcher | undefined;\n  signal: AbortSignal;\n  opaqueErrors: boolean;\n}): Promise<Response> {\n  const {\n    url,\n    method,\n    body,\n    headers,\n    init,\n    fetchImpl,\n    dispatcher,\n    signal,\n    opaqueErrors,\n  } = params;\n\n  const fetchInit: RequestInit = {\n    ...init,\n    method,\n    headers: headers as RequestInit['headers'],\n    body: body ?? undefined,\n    redirect: 'manual',\n    signal,\n  };\n  if (dispatcher) {\n    (fetchInit as Record<string, unknown>).dispatcher = dispatcher;\n  }\n\n  try {\n    return await fetchImpl(url.toString(), fetchInit);\n  } catch (error) {\n    // AbortError — surface as TIMEOUT if we raised it via our own reason,\n    // otherwise as NETWORK_ERROR to preserve caller intent.\n    if (signal.aborted && signal.reason instanceof GuardedFetchError) {\n      throw signal.reason;\n    }\n\n    // The guarded connector rejects the connect with a GuardedFetchError.\n    // undici typically wraps connect errors inside a TypeError/fetch error\n    // with the original error available via `.cause` (sometimes nested one\n    // level deeper). Walk the chain and surface the original if we find one\n    // so that callers see `HOSTNAME_UNSAFE` rather than a generic network\n    // error when an unsafe target is blocked at connect time.\n    const unwrapped = findGuardedFetchErrorInCause(error);\n    if (unwrapped) {\n      // The connector composes its own message and cannot see this call's\n      // `opaqueErrors` — the shared dispatcher is process-wide and is built\n      // before any caller options exist. Re-wrap here so the caller's choice\n      // wins no matter which dispatcher produced the rejection; otherwise the\n      // rejected address leaks in the message despite `opaqueErrors: true`.\n      if (opaqueErrors && unwrapped.message !== OPAQUE_ERROR_MESSAGE) {\n        throw new GuardedFetchError(unwrapped.code, OPAQUE_ERROR_MESSAGE, {\n          hostname: unwrapped.hostname ?? url.hostname,\n          url: unwrapped.url ?? url.toString(),\n          status: unwrapped.status,\n          cause: unwrapped,\n        });\n      }\n      throw unwrapped;\n    }\n\n    if (isAbortError(error)) {\n      throw new GuardedFetchError(\n        GuardedFetchErrorCode.NETWORK_ERROR,\n        opaqueErrors\n          ? OPAQUE_ERROR_MESSAGE\n          : `Request to \"${url.hostname}\" was aborted.`,\n        { hostname: url.hostname, url: url.toString(), cause: error },\n      );\n    }\n    throw new GuardedFetchError(\n      GuardedFetchErrorCode.NETWORK_ERROR,\n      opaqueErrors\n        ? OPAQUE_ERROR_MESSAGE\n        : `Network error connecting to \"${url.hostname}\": ${errorMessage(error)}`,\n      { hostname: url.hostname, url: url.toString(), cause: error },\n    );\n  }\n}\n\nfunction isRedirect(status: number): boolean {\n  return (\n    status === 301 ||\n    status === 302 ||\n    status === 303 ||\n    status === 307 ||\n    status === 308\n  );\n}\n\nfunction isAbortError(error: unknown): boolean {\n  return (\n    error instanceof Error &&\n    (error.name === 'AbortError' ||\n      (error as { code?: string }).code === 'ABORT_ERR')\n  );\n}\n\n/**\n * Walks `error.cause` up to 5 levels looking for a {@link GuardedFetchError}.\n * undici tends to wrap socket-connect errors in its own `TypeError: fetch\n * failed`, placing the real error on `.cause`. When our safe lookup blocks\n * a connection due to DNS rebinding, the `GuardedFetchError` we raise there\n * needs to be surfaced to the caller with its original code intact.\n */\nfunction findGuardedFetchErrorInCause(\n  error: unknown,\n): GuardedFetchError | undefined {\n  let current: unknown = error;\n  for (let i = 0; i < 5; i += 1) {\n    if (isGuardedFetchError(current)) {\n      return current as GuardedFetchError;\n    }\n    if (!(current instanceof Error) || !('cause' in current)) {\n      return undefined;\n    }\n    current = (current as Error).cause;\n    if (current === undefined || current === null) {\n      return undefined;\n    }\n  }\n  return undefined;\n}\n\nfunction errorMessage(error: unknown): string {\n  if (error instanceof Error) {\n    return error.message;\n  }\n  return String(error);\n}\n\nfunction dropContentHeaders(headers: Headers): Headers {\n  const out = new Headers();\n  headers.forEach((value, name) => {\n    const lower = name.toLowerCase();\n    if (\n      lower === 'content-length' ||\n      lower === 'content-type' ||\n      lower === 'content-encoding' ||\n      lower === 'content-language' ||\n      lower === 'content-location'\n    ) {\n      return;\n    }\n    out.append(name, value);\n  });\n  return out;\n}\n\nfunction stripHeader(headers: Headers, name: string): Headers {\n  const lower = name.toLowerCase();\n  const out = new Headers();\n  headers.forEach((value, key) => {\n    if (key.toLowerCase() !== lower) {\n      out.append(key, value);\n    }\n  });\n  return out;\n}\n\n/**\n * How much of a discarded redirect body is read before the connection is\n * dropped instead. Small bodies are drained so the socket can be reused by\n * the next hop; anything larger is not worth holding a connection for.\n */\nconst MAX_REDIRECT_DRAIN_BYTES = 64 * 1024;\n\n/**\n * Releases the socket behind a redirect response whose body is never returned\n * to the caller.\n *\n * Reads at most {@link MAX_REDIRECT_DRAIN_BYTES} and cancels beyond that.\n * Buffering the whole body here would be unbounded: redirect bodies are\n * attacker-controlled, `maxResponseBytes` only applies to the readers in\n * `read-body.ts`, and `timeoutMs` caps how long a hop may take but not how\n * much memory it may consume while doing so.\n */\nasync function drainBody(response: Response): Promise<void> {\n  const body = response.body;\n  if (!body) {\n    return;\n  }\n\n  const reader = body.getReader();\n  try {\n    let drained = 0;\n    while (drained <= MAX_REDIRECT_DRAIN_BYTES) {\n      const { done, value } = await reader.read();\n      if (done) {\n        return;\n      }\n      drained += value?.byteLength ?? 0;\n    }\n    await reader.cancel();\n  } catch {\n    // Aborted, errored, or already-consumed stream — nothing left to release.\n  } finally {\n    try {\n      reader.releaseLock();\n    } catch {\n      // Already released by cancel().\n    }\n  }\n}\n","import {\n  OPAQUE_ERROR_MESSAGE,\n  GuardedFetchError,\n  GuardedFetchErrorCode,\n} from './errors';\n\nconst DEFAULT_MAX_BYTES = 10 * 1024 * 1024; // 10 MB\n\nexport interface ReadBodyOptions {\n  /**\n   * Maximum number of bytes to read from the response body before aborting\n   * with {@link GuardedFetchErrorCode.RESPONSE_TOO_LARGE}.\n   *\n   * @default 10 MB\n   */\n  maxResponseBytes?: number;\n\n  /**\n   * Wall-clock deadline (ms since epoch) for reading the full response body.\n   * When exceeded, the stream is cancelled and {@link GuardedFetchErrorCode.TIMEOUT}\n   * is thrown. Used by {@link guardedFetchJson} / {@link guardedFetchText} to extend\n   * the same `timeoutMs` budget through body streaming.\n   */\n  deadlineAt?: number;\n\n  /**\n   * Return an opaque error for size-limit / decode failures to avoid leaking\n   * response-shape information.\n   *\n   * @default false\n   */\n  opaqueErrors?: boolean;\n}\n\nclass BodyReadDeadlineExceeded extends Error {\n  constructor() {\n    super('Response body read deadline exceeded.');\n    this.name = 'BodyReadDeadlineExceeded';\n  }\n}\n\nfunction createBodyReadTimeoutError(\n  response: Response,\n  opaqueErrors: boolean,\n): GuardedFetchError {\n  return new GuardedFetchError(\n    GuardedFetchErrorCode.TIMEOUT,\n    opaqueErrors\n      ? OPAQUE_ERROR_MESSAGE\n      : `Response body read from \"${response.url}\" timed out.`,\n    { url: response.url, status: response.status },\n  );\n}\n\nasync function readChunkWithDeadline(\n  reader: ReadableStreamDefaultReader<Uint8Array>,\n  deadlineAt: number | undefined,\n  response: Response,\n  opaqueErrors: boolean,\n): Promise<{ done: boolean; value?: Uint8Array }> {\n  if (deadlineAt === undefined) {\n    return reader.read();\n  }\n\n  const remainingMs = deadlineAt - Date.now();\n  if (remainingMs <= 0) {\n    throw createBodyReadTimeoutError(response, opaqueErrors);\n  }\n\n  let timer: ReturnType<typeof setTimeout> | undefined;\n  try {\n    return await Promise.race([\n      reader.read(),\n      new Promise<never>((_, reject) => {\n        timer = setTimeout(\n          () => reject(new BodyReadDeadlineExceeded()),\n          remainingMs,\n        );\n      }),\n    ]);\n  } catch (error) {\n    if (error instanceof BodyReadDeadlineExceeded) {\n      throw createBodyReadTimeoutError(response, opaqueErrors);\n    }\n    throw error;\n  } finally {\n    if (timer !== undefined) {\n      clearTimeout(timer);\n    }\n  }\n}\n\nasync function cancelReader(\n  reader: ReadableStreamDefaultReader<Uint8Array>,\n): Promise<void> {\n  try {\n    await reader.cancel();\n  } catch {\n    // ignore\n  }\n}\n\nfunction releaseReader(reader: ReadableStreamDefaultReader<Uint8Array>): void {\n  try {\n    reader.releaseLock();\n  } catch {\n    // reader may already be closed / cancelled\n  }\n}\n\n/**\n * Reads a `Response` body as a UTF-8 string, bounded by `maxResponseBytes`.\n *\n * Streaming is used where available so oversized responses are detected\n * early, without buffering the full body. The underlying connection is\n * cancelled as soon as the limit is exceeded.\n */\nexport async function readBodyAsText(\n  response: Response,\n  options: ReadBodyOptions = {},\n): Promise<string> {\n  const {\n    maxResponseBytes = DEFAULT_MAX_BYTES,\n    deadlineAt,\n    opaqueErrors = false,\n  } = options;\n\n  const body = response.body;\n  if (!body) {\n    return '';\n  }\n\n  const reader = body.getReader();\n  const decoder = new TextDecoder('utf-8');\n  const chunks: string[] = [];\n  let received = 0;\n\n  try {\n    while (true) {\n      const { done, value } = await readChunkWithDeadline(\n        reader,\n        deadlineAt,\n        response,\n        opaqueErrors,\n      );\n      if (done) {\n        break;\n      }\n      if (!value) {\n        continue;\n      }\n      received += value.byteLength;\n      if (received > maxResponseBytes) {\n        await cancelReader(reader);\n        throw new GuardedFetchError(\n          GuardedFetchErrorCode.RESPONSE_TOO_LARGE,\n          opaqueErrors\n            ? OPAQUE_ERROR_MESSAGE\n            : `Response body exceeded ${maxResponseBytes} bytes.`,\n          { url: response.url, status: response.status },\n        );\n      }\n      chunks.push(decoder.decode(value, { stream: true }));\n    }\n    chunks.push(decoder.decode());\n  } catch (error) {\n    if (\n      error instanceof GuardedFetchError &&\n      error.code === GuardedFetchErrorCode.TIMEOUT\n    ) {\n      await cancelReader(reader);\n    }\n    throw error;\n  } finally {\n    releaseReader(reader);\n  }\n\n  return chunks.join('');\n}\n\n/**\n * Reads a `Response` body as JSON, bounded by `maxResponseBytes`. Throws\n * {@link GuardedFetchError} with code `NETWORK_ERROR` on parse failure.\n */\nexport async function readBodyAsJson<T = unknown>(\n  response: Response,\n  options: ReadBodyOptions = {},\n): Promise<T> {\n  const text = await readBodyAsText(response, options);\n  try {\n    return JSON.parse(text) as T;\n  } catch (cause) {\n    throw new GuardedFetchError(\n      GuardedFetchErrorCode.NETWORK_ERROR,\n      options.opaqueErrors\n        ? OPAQUE_ERROR_MESSAGE\n        : `Failed to parse JSON response from \"${response.url}\".`,\n      { url: response.url, status: response.status, cause },\n    );\n  }\n}\n","import {\n  OPAQUE_ERROR_MESSAGE,\n  GuardedFetchError,\n  GuardedFetchErrorCode,\n} from './errors';\nimport {\n  DEFAULT_TIMEOUT_MS,\n  guardedFetch,\n  type GuardedFetchOptions,\n} from './guarded-fetch';\nimport {\n  readBodyAsJson,\n  readBodyAsText,\n  type ReadBodyOptions,\n} from './read-body';\n\nexport interface GuardedFetchBodyOptions\n  extends GuardedFetchOptions, ReadBodyOptions {\n  /**\n   * If true, non-2xx responses throw a {@link GuardedFetchError} with code\n   * `NETWORK_ERROR` and the response's `status` populated. If false (default),\n   * the body is returned regardless of status, matching `fetch`'s behavior.\n   *\n   * @default false\n   */\n  throwOnHttpError?: boolean;\n}\n\n/**\n * Convenience wrapper: performs a safe fetch and parses the body as JSON,\n * enforcing a configurable response-size limit and the same `timeoutMs`\n * budget through body streaming.\n */\nexport async function guardedFetchJson<T = unknown>(\n  url: string | URL,\n  options: GuardedFetchBodyOptions = {},\n): Promise<T> {\n  const { maxResponseBytes, throwOnHttpError, timeoutMs, ...fetchOptions } =\n    options;\n  const startedAt = Date.now();\n  const deadlineAt = startedAt + (timeoutMs ?? DEFAULT_TIMEOUT_MS);\n  const response = await guardedFetch(url, { ...fetchOptions, timeoutMs });\n  await maybeThrowForStatus(response, throwOnHttpError, options.opaqueErrors);\n  return readBodyAsJson<T>(response, {\n    maxResponseBytes,\n    deadlineAt,\n    opaqueErrors: options.opaqueErrors,\n  });\n}\n\n/**\n * Convenience wrapper: performs a safe fetch and returns the body as UTF-8\n * text, enforcing a configurable response-size limit and the same `timeoutMs`\n * budget through body streaming.\n */\nexport async function guardedFetchText(\n  url: string | URL,\n  options: GuardedFetchBodyOptions = {},\n): Promise<string> {\n  const { maxResponseBytes, throwOnHttpError, timeoutMs, ...fetchOptions } =\n    options;\n  const startedAt = Date.now();\n  const deadlineAt = startedAt + (timeoutMs ?? DEFAULT_TIMEOUT_MS);\n  const response = await guardedFetch(url, { ...fetchOptions, timeoutMs });\n  await maybeThrowForStatus(response, throwOnHttpError, options.opaqueErrors);\n  return readBodyAsText(response, {\n    maxResponseBytes,\n    deadlineAt,\n    opaqueErrors: options.opaqueErrors,\n  });\n}\n\nasync function maybeThrowForStatus(\n  response: Response,\n  throwOnHttpError: boolean | undefined,\n  opaqueErrors: boolean | undefined,\n): Promise<void> {\n  if (!throwOnHttpError || response.ok) {\n    return;\n  }\n\n  // The body is never handed to the caller on this path, so cancel it before\n  // throwing. Leaving it unread holds the stream and its socket open — and an\n  // error response is attacker-controlled just like a successful one, so its\n  // size is not bounded by anything here.\n  try {\n    await response.body?.cancel();\n  } catch {\n    // The stream may already be errored or locked; nothing to release.\n  }\n\n  throw new GuardedFetchError(\n    GuardedFetchErrorCode.NETWORK_ERROR,\n    opaqueErrors\n      ? OPAQUE_ERROR_MESSAGE\n      : `HTTP ${response.status} from \"${response.url}\".`,\n    { url: response.url, status: response.status },\n  );\n}\n"],"mappings":"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"}