{"version":3,"sources":["../src/variables.ts","../src/functions.ts","../src/schema.ts","../src/renders.ts","../src/pixel.ts"],"names":["moduleDir","path","fileURLToPath","getAssetPath","filename","NOT_FOUND_IMAGE","NOT_FOUND_AVATAR","FALLBACKIMAGES","readFile","API_REGEX","allowedFormats","mimeTypes","safeOnError","hook","err","phase","src","MAX_SPECIFIED_PATH_LEN","isValidPath","basePath","specifiedPath","resolvedBase","resolvedPath","realBase","realPath","normalizedBase","isInside","relative","isPrivateIp","ip","family","isIP","parts","p","a","b","lower","v4","isPublicHost","hostname","stripped","addresses","resolvePinnedAddress","first","buildPinnedLookup","address","_hostname","_options","callback","buildPinnedAgents","lookup","isHostAllowed","host","allowedNetworkList","requestNoRedirect","timeoutMs","maxBytes","agents","axios","status","aerr","fetchFromNetwork","type","maxRedirects","onError","currentUrl","hop","parsed","pinned","response","location","contentType","allowedMimeTypes","readLocalImage","filePath","baseDir","resolvedFile","stats","stripApiPrefix","pathname","apiRegex","apiPrefix","fetchImage","websiteURL","url","localPath","imageFormatEnum","z","imageTypeEnum","userDataSchema","value","ctx","got","val","optionsSchema","arr","data","renderOptions","options","renderUserData","userData","bounds","clamp","min","max","reportError","context","safeOnComplete","elapsedMs","start","diff","whole","remainder","FILENAME_MAX_LEN","ASCII_FALLBACK_DEFAULT","buildFilename","rawSrc","outputFormat","ext","baseWithExt","baseNoExt","asciiBase","safeAsciiBase","maxBase","asciiFilename","utfBase","truncatedEncoded","c","lastPercent","tail","byte","encodedFilename","buildSourceIdentifier","resolved","R","buildDeterministicEtag","fields","sourceIdentifier","key","createHash","raceWithTimeout","promise","ms","label","timer","_","reject","isInsideRoot","rootDir","candidate","preResolvedRoot","realRoot","lexicalCandidate","realCandidate","resolveRootDir","looksLikeSvg","buf","isLe","sliceEnd","head16","beSrc","swapped","i","trimmed","head","serveImage","req","res","next","parsedOptions","cachedRealRoot","startedAt","requestedType","onComplete","observedSrc","observedUserId","parsedUserId","rawUserId","idTimeoutMs","handlerResult","folderPromise","dir","etag","imageBuffer","processedImage","image","sharp","meta","resizeOptions","flushedError","fallback","fallbackError","registerServe","cacheResolved","pendingResolution","ensureCachedRealRoot","rootForRequest","pixel_default"],"mappings":"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","file":"index.mjs","sourcesContent":["import type { ImageFormat } from \"./types\";\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/**\n * Get the directory path for the current module.\n * Uses import.meta.url for ESM (tsup provides shims for CJS compatibility).\n */\nconst moduleDir = path.dirname(fileURLToPath(import.meta.url));\n\nconst getAssetPath = (filename: string): string => {\n  return path.join(moduleDir, \"assets\", filename);\n};\n\nconst NOT_FOUND_IMAGE = getAssetPath(\"noimage.jpg\");\nconst NOT_FOUND_AVATAR = getAssetPath(\"noavatar.png\");\n\nexport const FALLBACKIMAGES: Record<\n  \"normal\" | \"avatar\",\n  () => Promise<Buffer>\n> = {\n  normal: async (): Promise<Buffer> => readFile(NOT_FOUND_IMAGE),\n  avatar: async (): Promise<Buffer> => readFile(NOT_FOUND_AVATAR),\n};\n\nexport const API_REGEX: RegExp = /^\\/api\\/v1\\//;\n\nexport const allowedFormats: ImageFormat[] = [\n  \"jpeg\",\n  \"jpg\",\n  \"png\",\n  \"webp\",\n  \"gif\",\n  \"tiff\",\n  \"avif\",\n];\n\nexport const mimeTypes: Readonly<Record<string, string>> = {\n  jpeg: \"image/jpeg\",\n  jpg: \"image/jpeg\",\n  png: \"image/png\",\n  webp: \"image/webp\",\n  gif: \"image/gif\",\n  tiff: \"image/tiff\",\n  avif: \"image/avif\",\n};\n","import path from \"node:path\";\nimport * as fs from \"node:fs/promises\";\nimport * as dns from \"node:dns/promises\";\nimport * as http from \"node:http\";\nimport * as https from \"node:https\";\nimport { isIP } from \"node:net\";\nimport axios, { AxiosError, AxiosResponse } from \"axios\";\nimport { FALLBACKIMAGES, mimeTypes } from \"./variables\";\nimport type { ImageType, PixelServeOnError } from \"./types\";\n\n/**\n * Internal helper that fires the user-supplied `onError` hook without ever\n * propagating a hook error back into the request pipeline. Mirrors the\n * dispatcher in `pixel.ts`; duplicated to avoid a circular import.\n */\nconst safeOnError = (\n  hook: PixelServeOnError | undefined,\n  err: unknown,\n  phase: string,\n  src?: string,\n): void => {\n  if (!hook) return;\n  try {\n    hook(err, { phase, src });\n  } catch {\n    // intentionally suppressed\n  }\n};\n\n/**\n * @typedef {(\"avatar\" | \"normal\")} ImageType\n * @description Defines the type of image being processed.\n */\n\n/**\n * Maximum length accepted for `specifiedPath`. Both Windows (260 by default,\n * 32767 with `\\\\?\\` LFN prefix) and POSIX (`PATH_MAX` is typically 4096) cap\n * absolute path lengths, but the input here is a relative segment joined to\n * `basePath`, so we cap defensively below the POSIX limit to avoid pathological\n * inputs forcing megabyte string allocations through `path.resolve`.\n */\nconst MAX_SPECIFIED_PATH_LEN = 4096;\n\n/**\n * Checks if a specified path is valid within a base path.\n *\n * Performs shape validation first (no null bytes, no control characters\n * including `DEL`/`\\x7F`, no backslashes on any platform, no absolute paths,\n * length cap), then resolves both `basePath` and the joined path via\n * `fs.realpath`, then asserts containment via `path.relative` plus a\n * prefix check.\n *\n * Cross-platform notes:\n *\n *   - Backslashes are rejected on **all** platforms (not just Windows). On\n *     POSIX, literal `\\\\` is a valid filename byte; on Windows it is a\n *     directory separator. Allowing the divergence silently is a security\n *     smell, so this guard rejects backslash universally to keep behavior\n *     consistent.\n *   - UNC paths (`\\\\server\\share\\...`) are caught by both the backslash\n *     check and `path.isAbsolute` on Windows.\n *   - `\\x7F` (DEL) is part of the control-character regex so request paths\n *     containing it are rejected (the `Content-Disposition` sanitizer in\n *     `pixel.ts` also strips `\\x7F`; keeping the two consistent here matters).\n *\n * TOCTOU caveat: this function calls `fs.realpath` to validate containment,\n * but `pixel.ts` later re-resolves the path and calls `fs.readFile`\n * independently. Between those two calls the filesystem could change (for\n * example, a symlink target could be swapped). For an image-serving pipeline\n * the resulting worst case is a fallback image being returned; for higher\n * security workloads, callers should mount images on a read-only filesystem\n * or run the process with `fs.open` + atime-checked file handles. This is\n * accepted risk for the default deployment model.\n *\n * @param {string} basePath - The base directory to resolve paths.\n * @param {string} specifiedPath - The path to check.\n * @returns {Promise<boolean>} True if the path is valid, false otherwise.\n */\nexport const isValidPath = async (\n  basePath: string,\n  specifiedPath: string,\n): Promise<boolean> => {\n  try {\n    if (!basePath || !specifiedPath) return false;\n    if (typeof specifiedPath !== \"string\") return false;\n    if (specifiedPath.length > MAX_SPECIFIED_PATH_LEN) return false;\n    if (specifiedPath.includes(\"\\0\")) return false;\n    // Reject backslash on ALL platforms. On POSIX this is technically a\n    // legal filename byte, but on Windows it is a path separator that can\n    // be used for traversal. Rejecting unconditionally keeps cross-platform\n    // behavior identical and removes a silent divergence.\n    if (specifiedPath.includes(\"\\\\\")) return false;\n    if (path.isAbsolute(specifiedPath)) return false;\n    // Reject every control character (`\\x00`–`\\x1F`) and `\\x7F` (DEL).\n    // eslint-disable-next-line no-control-regex\n    if (!/^[^\\x00-\\x1F\\x7F]+$/.test(specifiedPath)) return false;\n\n    const resolvedBase = path.resolve(basePath);\n    const resolvedPath = path.resolve(resolvedBase, specifiedPath);\n\n    const [realBase, realPath] = await Promise.all([\n      fs.realpath(resolvedBase),\n      fs.realpath(resolvedPath),\n    ]);\n\n    const baseStats = await fs.stat(realBase);\n    if (!baseStats.isDirectory()) return false;\n\n    // The resolved path must be a regular file (or symlink to one), not a\n    // directory. Image serving never returns directory listings, and\n    // rejecting directories prevents callers from accidentally short-\n    // circuiting fallback logic when an attacker references the root.\n    const pathStats = await fs.stat(realPath);\n    if (!pathStats.isFile()) return false;\n\n    const normalizedBase = realBase + path.sep;\n    const normalizedPath = realPath + path.sep;\n\n    const isInside =\n      normalizedPath.startsWith(normalizedBase) || realPath === realBase;\n\n    const relative = path.relative(realBase, realPath);\n    return !relative.startsWith(\"..\") && !path.isAbsolute(relative) && isInside;\n  } catch {\n    return false;\n  }\n};\n\n/**\n * Determines if an IP address (v4 or v6) is private, loopback, link-local,\n * unique-local, multicast, broadcast, or otherwise unsafe to issue requests to.\n *\n * @param {string} ip - The IP address to check.\n * @returns {boolean} True if the IP is considered private/internal.\n */\nexport const isPrivateIp = (ip: string): boolean => {\n  const family = isIP(ip);\n  if (family === 0) return true; // not a valid IP — treat as unsafe\n\n  if (family === 4) {\n    const parts = ip.split(\".\").map((p) => Number(p));\n    if (parts.length !== 4 || parts.some((p) => Number.isNaN(p))) return true;\n    const [a, b] = parts;\n    // 0.0.0.0/8\n    if (a === 0) return true;\n    // 10.0.0.0/8\n    if (a === 10) return true;\n    // 127.0.0.0/8 (loopback)\n    if (a === 127) return true;\n    // 169.254.0.0/16 (link-local, AWS IMDS)\n    if (a === 169 && b === 254) return true;\n    // 172.16.0.0/12\n    if (a === 172 && b >= 16 && b <= 31) return true;\n    // 192.168.0.0/16\n    if (a === 192 && b === 168) return true;\n    // 192.0.0.0/24 (IETF Protocol Assignments) and 192.0.2.0/24 (TEST-NET-1)\n    if (a === 192 && b === 0) return true;\n    // 198.18.0.0/15 (benchmarking)\n    if (a === 198 && (b === 18 || b === 19)) return true;\n    // 198.51.100.0/24 (TEST-NET-2)\n    if (a === 198 && b === 51 && parts[2] === 100) return true;\n    // 203.0.113.0/24 (TEST-NET-3)\n    if (a === 203 && b === 0 && parts[2] === 113) return true;\n    // 224.0.0.0/4 (multicast)\n    if (a >= 224 && a <= 239) return true;\n    // 240.0.0.0/4 (reserved / 255.255.255.255 broadcast)\n    if (a >= 240) return true;\n    return false;\n  }\n\n  // IPv6 — normalize lowercase\n  const lower = ip.toLowerCase();\n  // unspecified ::\n  if (lower === \"::\" || lower === \"::0\") return true;\n  // loopback ::1\n  if (lower === \"::1\") return true;\n  // IPv4-mapped (::ffff:a.b.c.d) — any malformed v4 tail is treated as unsafe\n  if (lower.startsWith(\"::ffff:\")) {\n    const v4 = lower.slice(\"::ffff:\".length);\n    if (isIP(v4) === 4) return isPrivateIp(v4);\n    return true;\n  }\n  // link-local fe80::/10\n  if (/^fe[89ab][0-9a-f]?:/i.test(lower)) return true;\n  // unique-local fc00::/7\n  if (/^f[cd][0-9a-f]{0,2}:/i.test(lower)) return true;\n  // multicast ff00::/8\n  if (lower.startsWith(\"ff\")) return true;\n  return false;\n};\n\n/**\n * Resolves a hostname via DNS and verifies every returned address is a public\n * (non-private/loopback/link-local) IP. If the hostname is already an IP\n * literal, validates that directly without a DNS lookup.\n *\n * @param {string} hostname - The hostname to validate.\n * @returns {Promise<boolean>} True if the hostname only resolves to public IPs.\n */\nexport const isPublicHost = async (hostname: string): Promise<boolean> => {\n  if (!hostname) return false;\n  // strip brackets that URL.hostname leaves around IPv6 literals\n  const stripped = hostname.replace(/^\\[|\\]$/g, \"\");\n  if (isIP(stripped) !== 0) return !isPrivateIp(stripped);\n\n  try {\n    const addresses = await dns.lookup(stripped, { all: true, verbatim: true });\n    if (!addresses.length) return false;\n    return addresses.every((a) => !isPrivateIp(a.address));\n  } catch {\n    return false;\n  }\n};\n\n/**\n * Resolves a hostname once, validates every returned address is public, and\n * returns a `{ address, family }` pair that can be pinned to an\n * `http.Agent`/`https.Agent`'s `lookup` function. The same address is then\n * guaranteed to be the one the TCP socket connects to, closing the DNS-\n * rebinding window between the validation lookup and axios' subsequent\n * resolve. For IP literals the input is returned verbatim (still subject to\n * `isPrivateIp`) and no DNS lookup is performed.\n *\n * Returns `null` when the host is empty, the host resolves to no addresses,\n * the host resolves to (or is) a private/loopback/link-local IP, or DNS\n * resolution fails. Callers fall back to the regular failure path on `null`.\n */\nexport const resolvePinnedAddress = async (\n  hostname: string,\n): Promise<{ address: string; family: 4 | 6 } | null> => {\n  if (!hostname) return null;\n  const stripped = hostname.replace(/^\\[|\\]$/g, \"\");\n  if (isIP(stripped) !== 0) {\n    if (isPrivateIp(stripped)) return null;\n    const family = isIP(stripped) === 6 ? 6 : 4;\n    return { address: stripped, family };\n  }\n  try {\n    const addresses = await dns.lookup(stripped, { all: true, verbatim: true });\n    if (!addresses.length) return null;\n    if (addresses.some((a) => isPrivateIp(a.address))) return null;\n    const first = addresses[0]!;\n    return {\n      address: first.address,\n      family: first.family === 6 ? 6 : 4,\n    };\n  } catch {\n    return null;\n  }\n};\n\n/**\n * Internal type for the Node `lookup` callback shape we override below.\n * Exported only for tests; real consumers should not depend on it.\n */\ntype PinnedLookup = (\n  hostname: string,\n  options: unknown,\n  callback: (\n    err: NodeJS.ErrnoException | null,\n    address: string,\n    family: number,\n  ) => void,\n) => void;\n\n/**\n * Builds a pinned `lookup` function that always resolves to the same\n * `{ address, family }` pair. Used by `buildPinnedAgents` to force axios to\n * connect to the pre-validated IP rather than re-resolving the hostname.\n */\nconst buildPinnedLookup =\n  (address: string, family: 4 | 6): PinnedLookup =>\n  (_hostname, _options, callback): void => {\n    callback(null, address, family);\n  };\n\n/**\n * Builds `httpAgent` and `httpsAgent` instances whose internal `lookup`\n * function is pinned to a single `{ address, family }` pair. Passed to axios\n * via the per-request config so the kernel resolver is never consulted again\n * after our `isPublicHost` validation. Mitigates the classic DNS-rebinding\n * exploit where an attacker-controlled authoritative server answers the\n * validation lookup with a public IP and the subsequent connect-time lookup\n * with `127.0.0.1`/`169.254.169.254`/etc.\n *\n * Each call returns a new pair of agents (one per request); the agents are\n * not reused across requests so the pinning lifetime matches the redirect\n * loop hop that validated the IP. Agents are not explicitly `destroy()`-ed\n * because Node garbage-collects unused agents once their sockets close.\n */\nexport const buildPinnedAgents = (\n  address: string,\n  family: 4 | 6,\n): { httpAgent: http.Agent; httpsAgent: https.Agent } => {\n  const lookup = buildPinnedLookup(address, family);\n  return {\n    httpAgent: new http.Agent({ lookup }),\n    httpsAgent: new https.Agent({ lookup }),\n  };\n};\n\nconst isHostAllowed = (\n  hostname: string,\n  host: string,\n  allowedNetworkList: string[],\n): boolean =>\n  allowedNetworkList.includes(hostname) || allowedNetworkList.includes(host);\n\n/**\n * Issues a single (non-redirecting) GET request and returns the axios response\n * or null on transport error / non-2xx (when redirects are present). The\n * caller supplies a pinned pair of `httpAgent`/`httpsAgent` so the TCP\n * connection targets the IP that was validated by `resolvePinnedAddress`\n * rather than whatever the kernel resolver returns at connect time.\n */\nconst requestNoRedirect = async (\n  src: string,\n  timeoutMs: number,\n  maxBytes: number,\n  agents: { httpAgent: http.Agent; httpsAgent: https.Agent },\n): Promise<AxiosResponse | null> => {\n  try {\n    return await axios.get(src, {\n      responseType: \"arraybuffer\",\n      timeout: timeoutMs,\n      maxContentLength: maxBytes,\n      maxBodyLength: maxBytes,\n      maxRedirects: 0,\n      httpAgent: agents.httpAgent,\n      httpsAgent: agents.httpsAgent,\n      validateStatus: (status) =>\n        (status >= 200 && status < 300) || (status >= 300 && status < 400),\n    });\n  } catch (err) {\n    // axios throws on 3xx because of maxRedirects: 0; pull response if present\n    const aerr = err as AxiosError;\n    if (aerr?.response) return aerr.response;\n    return null;\n  }\n};\n\n/**\n * Fetches an image from a network source with manual redirect handling.\n * Every hop re-validates the destination against the allowlist, restricts\n * the protocol to http/https, and verifies the destination hostname does\n * not resolve to a private/loopback/link-local IP (SSRF protection).\n *\n * @param {string} src - The URL of the image.\n * @param {ImageType} [type=\"normal\"] - Type of fallback image in case of an error.\n * @returns {Promise<Buffer>} A buffer containing the image data or a fallback image.\n */\nconst fetchFromNetwork = async (\n  src: string,\n  type: ImageType = \"normal\",\n  {\n    timeoutMs,\n    maxBytes,\n    allowedNetworkList,\n    maxRedirects,\n    onError,\n  }: {\n    timeoutMs: number;\n    maxBytes: number;\n    allowedNetworkList: string[];\n    maxRedirects: number;\n    onError?: PixelServeOnError;\n  },\n): Promise<Buffer> => {\n  try {\n    let currentUrl = src;\n    for (let hop = 0; hop <= maxRedirects; hop++) {\n      let parsed: URL;\n      try {\n        parsed = new URL(currentUrl);\n      } catch (err) {\n        safeOnError(onError, err, \"fetch\", currentUrl);\n        return await FALLBACKIMAGES[type]();\n      }\n      if (![\"http:\", \"https:\"].includes(parsed.protocol)) {\n        safeOnError(\n          onError,\n          new Error(`disallowed protocol ${parsed.protocol}`),\n          \"fetch\",\n          currentUrl,\n        );\n        return await FALLBACKIMAGES[type]();\n      }\n      if (!isHostAllowed(parsed.hostname, parsed.host, allowedNetworkList)) {\n        safeOnError(\n          onError,\n          new Error(`host ${parsed.hostname} not in allowedNetworkList`),\n          \"fetch\",\n          currentUrl,\n        );\n        return await FALLBACKIMAGES[type]();\n      }\n      // Resolve once and pin the address into the http(s) agent's `lookup`\n      // function so axios connects to the IP we validated, NOT whatever the\n      // kernel resolver answers microseconds later. This closes the classic\n      // DNS-rebinding TOCTOU window across the manual redirect loop. The\n      // pinned-address helper also runs the `isPrivateIp` validation, so a\n      // separate `isPublicHost` call is redundant here.\n      const pinned = await resolvePinnedAddress(parsed.hostname);\n      if (!pinned) {\n        safeOnError(\n          onError,\n          new Error(\n            `host ${parsed.hostname} resolves to a private IP or DNS lookup failed`,\n          ),\n          \"fetch\",\n          currentUrl,\n        );\n        return await FALLBACKIMAGES[type]();\n      }\n\n      const agents = buildPinnedAgents(pinned.address, pinned.family);\n      const response = await requestNoRedirect(\n        currentUrl,\n        timeoutMs,\n        maxBytes,\n        agents,\n      );\n      if (!response) {\n        safeOnError(\n          onError,\n          new Error(\"network request returned no response\"),\n          \"fetch\",\n          currentUrl,\n        );\n        return await FALLBACKIMAGES[type]();\n      }\n\n      if (response.status >= 300 && response.status < 400) {\n        const location = response.headers?.[\"location\"] as string | undefined;\n        if (!location) {\n          safeOnError(\n            onError,\n            new Error(\"redirect response missing Location header\"),\n            \"fetch\",\n            currentUrl,\n          );\n          return await FALLBACKIMAGES[type]();\n        }\n        // resolve relative redirects against current URL\n        try {\n          currentUrl = new URL(location, currentUrl).toString();\n        } catch (err) {\n          safeOnError(onError, err, \"fetch\", location);\n          return await FALLBACKIMAGES[type]();\n        }\n        continue;\n      }\n\n      if (response.status < 200 || response.status >= 300) {\n        safeOnError(\n          onError,\n          new Error(`non-2xx status ${response.status}`),\n          \"fetch\",\n          currentUrl,\n        );\n        return await FALLBACKIMAGES[type]();\n      }\n\n      const contentType = (\n        response.headers?.[\"content-type\"] as string | undefined\n      )\n        ?.toLowerCase()\n        ?.split(\";\")[0]\n        ?.trim();\n      const allowedMimeTypes = Object.values(mimeTypes);\n\n      if (contentType && allowedMimeTypes.includes(contentType)) {\n        return Buffer.from(response.data as ArrayBuffer);\n      }\n      safeOnError(\n        onError,\n        new Error(\n          `disallowed content-type ${contentType ?? \"missing\"} for ${currentUrl}`,\n        ),\n        \"fetch\",\n        currentUrl,\n      );\n      return await FALLBACKIMAGES[type]();\n    }\n    // exhausted redirect budget\n    safeOnError(\n      onError,\n      new Error(`exceeded maxRedirects=${maxRedirects}`),\n      \"fetch\",\n      src,\n    );\n    return await FALLBACKIMAGES[type]();\n  } catch (err) {\n    safeOnError(onError, err, \"fetch\", src);\n    return await FALLBACKIMAGES[type]();\n  }\n};\n\n/**\n * Reads an image from the local file system.\n *\n * @param {string} filePath - Path to the image file.\n * @param {string} baseDir - Base directory to resolve paths.\n * @param {ImageType} [type=\"normal\"] - Type of fallback image if the path is invalid.\n * @returns {Promise<Buffer>} A buffer containing the image data.\n */\nexport const readLocalImage = async (\n  filePath: string,\n  baseDir: string,\n  type: ImageType = \"normal\",\n  maxBytes?: number,\n  onError?: PixelServeOnError,\n): Promise<Buffer> => {\n  const isValid = await isValidPath(baseDir, filePath);\n  if (!isValid) {\n    safeOnError(\n      onError,\n      new Error(`invalid local path: ${filePath}`),\n      \"fs\",\n      filePath,\n    );\n    return await FALLBACKIMAGES[type]();\n  }\n  try {\n    const resolvedFile = path.resolve(baseDir, filePath);\n    if (maxBytes) {\n      const stats = await fs.stat(resolvedFile);\n      if (stats.size > maxBytes) {\n        safeOnError(\n          onError,\n          new Error(\n            `local file ${filePath} exceeds maxDownloadBytes (${stats.size} > ${maxBytes})`,\n          ),\n          \"fs\",\n          filePath,\n        );\n        return await FALLBACKIMAGES[type]();\n      }\n    }\n    return await fs.readFile(resolvedFile);\n  } catch (err) {\n    safeOnError(onError, err, \"fs\", filePath);\n    return await FALLBACKIMAGES[type]();\n  }\n};\n\n/**\n * Strips a leading prefix from `pathname` using either a literal-string\n * `apiPrefix` (preferred, ReDoS-free) or a user-supplied `apiRegex`. When\n * both are provided, `apiPrefix` wins — the regex is not evaluated at all,\n * so a vulnerable pattern in `apiRegex` cannot reach this code path.\n *\n * Exported for unit-testability of the precedence + prefix matching logic.\n */\nexport const stripApiPrefix = (\n  pathname: string,\n  apiRegex: RegExp,\n  apiPrefix: string | undefined,\n): string => {\n  if (apiPrefix !== undefined) {\n    return pathname.startsWith(apiPrefix)\n      ? pathname.slice(apiPrefix.length)\n      : pathname;\n  }\n  return pathname.replace(apiRegex, \"\");\n};\n\n/**\n * Fetches an image from either a local file or a network source.\n *\n * @param {string} src - The URL or local path of the image.\n * @param {string} baseDir - Base directory to resolve local paths.\n * @param {string} websiteURL - The URL of the website.\n * @param {ImageType} [type=\"normal\"] - Type of fallback image if the path is invalid.\n * @param {string[]} [allowedNetworkList=[]] - List of allowed network hosts.\n * @returns {Promise<Buffer>} A buffer containing the image data or a fallback image.\n */\nexport const fetchImage = (\n  src: string,\n  baseDir: string,\n  websiteURL: string | undefined,\n  type: ImageType = \"normal\",\n  apiRegex: RegExp,\n  allowedNetworkList: string[] = [],\n  {\n    timeoutMs,\n    maxBytes,\n    maxRedirects = 3,\n    onError,\n    apiPrefix,\n  }: {\n    timeoutMs: number;\n    maxBytes: number;\n    maxRedirects?: number;\n    onError?: PixelServeOnError;\n    apiPrefix?: string;\n  },\n): Promise<Buffer> => {\n  try {\n    const url = new URL(src);\n    const isInternal =\n      websiteURL !== undefined &&\n      [websiteURL, `www.${websiteURL}`].includes(url.hostname);\n\n    if (isInternal) {\n      const localPath = stripApiPrefix(url.pathname, apiRegex, apiPrefix);\n      return readLocalImage(localPath, baseDir, type, maxBytes, onError);\n    }\n\n    const allowedCondition = isHostAllowed(\n      url.hostname,\n      url.host,\n      allowedNetworkList,\n    );\n    if (!allowedCondition) {\n      safeOnError(\n        onError,\n        new Error(`host ${url.hostname} not in allowedNetworkList`),\n        \"fetch\",\n        src,\n      );\n      return FALLBACKIMAGES[type]();\n    }\n    if (![\"http:\", \"https:\"].includes(url.protocol)) {\n      safeOnError(\n        onError,\n        new Error(`disallowed protocol ${url.protocol}`),\n        \"fetch\",\n        src,\n      );\n      return FALLBACKIMAGES[type]();\n    }\n    return fetchFromNetwork(src, type, {\n      timeoutMs,\n      maxBytes,\n      allowedNetworkList,\n      maxRedirects,\n      onError,\n    });\n  } catch (err) {\n    safeOnError(onError, err, \"fetch\", src);\n    return readLocalImage(src, baseDir, type, maxBytes, onError);\n  }\n};\n","import { z } from \"zod\";\nimport type {\n  ImageFormat,\n  PixelServeOnError,\n  PixelServeOnComplete,\n} from \"./types\";\nimport { API_REGEX, allowedFormats } from \"./variables\";\n\nconst imageFormatEnum = z.enum(allowedFormats as [string, ...string[]]);\nconst imageTypeEnum = z.enum([\"avatar\", \"normal\"]);\n\nexport const userDataSchema = z\n  .object({\n    src: z\n      // Reject arrays/objects/numbers/booleans with a clear error — a\n      // malicious or buggy client sending `?src[]=a&src[]=b` (which Express\n      // parses as an array) gets a meaningful rejection here. Anything that\n      // is not a primitive string or `undefined`/`null` falls into the outer\n      // pipeline catch and produces the standard fallback image rather than\n      // `[object Object]` being forwarded through downstream logic.\n      //\n      // `src` is intentionally truly optional with no `.min(1)` and no\n      // default — empty strings and `undefined` are both valid inputs at\n      // the schema layer, and `pixel.ts` handles them via the\n      // `if (!userData.src)` branch which serves the appropriate fallback\n      // image based on the requested `type`.\n      .preprocess((value, ctx) => {\n        if (value === undefined || value === null) return value;\n        if (typeof value === \"string\") return value;\n        const got = Array.isArray(value) ? \"array\" : typeof value;\n        ctx.addIssue({\n          code: z.ZodIssueCode.custom,\n          message: `src must be a string (received ${got})`,\n        });\n        return z.NEVER;\n      }, z.string().optional())\n      .optional(),\n    format: z\n      .string()\n      .optional()\n      .transform((val): ImageFormat | undefined => {\n        const lower = val?.toLowerCase();\n        return lower && imageFormatEnum.options.includes(lower)\n          ? (lower as ImageFormat)\n          : undefined;\n      })\n      .optional(),\n    width: z\n      .union([z.number(), z.string()])\n      .optional()\n      .transform((value) =>\n        value === undefined || value === null ? undefined : Number(value),\n      )\n      .pipe(\n        z\n          .number()\n          .int()\n          .min(50, \"width too small\")\n          .max(4000, \"width too large\")\n          .optional(),\n      ),\n    height: z\n      .union([z.number(), z.string()])\n      .optional()\n      .transform((value) =>\n        value === undefined || value === null ? undefined : Number(value),\n      )\n      .pipe(\n        z\n          .number()\n          .int()\n          .min(50, \"height too small\")\n          .max(4000, \"height too large\")\n          .optional(),\n      ),\n    quality: z\n      .union([z.number(), z.string()])\n      .optional()\n      .transform((value) =>\n        value === undefined || value === null ? undefined : Number(value),\n      )\n      .pipe(z.number().int().min(1).max(100).default(80)),\n    folder: z.enum([\"public\", \"private\"]).default(\"public\"),\n    type: imageTypeEnum.default(\"normal\"),\n    userId: z\n      .union([z.string(), z.number()])\n      .optional()\n      .transform((value) =>\n        value === undefined || value === null\n          ? undefined\n          : String(value).trim(),\n      )\n      .pipe(\n        z\n          .string()\n          .min(1, \"userId cannot be empty\")\n          .max(128, \"userId too long\")\n          .optional(),\n      ),\n  })\n  .strict();\n\nexport const optionsSchema = z\n  .object({\n    baseDir: z.string().min(1, \"baseDir is required\"),\n    idHandler: z\n      .custom<\n        (id: string) => string | Promise<string>\n      >((val) => typeof val === \"function\", { message: \"idHandler must be a function\" })\n      .optional(),\n    getUserFolder: z\n      .custom<\n        (req: unknown, id?: string) => Promise<string> | string\n      >((val) => typeof val === \"function\", { message: \"getUserFolder must be a function\" })\n      .optional(),\n    getUserFolderRootDir: z.string().min(1).optional(),\n    websiteURL: z\n      .union([\n        z.url(),\n        // Hostname grammar with NO nested quantifiers — every label is 1-63\n        // alphanumeric/hyphen chars, labels are dot-separated, and no label\n        // may start with `-`. The earlier `/^(?![-.])([\\w]+[-.]?)*[\\w]+$/`\n        // was ReDoS-vulnerable (catastrophic backtracking on inputs like\n        // `\"a\".repeat(50) + \"!\"`); this anchored pattern runs in linear time\n        // because the outer group is a flat `(\\.label)*` with no nested\n        // repetition. Single-label hostnames (`localhost`) and FQDNs both\n        // validate; the previous regex's underscore acceptance is dropped\n        // because RFC 1123 hostnames do not include `_`.\n        z\n          .string()\n          .regex(/^(?!-)[A-Za-z0-9-]{1,63}(\\.(?!-)[A-Za-z0-9-]{1,63})*$/),\n      ])\n      .optional(),\n    apiRegex: z.instanceof(RegExp).default(API_REGEX),\n    apiPrefix: z.string().min(1, \"apiPrefix cannot be empty\").optional(),\n    allowedNetworkList: z\n      .array(\n        z\n          .string()\n          // Trim FIRST so the regex/min-length checks below operate on the\n          // operator-intended hostname rather than incidental whitespace\n          // injected by `.env` files or CI config copies.\n          .transform((value) => value.trim())\n          .pipe(\n            z\n              .string()\n              .min(1, \"allowedNetworkList entries cannot be empty\")\n              // Hostnames only: letters, digits, dots, hyphens. Rejects\n              // whitespace-only entries (after trim → empty → caught by\n              // `.min(1)`) and any entry containing path/protocol/internal-\n              // whitespace characters. Defence in depth — the Task 2\n              // lowercase transform below already collapses an empty entry\n              // into `\"\"` which would silently match\n              // `url.hostname === \"\"` for inputs like `http:///path`. The\n              // regex is intentionally permissive (no FQDN structure\n              // enforcement) so single-label hosts and IDN-encoded punycode\n              // labels still parse.\n              .regex(\n                /^[a-z0-9.-]+$/i,\n                \"allowedNetworkList entry is not a valid hostname\",\n              ),\n          ),\n      )\n      // Task 2 contract: also lowercase so case-mismatched config still\n      // matches the WHATWG-URL-lowercased `url.hostname`. The trim above\n      // happens per-entry inside the inner schema; this transform finishes\n      // the normalisation.\n      .transform((arr) => arr.map((host) => host.toLowerCase()))\n      .default([]),\n    cacheControl: z.string().optional(),\n    etag: z.boolean().default(true),\n    minWidth: z.number().int().positive().default(50),\n    maxWidth: z.number().int().positive().default(4000),\n    minHeight: z.number().int().positive().default(50),\n    maxHeight: z.number().int().positive().default(4000),\n    defaultQuality: z.number().int().min(1).max(100).default(80),\n    requestTimeoutMs: z.number().int().positive().default(5000),\n    idHandlerTimeoutMs: z.number().int().positive().optional(),\n    maxDownloadBytes: z.number().int().positive().default(5_000_000),\n    maxRedirects: z.number().int().min(0).max(10).default(3),\n    maxInputPixels: z\n      .number()\n      .int()\n      .positive()\n      .default(16_000 * 16_000),\n    allowSvgInput: z.boolean().default(false),\n    onError: z\n      .custom<PixelServeOnError>((val) => typeof val === \"function\", {\n        message: \"onError must be a function\",\n      })\n      .optional(),\n    onComplete: z\n      .custom<PixelServeOnComplete>((val) => typeof val === \"function\", {\n        message: \"onComplete must be a function\",\n      })\n      .optional(),\n  })\n  .strict()\n  .refine((data) => data.minWidth <= data.maxWidth, {\n    message: \"minWidth must be less than or equal to maxWidth\",\n    path: [\"minWidth\"],\n  })\n  .refine((data) => data.minHeight <= data.maxHeight, {\n    message: \"minHeight must be less than or equal to maxHeight\",\n    path: [\"minHeight\"],\n  });\n\nexport type ParsedUserData = z.infer<typeof userDataSchema>;\nexport type ParsedOptions = z.infer<typeof optionsSchema>;\n","import { optionsSchema, userDataSchema } from \"./schema\";\nimport type { ParsedOptions, ParsedUserData } from \"./schema\";\nimport type { ImageFormat, PixelServeOptions } from \"./types\";\n\n/**\n * @typedef {(\"avatar\" | \"normal\")} ImageType\n * @description Defines the type of image being processed.\n */\n\n/**\n * @typedef {(\"jpeg\" | \"jpg\" | \"png\" | \"webp\" | \"gif\" | \"tiff\" | \"avif\")} ImageFormat\n * @description Supported output formats. SVG is intentionally excluded because\n *   Sharp/libvips cannot re-encode SVG output.\n */\n\n/**\n * @typedef {Object} Options\n * @property {string} baseDir - The base directory for public image files.\n * @property {function(string): string} idHandler - A function to handle user IDs.\n * @property {function(string, Request): Promise<string>} getUserFolder - Asynchronous function to retrieve user-specific folders.\n * @property {string} websiteURL - The base URL of the website for internal link resolution.\n * @property {RegExp} apiRegex - Regex to parse API endpoints from URLs.\n * @property {string[]} allowedNetworkList - List of allowed network domains for external image fetching.\n */\n\n/**\n * @typedef {Object} UserData\n * @property {number|string} quality - Quality of the image (1–100).\n * @property {ImageFormat} format - Desired format of the image.\n * @property {string} [src] - Source path or URL for the image.\n * @property {string} [folder] - The folder type (\"public\" or \"private\").\n * @property {ImageType} [type] - Type of the image (\"avatar\" or \"normal\").\n * @property {string|null} [userId] - Optional user identifier.\n * @property {number|string} [width] - Desired image width.\n * @property {number|string} [height] - Desired image height.\n */\n\n/**\n * Renders the options object with default values and user-provided values.\n *\n * @param {Partial<Options>} options - The user-provided options.\n * @returns {Options} The rendered options object.\n */\nexport const renderOptions = (options: PixelServeOptions): ParsedOptions =>\n  optionsSchema.parse(options);\n\n/**\n * Renders the user data object with default values and user-provided values.\n *\n * @param {Partial<UserData>} userData - The user-provided data.\n * @returns {UserData} The rendered user data object.\n */\n/**\n * Result of `renderUserData`. Narrower than `ParsedUserData` (Zod-inferred):\n * `format` is guaranteed to be an `ImageFormat` (defaulting to `\"jpeg\"`),\n * and `quality` is guaranteed to be a number (defaulting to\n * `bounds.defaultQuality`). The remaining fields keep their Zod-inferred\n * types, so callers can drop ad-hoc `as ImageFormat` / `as ImageType`\n * casts in favor of the validated shape.\n */\nexport type RenderedUserData = Omit<ParsedUserData, \"format\" | \"quality\"> & {\n  format: ImageFormat;\n  quality: number;\n};\n\nexport const renderUserData = (\n  userData: unknown,\n  bounds: {\n    minWidth: number;\n    maxWidth: number;\n    minHeight: number;\n    maxHeight: number;\n    defaultQuality: number;\n  },\n): RenderedUserData => {\n  const parsed = userDataSchema.parse(userData);\n\n  const clamp = (\n    value: number | undefined,\n    min: number,\n    max: number,\n  ): number | undefined => {\n    if (value === undefined) return undefined;\n    return Math.min(Math.max(value, min), max);\n  };\n\n  return {\n    ...parsed,\n    width: clamp(parsed.width, bounds.minWidth, bounds.maxWidth),\n    height: clamp(parsed.height, bounds.minHeight, bounds.maxHeight),\n    quality: parsed.quality ?? bounds.defaultQuality,\n    format: parsed.format ?? \"jpeg\",\n  };\n};\n","import path from \"node:path\";\nimport * as fs from \"node:fs/promises\";\nimport { createHash } from \"node:crypto\";\nimport sharp, { FormatEnum, ResizeOptions } from \"sharp\";\nimport type { Request, Response, NextFunction } from \"express\";\nimport type {\n  PixelServeOptions,\n  ImageFormat,\n  ImageType,\n  PixelServeErrorContext,\n  PixelServeOnError,\n  PixelServeCompletionContext,\n  PixelServeOnComplete,\n} from \"./types\";\nimport { allowedFormats, FALLBACKIMAGES, mimeTypes } from \"./variables\";\nimport { fetchImage, readLocalImage } from \"./functions\";\nimport { renderOptions, renderUserData } from \"./renders\";\nimport type { ParsedOptions } from \"./schema\";\n\n/**\n * Best-effort observability hook dispatcher. Swallows hook errors so a buggy\n * logger never crashes a request. Returns void.\n */\nconst reportError = (\n  hook: PixelServeOnError | undefined,\n  err: unknown,\n  context: PixelServeErrorContext,\n): void => {\n  if (!hook) return;\n  try {\n    hook(err, context);\n  } catch {\n    // intentionally suppressed — observability must never break the response\n  }\n};\n\n/**\n * Best-effort observability hook dispatcher for the success / 304 path.\n * Swallows hook errors so a buggy logger never crashes a request. Returns\n * void. Mirrors `reportError`'s contract so consumers can rely on the same\n * dispatch guarantees for both observability surfaces.\n */\nconst safeOnComplete = (\n  hook: PixelServeOnComplete | undefined,\n  context: PixelServeCompletionContext,\n): void => {\n  if (!hook) return;\n  try {\n    hook(context);\n  } catch {\n    // intentionally suppressed — observability must never break the response\n  }\n};\n\n/**\n * Computes the elapsed milliseconds between a `process.hrtime.bigint()`\n * checkpoint and now. Uses bigint math so monotonic-clock precision is\n * preserved (we lose precision when we convert back to a `Number`, but the\n * resulting ms float is plenty precise for APM latency reporting).\n */\nconst elapsedMs = (start: bigint): number => {\n  const diff = process.hrtime.bigint() - start;\n  // 1 ms = 1_000_000 ns. We do the division in bigint first to avoid\n  // overflowing through Number for absurdly long latencies, then attach the\n  // sub-millisecond remainder as a float.\n  const whole = Number(diff / 1_000_000n);\n  const remainder = Number(diff % 1_000_000n) / 1_000_000;\n  return whole + remainder;\n};\n\n/**\n * Derives an ASCII-safe filename and an RFC 5987 / RFC 6266\n * `filename*=UTF-8''<percent-encoded>` parameter for use in\n * `Content-Disposition`.\n *\n *  - Strips query strings and URL fragments before extracting basename.\n *  - Strips the existing extension and replaces it with the encoded output\n *    format extension supplied by the caller.\n *  - Maps every non-printable-ASCII / forbidden-header byte to `_` for the\n *    fallback `filename=` parameter (RFC 5987 prohibits raw non-ASCII bytes).\n *  - Percent-encodes the original (UTF-8) basename for the `filename*=`\n *    parameter so unicode names round-trip cleanly.\n *  - Caps both parameters to a sane length so absurd filenames cannot bloat\n *    the response header. The cap is applied AFTER the extension is appended\n *    so the extension is never truncated.\n */\nconst FILENAME_MAX_LEN = 100;\nconst ASCII_FALLBACK_DEFAULT = \"image\";\n\nexport const buildFilename = (\n  rawSrc: string | undefined,\n  outputFormat: string,\n): { asciiFilename: string; encodedFilename: string } => {\n  const ext = outputFormat;\n  // strip query + fragment, then take basename (URL or path-shaped)\n  const stripped = (rawSrc ?? \"\").split(\"#\")[0]!.split(\"?\")[0]!;\n  const baseWithExt = path.basename(stripped);\n  const baseNoExt =\n    baseWithExt && baseWithExt !== \"/\" && baseWithExt !== \"\\\\\"\n      ? path.basename(baseWithExt, path.extname(baseWithExt))\n      : \"\";\n\n  // ASCII fallback: replace any byte outside the safe printable ASCII range,\n  // plus quote / backslash / control / DEL, with `_`. Collapse runs of `_`\n  // into one, then strip a single leading/trailing `_` via direct string\n  // ops — the regex form `/^_+|_+$/g` is flagged by CodeQL `js/polynomial-redos`\n  // even though the prior collapse guarantees a single underscore in a row.\n  let asciiBase = baseNoExt\n    .replace(/[^\\x20-\\x7E]/g, \"_\")\n    // eslint-disable-next-line no-control-regex\n    .replace(/[\"\\\\\\x00-\\x1F\\x7F]/g, \"_\")\n    .replace(/_+/g, \"_\");\n  if (asciiBase.startsWith(\"_\")) asciiBase = asciiBase.slice(1);\n  if (asciiBase.endsWith(\"_\")) asciiBase = asciiBase.slice(0, -1);\n\n  const safeAsciiBase =\n    asciiBase.length > 0 ? asciiBase : ASCII_FALLBACK_DEFAULT;\n  // Cap base length so the FULL filename stays under the limit including the\n  // extension. +1 accounts for the dot.\n  const maxBase = Math.max(1, FILENAME_MAX_LEN - ext.length - 1);\n  const truncatedAscii = safeAsciiBase.slice(0, maxBase);\n  const asciiFilename = `${truncatedAscii}.${ext}`;\n\n  // RFC 5987 encoded value: percent-encode the UTF-8 bytes. We use\n  // encodeURIComponent and then re-encode the few characters it leaves alone\n  // that are still illegal inside a quoted parameter value per RFC 5987\n  // (`'*` are reserved in attr-char; `()<>@,;:\\\"/[]?={}` are tspecials).\n  const utfBase = baseNoExt.length > 0 ? baseNoExt : ASCII_FALLBACK_DEFAULT;\n  const encodedBase = encodeURIComponent(utfBase).replace(\n    /['()*]/g,\n    (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`,\n  );\n  // Apply the same overall length cap to the encoded form (percent-encoded\n  // bytes count toward the limit so very long unicode names stay bounded).\n  let truncatedEncoded = encodedBase.slice(0, maxBase);\n  // The slice may land INSIDE a `%XX` triplet (e.g., cutting `%E4%B8` to\n  // `%E4%B` for a long CJK name). Strict RFC 3986 / RFC 5987 parsers reject\n  // partial percent-encodings, so walk back to the nearest `%` and drop any\n  // incomplete trailing sequence. Triplets that fit (`len - lastPercent >= 3`)\n  // are kept verbatim.\n  const lastPercent = truncatedEncoded.lastIndexOf(\"%\");\n  if (lastPercent >= 0 && truncatedEncoded.length - lastPercent < 3) {\n    truncatedEncoded = truncatedEncoded.slice(0, lastPercent);\n  }\n  // The truncation may also leave an incomplete UTF-8 multi-byte sequence\n  // (e.g., `%E4` is a valid percent-encoded byte but `0xE4` alone is not\n  // valid UTF-8 — it is the lead byte of a 3-byte sequence). Walk back past\n  // any trailing UTF-8 lead bytes that lost their continuation bytes so the\n  // header value decodes cleanly under strict UTF-8 parsers. Each triplet\n  // occupies exactly 3 characters (`%XX`) so the lookback is bounded.\n  while (truncatedEncoded.length >= 3) {\n    const tail = truncatedEncoded.slice(-3);\n    if (tail[0] !== \"%\") break;\n    const byte = parseInt(tail.slice(1), 16);\n    // 0xC0-0xFD are UTF-8 lead bytes (2-byte through 6-byte sequences in the\n    // historical encoding; only 2-4 byte sequences are valid today). A lead\n    // byte at the very end has no continuation byte after it, so drop it.\n    // Continuation bytes (0x80-0xBF) sitting alone at the end without their\n    // preceding lead byte are also invalid and must be dropped — keep walking\n    // back until we find an ASCII byte or a complete multi-byte sequence.\n    if (byte >= 0xc0 && byte <= 0xfd) {\n      truncatedEncoded = truncatedEncoded.slice(0, -3);\n      break;\n    }\n    if (byte >= 0x80 && byte <= 0xbf) {\n      // Standalone continuation byte: drop it and re-evaluate the new tail.\n      truncatedEncoded = truncatedEncoded.slice(0, -3);\n      continue;\n    }\n    break;\n  }\n  const encodedFilename = `${truncatedEncoded}.${ext}`;\n\n  return { asciiFilename, encodedFilename };\n};\n\n/**\n * Returns a stable source identifier for the deterministic ETag.\n *\n *  - Local files contribute `mtimeMs:size`, so any edit to the underlying\n *    file invalidates the cache key.\n *  - Remote URLs contribute the resolved URL string. The framework cannot\n *    cheaply re-fetch HEAD per request, so the URL is the strongest\n *    identifier available without paying for the body.\n *  - Anything else (missing file, fallback paths) returns `null` so the\n *    caller falls back to the post-Sharp buffer hash.\n */\nexport const buildSourceIdentifier = async (\n  src: string | undefined,\n  baseDir: string,\n): Promise<string | null> => {\n  if (!src) return null;\n  if (src.startsWith(\"http://\") || src.startsWith(\"https://\")) {\n    return `url:${src}`;\n  }\n  try {\n    const resolved = path.resolve(baseDir, src);\n    const stats = await fs.stat(resolved);\n    return `file:${stats.mtimeMs}:${stats.size}`;\n  } catch {\n    return null;\n  }\n};\n\n/**\n * Builds the deterministic SHA-256 ETag from the resolved user data + source\n * identifier. The result is wrapped in double-quotes per RFC 7232.\n *\n * SHA-256 is used over SHA-1 because the input contains user-controlled fields\n * (post-`idHandler` userId, src). SHA-1's collision weakness flagged by CodeQL\n * `js/weak-cryptographic-algorithm` does not affect ETag correctness in\n * practice, but a modern hash keeps static analysis green and removes any\n * theoretical concern about a third party forging a matching ETag.\n */\nexport const buildDeterministicEtag = (\n  fields: {\n    src: string | undefined;\n    width: number | undefined;\n    height: number | undefined;\n    format: string;\n    quality: number;\n    type: ImageType;\n    folder: \"public\" | \"private\";\n    parsedUserId: string | undefined;\n  },\n  sourceIdentifier: string,\n): string => {\n  const key = JSON.stringify({\n    src: fields.src ?? \"\",\n    w: fields.width ?? \"\",\n    h: fields.height ?? \"\",\n    f: fields.format,\n    q: fields.quality,\n    t: fields.type,\n    fo: fields.folder,\n    u: fields.parsedUserId ?? \"\",\n    sid: sourceIdentifier,\n  });\n  return `\"${createHash(\"sha256\").update(key).digest(\"hex\")}\"`;\n};\n\n/**\n * @typedef {Object} Options\n * @property {string} baseDir - The base directory for public image files.\n * @property {function(string): string} idHandler - A function to handle user IDs.\n * @property {function(string, Request): Promise<string>} getUserFolder - Asynchronous function to retrieve user-specific folders.\n * @property {string} websiteURL - The base URL of the website for internal link resolution.\n * @property {RegExp} apiRegex - Regex to parse API endpoints from URLs.\n * @property {string[]} allowedNetworkList - List of allowed network domains for external image fetching.\n */\n\n/**\n * Races a promise against a timeout and clears the timer on settle so it\n * cannot pin the event loop after the race resolves.\n */\nconst raceWithTimeout = async <T>(\n  promise: Promise<T>,\n  ms: number,\n  label: string,\n): Promise<T> => {\n  let timer: NodeJS.Timeout | undefined;\n  try {\n    return await Promise.race([\n      promise,\n      new Promise<never>((_, reject) => {\n        timer = setTimeout(\n          () => reject(new Error(`${label} timed out after ${ms}ms`)),\n          ms,\n        );\n      }),\n    ]);\n  } finally {\n    if (timer !== undefined) clearTimeout(timer);\n  }\n};\n\n/**\n * Verifies that `candidate` is contained within `rootDir`. Used to enforce\n * `getUserFolderRootDir` containment so a buggy or malicious `getUserFolder`\n * implementation cannot expand the framework's filesystem surface area\n * beyond an opt-in root.\n *\n * Both the **root** and the **candidate** are normalized via `fs.realpath`\n * before the containment check. This catches symlink escapes (a path that\n * lexically lives inside the root but whose final segment is a symlink\n * pointing outward) at the containment layer rather than waiting for the\n * downstream `isValidPath` read. When `fs.realpath` fails — typically\n * because the candidate is a lazy per-user directory that doesn't exist\n * yet — the function falls back to the lexical `path.resolve` value so\n * containment can still be evaluated; the descendant `isValidPath()`\n * check then realpaths the actual file before reading.\n *\n * The optional `preResolvedRoot` parameter lets the middleware factory\n * cache the resolved root path once at startup and skip the per-request\n * `fs.realpath` syscall on the root side. When supplied, the function\n * treats it as the already-resolved value.\n *\n * Returns `true` when the candidate is inside the root (or equal to it).\n * Returns `false` for empty inputs and any escape detected lexically or\n * via realpath.\n */\nexport const isInsideRoot = async (\n  rootDir: string,\n  candidate: string,\n  preResolvedRoot?: string,\n): Promise<boolean> => {\n  if (!rootDir || !candidate) return false;\n  let realRoot: string;\n  if (preResolvedRoot !== undefined) {\n    // The factory already paid the realpath cost — use the cached value.\n    realRoot = preResolvedRoot;\n  } else {\n    try {\n      realRoot = await fs.realpath(path.resolve(rootDir));\n    } catch {\n      // Root does not exist or is unreadable; fall back to lexical resolve\n      // so a not-yet-created root tree can still be evaluated. Symlink\n      // escapes from this branch are out of scope — the caller opted into a\n      // root that does not exist yet, and any descendant `isValidPath`\n      // check will still realpath the final candidate before reading.\n      realRoot = path.resolve(rootDir);\n    }\n  }\n\n  const lexicalCandidate = path.resolve(candidate);\n  // Resolve the candidate through realpath so a `getUserFolder` result that\n  // is a symlink pointing outside the root is caught here rather than\n  // silently passing the lexical-prefix check. When the candidate does not\n  // exist on disk yet, fall back to the lexical resolve — the descendant\n  // isValidPath() check will realpath the final file before reading.\n  let realCandidate: string;\n  try {\n    realCandidate = await fs.realpath(lexicalCandidate);\n  } catch {\n    realCandidate = lexicalCandidate;\n  }\n  if (realRoot === realCandidate) return true;\n  const relative = path.relative(realRoot, realCandidate);\n  if (relative === \"\" || relative === \".\") return true;\n  return !relative.startsWith(\"..\") && !path.isAbsolute(relative);\n};\n\n/**\n * Resolves a configured `getUserFolderRootDir` to its canonical realpath\n * once at middleware-factory time. Returns the lexically-resolved path\n * when the directory does not yet exist or `fs.realpath` fails so a lazy\n * containment root can still be evaluated against future requests.\n *\n * Exported so consumers can pre-resolve their own roots for unit tests.\n */\nexport const resolveRootDir = async (rootDir: string): Promise<string> => {\n  try {\n    return await fs.realpath(path.resolve(rootDir));\n  } catch {\n    return path.resolve(rootDir);\n  }\n};\n\n/**\n * Detects whether a buffer is an SVG by inspecting its leading bytes for\n * common SVG / XML markers. Tolerates UTF-8 BOM, UTF-16 BE/LE BOMs, leading\n * ASCII whitespace (incl. whitespace BEFORE a BOM), `<?xml` prologs, and\n * `<!--` comments preceding the `<svg` root element. Reads up to 4 KiB so\n * pathologically large XML prologs cannot push `<svg` out of the inspection\n * window.\n *\n * The detector is intentionally conservative — any buffer that looks even\n * vaguely SVG-shaped is rejected when `allowSvgInput` is false. This guards\n * against billion-laughs / nested-use SVG bombs that libvips/librsvg parses.\n */\nexport const looksLikeSvg = (buf: Buffer): boolean => {\n  if (!buf || buf.length === 0) return false;\n  let start = 0;\n  // Skip leading ASCII whitespace (tab, LF, CR, space) before checking BOMs.\n  // An attacker who prefixes a single 0x20 byte before the BOM previously\n  // bypassed the detector.\n  while (\n    start < buf.length &&\n    (buf[start] === 0x09 ||\n      buf[start] === 0x0a ||\n      buf[start] === 0x0d ||\n      buf[start] === 0x20)\n  ) {\n    start++;\n  }\n\n  // UTF-16 BE / LE BOMs — re-decode the head as UTF-16 then run the heuristic.\n  // UTF-16 SVGs are exotic but cheap to defend against.\n  if (\n    buf.length >= start + 2 &&\n    ((buf[start] === 0xfe && buf[start + 1] === 0xff) ||\n      (buf[start] === 0xff && buf[start + 1] === 0xfe))\n  ) {\n    const isLe = buf[start] === 0xff;\n    const sliceEnd = Math.min(buf.length, start + 2 + 4096);\n    // Node's `toString(\"utf16le\")` decodes LE bytes verbatim. For BE we swap\n    // bytes pair-wise into a temp buffer so the same decoder produces the\n    // intended characters.\n    let head16: string;\n    if (isLe) {\n      head16 = buf.subarray(start + 2, sliceEnd).toString(\"utf16le\");\n    } else {\n      const beSrc = buf.subarray(start + 2, sliceEnd);\n      const swapped = Buffer.alloc(beSrc.length - (beSrc.length % 2));\n      for (let i = 0; i + 1 < beSrc.length; i += 2) {\n        swapped[i] = beSrc[i + 1]!;\n        swapped[i + 1] = beSrc[i]!;\n      }\n      head16 = swapped.toString(\"utf16le\");\n    }\n    const trimmed = head16.trimStart().toLowerCase();\n    if (trimmed.startsWith(\"<svg\")) return true;\n    if (trimmed.startsWith(\"<?xml\") || trimmed.startsWith(\"<!--\")) {\n      return /<svg[\\s>]/.test(trimmed);\n    }\n    return /<svg[\\s>]/.test(trimmed);\n  }\n\n  // UTF-8 BOM.\n  if (\n    buf.length >= start + 3 &&\n    buf[start] === 0xef &&\n    buf[start + 1] === 0xbb &&\n    buf[start + 2] === 0xbf\n  ) {\n    start += 3;\n  }\n\n  // Read up to 4 KiB (was 1 KiB) as latin1 to avoid utf8 decode cost; SVG is\n  // ASCII so latin1 round-trips every meaningful byte. The wider window\n  // covers pathological XML prologs that pad with comments / DOCTYPE before\n  // `<svg`.\n  const head = buf\n    .subarray(start, Math.min(buf.length, start + 4096))\n    .toString(\"latin1\")\n    .trimStart()\n    .toLowerCase();\n  if (head.startsWith(\"<svg\")) return true;\n  if (head.startsWith(\"<?xml\") || head.startsWith(\"<!--\")) {\n    return /<svg[\\s>]/.test(head);\n  }\n  return false;\n};\n\n/**\n * @function serveImage\n * @description Processes and serves an image based on user data and options.\n * @param {Request} req - The Express request object.\n * @param {Response} res - The Express response object.\n * @param {NextFunction} next - The Express next function.\n * @param {ParsedOptions} parsedOptions - Already-validated options produced\n *   once by `registerServe`. The Zod schema parse is paid at factory time\n *   so the request hot path is purely arithmetic — see Task 4.\n * @param {string | undefined} cachedRealRoot - Optional pre-resolved\n *   realpath of `options.getUserFolderRootDir`, populated once by the\n *   middleware factory so per-request containment checks do not pay a\n *   fresh `fs.realpath` syscall on the root side.\n * @returns {Promise<void>}\n */\nconst serveImage = async (\n  req: Request,\n  res: Response,\n  next: NextFunction,\n  parsedOptions: ParsedOptions,\n  cachedRealRoot?: string,\n): Promise<void> => {\n  // Monotonic timestamp captured at the top of every request so the onComplete\n  // hook can report end-to-end pipeline latency regardless of which branch\n  // (200 happy path, 304 cached short-circuit, or fallback path) was taken.\n  const startedAt = process.hrtime.bigint();\n  let requestedType: ImageType = \"normal\";\n  // The schema parse already ran once at factory time, so `onError` and\n  // `onComplete` are already the validated function references. Aliased into\n  // locals so the outer catch (and the same-named helpers below) can read\n  // them without re-deriving the values on every request.\n  const onError: PixelServeOnError | undefined = parsedOptions.onError;\n  const onComplete: PixelServeOnComplete | undefined = parsedOptions.onComplete;\n  let observedSrc: string | undefined;\n  let observedUserId: string | undefined;\n  try {\n    let userData: ReturnType<typeof renderUserData>;\n    try {\n      // `req.query` is typed by Express as `ParsedQs` (recursive string /\n      // string[] / nested object). Pass through as `unknown` and let the\n      // Zod schema reject any shape that isn't a flat record of primitive\n      // strings/numbers — the schema preprocesses `src` to reject arrays\n      // (e.g., `?src[]=a&src[]=b`) with a clear error.\n      userData = renderUserData(req.query, {\n        minWidth: parsedOptions.minWidth,\n        maxWidth: parsedOptions.maxWidth,\n        minHeight: parsedOptions.minHeight,\n        maxHeight: parsedOptions.maxHeight,\n        defaultQuality: parsedOptions.defaultQuality,\n      });\n    } catch (err) {\n      reportError(onError, err, { phase: \"validation\" });\n      throw err;\n    }\n\n    observedSrc = userData.src;\n    observedUserId = userData.userId;\n    // userData.type is narrowed to `ImageType` by the schema enum default.\n    requestedType = userData.type ?? \"normal\";\n\n    let baseDir = parsedOptions.baseDir;\n    let parsedUserId: string | undefined;\n\n    if (userData.userId) {\n      parsedUserId = userData.userId;\n      if (parsedOptions.idHandler) {\n        const rawUserId = userData.userId;\n        const idTimeoutMs =\n          parsedOptions.idHandlerTimeoutMs ?? parsedOptions.requestTimeoutMs;\n        try {\n          const handlerResult = Promise.resolve().then(() =>\n            parsedOptions.idHandler!(rawUserId),\n          );\n          const candidate = await raceWithTimeout(\n            handlerResult,\n            idTimeoutMs,\n            \"idHandler\",\n          );\n          parsedUserId = typeof candidate === \"string\" ? candidate : rawUserId;\n          if (typeof candidate !== \"string\") {\n            reportError(\n              onError,\n              new Error(\n                `idHandler returned a non-string value (${typeof candidate})`,\n              ),\n              {\n                phase: \"idHandler\",\n                src: observedSrc,\n                userId: rawUserId,\n              },\n            );\n          }\n        } catch (err) {\n          // idHandler threw, rejected, or timed out — fall back to raw userId.\n          parsedUserId = rawUserId;\n          reportError(onError, err, {\n            phase: \"idHandler\",\n            src: observedSrc,\n            userId: rawUserId,\n          });\n        }\n        observedUserId = parsedUserId;\n      }\n    }\n\n    if (userData.folder === \"private\" && parsedOptions.getUserFolder) {\n      try {\n        // Wrap the invocation in `Promise.resolve().then(...)` so a\n        // synchronous throw from `getUserFolder` is captured as a rejection\n        // and routed through the timeout race + onError hook.\n        const folderPromise = Promise.resolve().then(() =>\n          parsedOptions.getUserFolder!(req, parsedUserId),\n        );\n        const dir = await raceWithTimeout(\n          folderPromise,\n          parsedOptions.requestTimeoutMs,\n          \"getUserFolder\",\n        );\n        if (dir) {\n          // When the user opts into `getUserFolderRootDir`, the framework\n          // validates that the returned path resolves to a descendant of the\n          // configured root via realpath + path.relative. If the path\n          // escapes (e.g., the user-supplied callback joined a malicious\n          // `../etc` userId, or a symlink redirects outside the tree), the\n          // resolver is treated as a failure: `onError` is invoked with\n          // `phase: \"getUserFolder\"` and the request falls back to the\n          // public `baseDir` configured on `PixelServeOptions`.\n          if (parsedOptions.getUserFolderRootDir) {\n            const inside = await isInsideRoot(\n              parsedOptions.getUserFolderRootDir,\n              dir,\n              cachedRealRoot,\n            );\n            if (!inside) {\n              reportError(\n                onError,\n                new Error(\n                  `getUserFolder returned path \"${dir}\" outside getUserFolderRootDir \"${parsedOptions.getUserFolderRootDir}\"`,\n                ),\n                {\n                  phase: \"getUserFolder\",\n                  src: observedSrc,\n                  userId: observedUserId,\n                },\n              );\n            } else {\n              baseDir = dir;\n            }\n          } else {\n            baseDir = dir;\n          }\n        }\n      } catch (err) {\n        // getUserFolder timed out or failed — use default baseDir\n        reportError(onError, err, {\n          phase: \"getUserFolder\",\n          src: observedSrc,\n          userId: observedUserId,\n        });\n      }\n    }\n\n    // `userData.format` is narrowed to `ImageFormat` by the schema — invalid\n    // formats coerce to `undefined`, the renderer fills in `\"jpeg\"`. The\n    // `allowedFormats.includes` check is defensive in case `allowedFormats`\n    // drifts away from the schema in the future.\n    const outputFormat: ImageFormat = allowedFormats.includes(userData.format)\n      ? userData.format\n      : \"jpeg\";\n\n    // ------------------------------------------------------------------\n    // Deterministic ETag: built BEFORE any Sharp work so `If-None-Match`\n    // can short-circuit decode + resize + re-encode entirely. The key\n    // combines every input that materially affects the response bytes\n    // (src, width, height, format, quality, type, folder, parsedUserId)\n    // plus a source identifier (mtime+size for local files, URL string\n    // for remote sources). Source-identifier failures degrade to \"no\n    // deterministic key available\" and the pipeline falls back to the\n    // legacy buffer hash (defense in depth).\n    // ------------------------------------------------------------------\n    // `buildSourceIdentifier` swallows its own filesystem errors and returns\n    // `null` when no stable key can be derived (missing file, etc.), so this\n    // call cannot throw and does not need its own try/catch.\n    const sourceIdentifier = await buildSourceIdentifier(userData.src, baseDir);\n\n    let etag: string | undefined;\n    if (parsedOptions.etag && sourceIdentifier) {\n      etag = buildDeterministicEtag(\n        {\n          src: userData.src,\n          width: userData.width,\n          height: userData.height,\n          format: outputFormat,\n          quality: userData.quality,\n          type: userData.type,\n          folder: userData.folder,\n          parsedUserId,\n        },\n        sourceIdentifier,\n      );\n      if (req.headers[\"if-none-match\"] === etag) {\n        // Short-circuit BEFORE Sharp is touched at all.\n        res.status(304).end();\n        safeOnComplete(onComplete, {\n          src: observedSrc,\n          userId: observedUserId,\n          format: outputFormat,\n          outputBytes: 0,\n          cached: true,\n          durationMs: elapsedMs(startedAt),\n        });\n        return;\n      }\n    }\n\n    const resolveBuffer = async (): Promise<Buffer> => {\n      if (!userData.src) {\n        // userData.type is always present (schema defaults to \"normal\").\n        return FALLBACKIMAGES[userData.type]();\n      }\n      if (\n        userData.src.startsWith(\"http://\") ||\n        userData.src.startsWith(\"https://\")\n      ) {\n        return fetchImage(\n          userData.src,\n          baseDir,\n          parsedOptions.websiteURL,\n          userData.type,\n          parsedOptions.apiRegex,\n          parsedOptions.allowedNetworkList,\n          {\n            timeoutMs: parsedOptions.requestTimeoutMs,\n            maxBytes: parsedOptions.maxDownloadBytes,\n            maxRedirects: parsedOptions.maxRedirects,\n            onError,\n            apiPrefix: parsedOptions.apiPrefix,\n          },\n        );\n      }\n      return readLocalImage(\n        userData.src,\n        baseDir,\n        userData.type,\n        parsedOptions.maxDownloadBytes,\n        onError,\n      );\n    };\n\n    const imageBuffer = await resolveBuffer();\n\n    if (!parsedOptions.allowSvgInput && looksLikeSvg(imageBuffer)) {\n      const err = new Error(\"svg input rejected\");\n      reportError(onError, err, {\n        phase: \"sharp\",\n        src: observedSrc,\n        userId: observedUserId,\n      });\n      throw err;\n    }\n\n    let processedImage: Buffer;\n    try {\n      let image = sharp(imageBuffer, {\n        failOn: \"warning\",\n        limitInputPixels: parsedOptions.maxInputPixels,\n        sequentialRead: true,\n        unlimited: false,\n      });\n\n      // Peek metadata first to avoid the expensive decode for hostile inputs.\n      const meta = await image.metadata();\n      if (meta.width && meta.height) {\n        if (meta.width * meta.height > parsedOptions.maxInputPixels) {\n          throw new Error(\"input exceeds maxInputPixels\");\n        }\n      }\n      if (!parsedOptions.allowSvgInput && meta.format === \"svg\") {\n        throw new Error(\"svg input rejected\");\n      }\n\n      // Re-instantiate Sharp because metadata() consumed the stream state.\n      image = sharp(imageBuffer, {\n        failOn: \"warning\",\n        limitInputPixels: parsedOptions.maxInputPixels,\n        sequentialRead: true,\n        unlimited: false,\n      }).rotate();\n\n      if (userData.width || userData.height) {\n        const resizeOptions: ResizeOptions = {\n          width: userData.width ?? undefined,\n          height: userData.height ?? undefined,\n          fit: sharp.fit.cover,\n          withoutEnlargement: true,\n        };\n        image = image.resize(resizeOptions);\n      }\n\n      processedImage = await image\n        .toFormat(outputFormat as keyof FormatEnum, {\n          quality: userData.quality,\n        })\n        .toBuffer();\n    } catch (err) {\n      reportError(onError, err, {\n        phase: \"sharp\",\n        src: observedSrc,\n        userId: observedUserId,\n      });\n      throw err;\n    }\n\n    // Fallback ETag: if no deterministic source identifier was available,\n    // hash the processed buffer. This preserves the historical behavior for\n    // sources that cannot produce a stable key (e.g., missing file paths).\n    if (parsedOptions.etag && !etag) {\n      etag = `\"${createHash(\"sha256\").update(processedImage).digest(\"hex\")}\"`;\n      if (req.headers[\"if-none-match\"] === etag) {\n        res.status(304).end();\n        safeOnComplete(onComplete, {\n          src: observedSrc,\n          userId: observedUserId,\n          format: outputFormat,\n          outputBytes: 0,\n          cached: true,\n          durationMs: elapsedMs(startedAt),\n        });\n        return;\n      }\n    }\n\n    const { asciiFilename, encodedFilename } = buildFilename(\n      userData.src,\n      outputFormat,\n    );\n\n    res.type(mimeTypes[outputFormat]);\n    res.setHeader(\n      \"Content-Disposition\",\n      `inline; filename=\"${asciiFilename}\"; filename*=UTF-8''${encodedFilename}`,\n    );\n    res.setHeader(\"Vary\", \"Accept-Encoding\");\n    res.setHeader(\n      \"Cache-Control\",\n      parsedOptions.cacheControl ??\n        \"public, max-age=86400, stale-while-revalidate=604800\",\n    );\n    if (etag) {\n      res.setHeader(\"ETag\", etag);\n    }\n    res.setHeader(\"Content-Length\", processedImage.length.toString());\n    res.send(processedImage);\n    safeOnComplete(onComplete, {\n      src: observedSrc,\n      userId: observedUserId,\n      format: outputFormat,\n      outputBytes: processedImage.length,\n      cached: false,\n      durationMs: elapsedMs(startedAt),\n    });\n  } catch {\n    // If the success path already started flushing the response (e.g., a\n    // future streaming refactor calls `res.write` before `res.send`), we\n    // cannot recover into a fresh fallback without tripping\n    // ERR_HTTP_HEADERS_SENT. Surface to the Express error handler instead so\n    // the connection is torn down cleanly. Today the happy path only flushes\n    // via `res.send` at the very end, so this guard is defence-in-depth.\n    if (res.headersSent) {\n      const flushedError = new Error(\"response already flushed\");\n      reportError(onError, flushedError, {\n        phase: \"fs\",\n        src: observedSrc,\n        userId: observedUserId,\n      });\n      next(flushedError);\n      return;\n    }\n    try {\n      const fallbackType = requestedType === \"avatar\" ? \"avatar\" : \"normal\";\n      const fallback = await FALLBACKIMAGES[fallbackType]();\n      res.type(mimeTypes.jpeg);\n      res.setHeader(\"Content-Disposition\", `inline; filename=\"fallback.jpeg\"`);\n      res.setHeader(\"Vary\", \"Accept-Encoding\");\n      res.setHeader(\"Cache-Control\", \"public, max-age=60\");\n      res.send(fallback);\n    } catch (fallbackError) {\n      reportError(onError, fallbackError, {\n        phase: \"fs\",\n        src: observedSrc,\n        userId: observedUserId,\n      });\n      next(fallbackError);\n    }\n  }\n};\n\n/**\n * @function registerServe\n * @description A function to register the serveImage function as middleware for Express.\n * @param {PixelServeOptions} options - The options object for image processing.\n * @returns {function(Request, Response, NextFunction): Promise<void>} The middleware function.\n *\n * The factory eagerly validates `options` via `optionsSchema.parse` exactly\n * **once** at registration time (Task 4) so the request hot path does not\n * re-run the Zod schema, refine() callbacks, regex matches, or the\n * `allowedNetworkList` trim/lowercase transform on every request. Operator\n * misconfiguration is surfaced synchronously: the eagerly-captured\n * `options.onError` hook (if any) receives `{ phase: \"schema\" }` and the\n * factory re-throws so the failure is loud at startup rather than silent\n * fallback noise per-request.\n *\n * The factory also eagerly resolves `options.getUserFolderRootDir` via\n * `fs.realpath` once and caches the result. Every subsequent request reuses\n * the cached realpath inside `isInsideRoot`, so the per-request containment\n * check costs zero extra filesystem syscalls on the root side. When the\n * configured root does not yet exist on disk, the factory falls back to a\n * lexical `path.resolve` so the containment check still works for lazily-\n * created trees.\n */\nconst registerServe = (\n  options: PixelServeOptions,\n): ((req: Request, res: Response, next: NextFunction) => Promise<void>) => {\n  // Validate options exactly once at factory time. On failure, fire the\n  // eagerly-captured onError hook with `phase: \"schema\"` so operators that\n  // wired up observability still see the misconfiguration, then re-throw so\n  // the deployment fails loudly at startup rather than serving fallback\n  // images forever.\n  let parsedOptions: ParsedOptions;\n  try {\n    parsedOptions = renderOptions(options);\n  } catch (err) {\n    reportError(options.onError, err, { phase: \"schema\" });\n    throw err;\n  }\n\n  // Cached real path of the configured containment root. Populated lazily on\n  // the first request that needs it so the factory itself stays synchronous\n  // (no awaits at module-load time) and the cost is paid exactly once.\n  let cachedRealRoot: string | undefined;\n  let cacheResolved = false;\n  let pendingResolution: Promise<string> | undefined;\n\n  const ensureCachedRealRoot = async (rootDir: string): Promise<string> => {\n    if (cacheResolved && cachedRealRoot !== undefined) return cachedRealRoot;\n    // Coalesce concurrent first-request resolutions so a burst of N requests\n    // produces exactly one realpath syscall rather than N.\n    if (!pendingResolution) {\n      pendingResolution = resolveRootDir(rootDir).then((resolved) => {\n        cachedRealRoot = resolved;\n        cacheResolved = true;\n        return resolved;\n      });\n    }\n    return pendingResolution;\n  };\n\n  return async (\n    req: Request,\n    res: Response,\n    next: NextFunction,\n  ): Promise<void> => {\n    let rootForRequest: string | undefined;\n    if (parsedOptions.getUserFolderRootDir) {\n      rootForRequest = await ensureCachedRealRoot(\n        parsedOptions.getUserFolderRootDir,\n      );\n    }\n    return serveImage(req, res, next, parsedOptions, rootForRequest);\n  };\n};\n\nexport default registerServe;\n"]}