{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * hppx — Superior HTTP Parameter Pollution protection middleware\n *\n * - Protects against parameter and prototype pollution\n * - Supports nested whitelists via dot-notation and leaf matching\n * - Merge strategies: keepFirst | keepLast | combine\n * - Multiple middleware compatibility: arrays are \"put aside\" once and selectively restored\n * - Exposes req.queryPolluted / req.bodyPolluted / req.paramsPolluted\n * - TypeScript-first API\n */\n\n// Augment the Express Request interface with hppx-specific properties so that\n// consumers importing this package automatically get typed access to the\n// polluted-tree fields. The `declare module` block lives here (rather than in\n// a separate `.d.ts` file) so that tsup emits it into both `dist/index.d.ts`\n// and `dist/index.d.cts`, which are kept in symbol-parity by\n// `scripts/check-dts-parity.mjs`.\n//\n// The `import type` below is required for TypeScript 6.0+ DTS rollup, which no\n// longer resolves augmentation targets through transitive `@types` packages.\n// It pulls the augmentation module into scope without affecting the JS output.\nimport type {} from \"express-serve-static-core\";\n\ndeclare module \"express-serve-static-core\" {\n  interface Request {\n    queryPolluted?: Record<string, unknown>;\n    bodyPolluted?: Record<string, unknown>;\n    paramsPolluted?: Record<string, unknown>;\n  }\n}\n\nexport type RequestSource = \"query\" | \"body\" | \"params\";\nexport type MergeStrategy = \"keepFirst\" | \"keepLast\" | \"combine\";\n\nexport interface SanitizeOptions {\n  whitelist?: string[] | string;\n  mergeStrategy?: MergeStrategy;\n  maxDepth?: number;\n  maxKeys?: number;\n  maxArrayLength?: number;\n  maxKeyLength?: number;\n  trimValues?: boolean;\n  preserveNull?: boolean;\n}\n\nexport interface HppxOptions extends SanitizeOptions {\n  sources?: RequestSource[];\n  /** When to process req.body */\n  checkBodyContentType?: \"urlencoded\" | \"any\" | \"none\";\n  excludePaths?: string[];\n  strict?: boolean;\n  onPollutionDetected?: (\n    req: Record<string, unknown>,\n    info: { source: RequestSource; pollutedKeys: string[] },\n  ) => void;\n  logger?: (err: Error | unknown) => void;\n  /** Enable logging when pollution is detected (default: true) */\n  logPollution?: boolean;\n}\n\nexport interface SanitizedResult<T> {\n  cleaned: T;\n  pollutedTree: Record<string, unknown>;\n  pollutedKeys: string[];\n}\n\nconst DEFAULT_SOURCES: RequestSource[] = [\"query\", \"body\", \"params\"];\nconst DEFAULT_STRATEGY: MergeStrategy = \"keepLast\";\nconst DANGEROUS_KEYS = new Set([\"__proto__\", \"prototype\", \"constructor\"]);\n\n// Pre-compiled, ReDoS-safe character class that rejects keys containing any of:\n//   - ASCII C0 controls (U+0000..U+001F) and DEL (U+007F)\n//   - C1 controls (U+0080..U+009F)\n//   - Unicode bidirectional control characters: U+200E/U+200F (LRM/RLM),\n//     U+202A..U+202E (LRE/RLE/PDF/LRO/RLO), U+2066..U+2069 (LRI/RLI/FSI/PDI)\n//   - U+FEFF (BOM / zero-width no-break space) — typically accidental in keys\n//     and used to bypass naive key-based logic.\n// Rejecting these prevents bypass of downstream key-based logic, log injection,\n// and DB/log corruption when output is not properly quoted. The character\n// class contains no quantifiers, so it cannot trigger ReDoS.\n/* eslint-disable no-control-regex -- intentional: this regex must include control characters in order to reject them */\nconst FORBIDDEN_KEY_CHARS =\n  /[\\u0000-\\u001F\\u007F-\\u009F\\u200E\\u200F\\u202A-\\u202E\\u2066-\\u2069\\uFEFF]/;\n/* eslint-enable no-control-regex */\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n  if (value === null || typeof value !== \"object\") return false;\n  const proto = Object.getPrototypeOf(value);\n  return proto === Object.prototype || proto === null;\n}\n\nfunction sanitizeKey(key: string, maxKeyLength?: number): string | null {\n  /* istanbul ignore next */ if (typeof key !== \"string\") return null;\n  if (DANGEROUS_KEYS.has(key)) return null;\n  // Reject keys containing ASCII/Unicode control characters or bidirectional\n  // override characters. Subsumes the previous explicit NUL-byte check.\n  if (FORBIDDEN_KEY_CHARS.test(key)) return null;\n  // Prevent excessively long keys that could cause DoS\n  /* istanbul ignore next -- defensive: callers always pass maxKeyLength explicitly */\n  const maxLen = maxKeyLength ?? 200;\n  if (key.length > maxLen) return null;\n  // Prevent keys that are only dots or brackets (malformed) - but allow single dot as it's valid\n  if (key.length > 1 && /^[.\\[\\]]+$/.test(key)) return null;\n  return key;\n}\n\n// Cache for parsed path segments to improve performance.\n// Uses a clear-on-full eviction policy: when the cache reaches capacity, it is\n// cleared in its entirety so future entries can still be cached. This prevents\n// a one-shot poisoning attack where an attacker pumps unique keys into a single\n// request to permanently disable the cache for legitimate traffic.\nconst PATH_SEGMENT_CACHE_LIMIT = 500;\nconst pathSegmentCache = new Map<string, string[]>();\n\n/**\n * Parse a (possibly composite) key into an array of path segments.\n *\n * Accepted forms (and how they're parsed):\n *   - Dotted:           \"a.b.c\"        -> [\"a\", \"b\", \"c\"]\n *   - Bracket:          \"a[b][c]\"      -> [\"a\", \"b\", \"c\"]\n *   - Mixed:            \"a.b[c].d\"     -> [\"a\", \"b\", \"c\", \"d\"]\n *   - Numeric brackets: \"a[0][1]\"      -> [\"a\", \"0\", \"1\"]   (indexes become string segments)\n *   - Trailing/empty:   \"a..b\" / \"a[]\" -> [\"a\", \"b\"] / [\"a\"] (empty segments dropped)\n *\n * **The parser is intentionally lenient.** Malformed inputs like `a]]b[[c`\n * collapse to `[\"a\", \"b\", \"c\"]` rather than being rejected. Justification:\n *\n *   1. Every key reaching this function has already passed `sanitizeKey`,\n *      which rejects truly hostile inputs — dangerous keys (`__proto__`,\n *      `prototype`, `constructor`), control characters, bidirectional\n *      override characters, dot/bracket-only patterns, and overly long\n *      strings. The remaining surface is benign syntactic noise.\n *\n *   2. Lenient parsing is acceptable defense-in-depth: even if an attacker\n *      crafts unusual bracket placement, the parsed segments are still\n *      passed through `sanitizeKey` again at every level by `setIn`, which\n *      blocks dangerous keys at the segment level too.\n *\n *   3. Strict grammar enforcement here would be a behavioral change that\n *      could break legitimate users with unusual key shapes, while\n *      providing little additional security beyond what `sanitizeKey`\n *      already enforces.\n *\n * The regexes are character-class only (no quantifiers), so this function is\n * ReDoS-safe.\n */\nfunction parsePathSegments(key: string): string[] {\n  // Check cache first\n  const cached = pathSegmentCache.get(key);\n  if (cached) return cached;\n\n  // Convert bracket notation to dots, then split\n  // a[b][c] -> a.b.c\n  const dotted = key.replace(/\\]/g, \"\").replace(/\\[/g, \".\");\n  const result = dotted.split(\".\").filter((s) => s.length > 0);\n\n  // Clear-on-full eviction: prevents permanent cache disablement after a flood\n  // of unique keys, while keeping the implementation O(1) on insert.\n  if (pathSegmentCache.size >= PATH_SEGMENT_CACHE_LIMIT) {\n    pathSegmentCache.clear();\n  }\n  pathSegmentCache.set(key, result);\n\n  return result;\n}\n\n/**\n * @internal — test-only helper that resets the module-level path segment cache.\n * Public callers must not depend on this; it exists solely so tests can verify\n * cache eviction behavior without exposing the internal Map.\n */\nexport function __resetPathSegmentCache(): void {\n  pathSegmentCache.clear();\n}\n\nfunction expandObjectPaths(\n  obj: Record<string, unknown>,\n  maxKeyLength?: number,\n  maxDepth = 20,\n  currentDepth = 0,\n  seen?: WeakSet<object>,\n): Record<string, unknown> {\n  if (currentDepth > maxDepth) {\n    throw new Error(`Maximum object depth (${maxDepth}) exceeded`);\n  }\n  // Path-stack cycle detection: only detect references currently on the recursion\n  // stack (true cycles), not previously-visited-but-fully-emitted nodes (shared\n  // acyclic subtrees, which must be fully cloned at each occurrence).\n  const seenSet = seen ?? new WeakSet<object>();\n  if (seenSet.has(obj)) return {};\n  seenSet.add(obj);\n  try {\n    const result: Record<string, unknown> = {};\n    for (const rawKey of Object.keys(obj)) {\n      const safeKey = sanitizeKey(rawKey, maxKeyLength);\n      if (!safeKey) continue;\n      const value = obj[rawKey];\n\n      // Recursively expand nested objects first\n      const expandedValue = isPlainObject(value)\n        ? expandObjectPaths(\n            value as Record<string, unknown>,\n            maxKeyLength,\n            maxDepth,\n            currentDepth + 1,\n            seenSet,\n          )\n        : value;\n\n      if (safeKey.includes(\".\") || safeKey.includes(\"[\")) {\n        const segments = parsePathSegments(safeKey);\n        if (segments.length > 0) {\n          setIn(result, segments, expandedValue);\n          continue;\n        }\n      }\n      result[safeKey] = expandedValue;\n    }\n    return result;\n  } finally {\n    seenSet.delete(obj);\n  }\n}\n\n/**\n * Attempts to set a property on a request-like object.\n *\n * Returns `true` if the property is observable as the requested value after the call,\n * `false` if every attempt failed.\n *\n * Strategy:\n * 1. If no own descriptor exists, or the descriptor is configurable, redefine via\n *    `Object.defineProperty` so the assigned value shadows any prototype-level getter\n *    (e.g. Express 5's lazy `req.query` getter on `IncomingMessage.prototype`).\n * 2. If the descriptor is non-configurable BUT writable, fall through to direct\n *    assignment.\n * 3. If the descriptor is non-configurable AND non-writable, attempt direct assignment\n *    in a `try/catch` (in strict mode it will throw); on failure, surface a warning via\n *    the supplied logger so the user knows the request continues to expose the original\n *    (potentially polluted) value.\n */\nfunction setReqPropertySafe(\n  target: Record<string, unknown>,\n  key: string,\n  value: unknown,\n  onFailure?: (message: string) => void,\n  enumerable = true,\n): boolean {\n  try {\n    const desc = Object.getOwnPropertyDescriptor(target, key);\n    if (!desc || desc.configurable !== false) {\n      Object.defineProperty(target, key, {\n        value,\n        writable: true,\n        configurable: true,\n        enumerable,\n      });\n      return true;\n    }\n    // desc is non-configurable; defineProperty cannot be used.\n    if (desc.writable) {\n      target[key] = value;\n      return true;\n    }\n    // Non-configurable + non-writable: attempt direct assignment which will throw in\n    // strict mode. Do NOT silently skip — surface the failure.\n    try {\n      target[key] = value;\n      /* istanbul ignore next -- sloppy-mode read-back unreachable here:\n         this module is ESM (implicitly strict mode), so the assignment above\n         throws on any non-writable target. The read-back guards against the\n         sloppy-mode case where the assignment silently no-ops. */\n      if (target[key] === value) return true;\n    } catch (_assignErr) {\n      // fall through to the warning below\n    }\n    if (onFailure) {\n      onFailure(\n        `[hppx] Could not write sanitized value to req.${key}: property is non-configurable and non-writable. The original (potentially polluted) value remains on req.${key}.`,\n      );\n    }\n    return false;\n  } catch (_definePropErr) {\n    // defineProperty itself threw — try plain assignment as a last resort.\n    try {\n      target[key] = value;\n      if (target[key] === value) return true;\n    } catch (_assignErr) {\n      // fall through\n    }\n    /* istanbul ignore next -- defensive fallback for an extreme edge case:\n       reaching here requires defineProperty to throw AND the subsequent\n       direct assignment to either throw or silently no-op AND an onFailure\n       callback to be configured. Exercised indirectly via the assignment\n       fallback test (which patches defineProperty to throw). */\n    {\n      if (onFailure) {\n        onFailure(`[hppx] Could not write sanitized value to req.${key}: defineProperty failed.`);\n      }\n      return false;\n    }\n  }\n}\n\nfunction safeDeepClone<T>(\n  input: T,\n  maxKeyLength?: number,\n  maxArrayLength?: number,\n  maxDepth = 20,\n  currentDepth = 0,\n  seen?: WeakSet<object>,\n): T {\n  // Path-stack cycle detection: a node is added to `seen` on entry and removed\n  // on exit (via `finally`). This correctly distinguishes true cycles\n  // (currently on the recursion stack) from shared acyclic subtrees (already\n  // emitted but no longer on the stack — must be cloned independently).\n  if (Array.isArray(input)) {\n    if (currentDepth > maxDepth) {\n      throw new Error(`Maximum object depth (${maxDepth}) exceeded`);\n    }\n    const seenSet = seen ?? new WeakSet<object>();\n    if (seenSet.has(input)) return [] as T;\n    seenSet.add(input);\n    try {\n      // Limit array length to prevent memory exhaustion\n      const limit = maxArrayLength ?? 1000;\n      const limited = input.slice(0, limit);\n      return limited.map((v) =>\n        safeDeepClone(v, maxKeyLength, maxArrayLength, maxDepth, currentDepth + 1, seenSet),\n      ) as T;\n    } finally {\n      seenSet.delete(input);\n    }\n  }\n  if (isPlainObject(input)) {\n    if (currentDepth > maxDepth) {\n      throw new Error(`Maximum object depth (${maxDepth}) exceeded`);\n    }\n    const seenSet = seen ?? new WeakSet<object>();\n    if (seenSet.has(input as object)) return {} as T;\n    seenSet.add(input as object);\n    try {\n      const out: Record<string, unknown> = {};\n      for (const k of Object.keys(input)) {\n        if (!sanitizeKey(k, maxKeyLength)) continue;\n        out[k] = safeDeepClone(\n          (input as Record<string, unknown>)[k],\n          maxKeyLength,\n          maxArrayLength,\n          maxDepth,\n          currentDepth + 1,\n          seenSet,\n        );\n      }\n      return out as T;\n    } finally {\n      seenSet.delete(input as object);\n    }\n  }\n  return input;\n}\n\nfunction mergeValues(values: unknown[], strategy: MergeStrategy): unknown {\n  switch (strategy) {\n    case \"keepFirst\":\n      return values[0];\n    case \"keepLast\":\n      return values[values.length - 1];\n    case \"combine\": {\n      const out: unknown[] = [];\n      for (const v of values) {\n        if (Array.isArray(v)) {\n          for (const el of v) out.push(el);\n        } else {\n          out.push(v);\n        }\n      }\n      return out;\n    }\n    /* istanbul ignore next -- exhaustiveness check unreachable from outside:\n       validateSanitizeOptions rejects every non-listed strategy at construction\n       time, so the only way to reach this branch is a programmer error (a new\n       MergeStrategy union member added without updating this switch). Failing\n       loudly here is preferable to a silent fallback. */\n    default: {\n      const _exhaustive: never = strategy;\n      throw new Error(`Unknown mergeStrategy: ${_exhaustive as string}`);\n    }\n  }\n}\n\nfunction isUrlEncodedContentType(req: any): boolean {\n  const ct = String(req?.headers?.[\"content-type\"] || \"\").toLowerCase();\n  return ct.startsWith(\"application/x-www-form-urlencoded\");\n}\n\nfunction shouldExcludePath(path: string | undefined, excludePaths: string[]): boolean {\n  if (!path || excludePaths.length === 0) return false;\n  const currentPath = path;\n  for (const p of excludePaths) {\n    if (p.endsWith(\"*\")) {\n      if (currentPath.startsWith(p.slice(0, -1))) return true;\n    } else if (currentPath === p) {\n      return true;\n    }\n  }\n  return false;\n}\n\nfunction normalizeWhitelist(whitelist?: string[] | string): string[] {\n  if (!whitelist) return [];\n  if (typeof whitelist === \"string\") return [whitelist];\n  return whitelist.filter((w) => typeof w === \"string\");\n}\n\nconst WHITELIST_PATH_CACHE_LIMIT = 1000;\n\nfunction buildWhitelistHelpers(whitelist: string[]) {\n  const exact = new Set(whitelist);\n  const prefixes = whitelist.filter((w) => w.length > 0);\n  // Pre-build a cache for commonly checked paths for performance. Uses a\n  // clear-on-full eviction policy (matching `pathSegmentCache`) to prevent a\n  // poisoning attack where an attacker pumps unique paths to permanently\n  // disable caching for legitimate paths.\n  const pathCache = new Map<string, boolean>();\n\n  return {\n    exact,\n    prefixes,\n    isWhitelistedPath(pathParts: string[]): boolean {\n      /* istanbul ignore if -- defensive: always called with non-empty path from walk() */\n      if (pathParts.length === 0) return false;\n      const full = pathParts.join(\".\");\n\n      // Check cache first for performance\n      const cached = pathCache.get(full);\n      if (cached !== undefined) return cached;\n\n      let result = false;\n\n      // Exact match\n      if (exact.has(full)) {\n        result = true;\n      }\n      // Leaf match\n      else if (exact.has(pathParts[pathParts.length - 1]!)) {\n        result = true;\n      }\n      // Prefix match (treat any listed segment as prefix of a subtree)\n      else {\n        for (const p of prefixes) {\n          if (full === p || full.startsWith(p + \".\")) {\n            result = true;\n            break;\n          }\n        }\n      }\n\n      // Clear-on-full eviction: prevents permanent cache disablement after a\n      // flood of unique paths, while keeping the implementation O(1) on insert.\n      if (pathCache.size >= WHITELIST_PATH_CACHE_LIMIT) {\n        pathCache.clear();\n      }\n      pathCache.set(full, result);\n\n      return result;\n    },\n  };\n}\n\nfunction setIn(target: Record<string, unknown>, path: string[], value: unknown): void {\n  /* istanbul ignore if */\n  if (path.length === 0) {\n    return;\n  }\n  let cur: Record<string, unknown> = target;\n  for (let i = 0; i < path.length - 1; i++) {\n    const k = path[i]!;\n    // Additional prototype pollution protection\n    if (DANGEROUS_KEYS.has(k)) return;\n    if (!isPlainObject(cur[k])) {\n      // Create a new plain object to avoid pollution\n      cur[k] = {};\n    }\n    cur = cur[k] as Record<string, unknown>;\n  }\n  const lastKey = path[path.length - 1]!;\n  // Final check on the last key\n  if (DANGEROUS_KEYS.has(lastKey)) return;\n  cur[lastKey] = value;\n}\n\nfunction moveWhitelistedFromPolluted(\n  reqPart: Record<string, unknown>,\n  polluted: Record<string, unknown>,\n  isWhitelisted: (path: string[]) => boolean,\n): void {\n  function walk(node: Record<string, unknown>, path: string[] = []) {\n    for (const k of Object.keys(node)) {\n      const v = node[k];\n      const curPath = [...path, k];\n      if (isPlainObject(v)) {\n        walk(v as Record<string, unknown>, curPath);\n        // prune empty objects\n        if (Object.keys(v as Record<string, unknown>).length === 0) {\n          delete node[k];\n        }\n      } else {\n        if (isWhitelisted(curPath)) {\n          // put back into request\n          /* istanbul ignore next -- defensive: polluted tree keys never contain dots after expansion */\n          const normalizedPath = curPath.flatMap((seg) =>\n            seg.includes(\".\") ? seg.split(\".\") : [seg],\n          );\n          setIn(reqPart, normalizedPath, v);\n          delete node[k];\n        }\n      }\n    }\n  }\n  walk(polluted);\n}\n\nfunction detectAndReduce(\n  input: Record<string, unknown>,\n  opts: Required<\n    Pick<\n      SanitizeOptions,\n      | \"mergeStrategy\"\n      | \"maxDepth\"\n      | \"maxKeys\"\n      | \"maxArrayLength\"\n      | \"maxKeyLength\"\n      | \"trimValues\"\n      | \"preserveNull\"\n    >\n  >,\n): SanitizedResult<Record<string, unknown>> {\n  let keyCount = 0;\n  const polluted: Record<string, unknown> = {};\n  // Use a Set for de-duplication. Multiple arrays can land at the same dotted\n  // path (e.g. `[{tags:[...]}, {tags:[...]}]` produces two array sites at\n  // `items.tags`); the user-facing `pollutedKeys` list should report each\n  // affected leaf path exactly once, not once per occurrence. The `inArray`\n  // flag below also prevents nested array-in-array recursion from recording\n  // duplicate entries at the same path (e.g. `{a: [[1,2],[3,4]]}`).\n  const pollutedKeysSet = new Set<string>();\n\n  // === Detachment invariant ===\n  //\n  // The single upfront `safeDeepClone` enforces maxKeyLength / maxArrayLength /\n  // maxDepth and produces `cloned`, a tree fully detached from `input`:\n  //\n  //   1. No reference inside `cloned` aliases back into the caller-owned input\n  //      tree. This holds because safeDeepClone walks plain objects and arrays\n  //      recursively and only re-emits primitives / freshly-created containers.\n  //   2. Cycles in `input` are broken by safeDeepClone's path-stack `WeakSet`\n  //      (a node currently on the recursion stack is replaced with `{}` / `[]`\n  //      on its second visit), so `cloned` is acyclic.\n  //\n  // processNode walks `cloned` (NOT `input`) and:\n  //   - records `node` (a reference INTO `cloned`) into the polluted tree,\n  //     which is safe precisely because (1) guarantees no caller-owned data\n  //     is exposed via the polluted tree, and\n  //   - rebuilds the cleaned output as a fresh structure (objects via the\n  //     `out` literal in this function; arrays via `Array.prototype.map`).\n  //\n  // No nested safeDeepClone is performed here — it would be redundant work\n  // and, if invoked with a fresh WeakSet, could re-introduce traversal of\n  // cycles that the upfront clone already broke.\n  const cloned = safeDeepClone(input, opts.maxKeyLength, opts.maxArrayLength, opts.maxDepth);\n\n  function processNode(node: unknown, path: string[] = [], depth = 0, inArray = false): unknown {\n    if (node === null) return opts.preserveNull ? null : undefined;\n    if (node === undefined) return node;\n\n    if (Array.isArray(node)) {\n      // Array is already truncated to maxArrayLength by the upfront clone, so\n      // we can use it directly without re-slicing.\n      //\n      // Pollution is recorded only at the OUTERMOST array site for a given\n      // `path`. When `inArray` is true, we are recursing into elements of an\n      // already-recorded outer array (e.g. `{a: [[1,2],[3,4]]}` reaches the\n      // inner arrays at the same `path` as the outer one); in that case we\n      // skip the redundant `setIn` / `pollutedKeys.push` so consumers of\n      // `pollutedKeys` and `pollutedTree` see one entry per affected leaf.\n      const mapped = node.map((v) => processNode(v, path, depth, true));\n      if (!inArray) {\n        // Record pollution for ALL strategies (including combine). The combined\n        // output remains the cleaned value, but the security signal — polluted\n        // tree, pollutedKeys, onPollutionDetected callback — must still fire so\n        // consumers of those signals are not silently bypassed in combine mode.\n        //\n        // Per the detachment invariant above, `node` is part of `cloned` (NOT\n        // `input`); storing it in the polluted tree therefore cannot leak any\n        // caller-owned reference, and the polluted tree is itself acyclic\n        // because `cloned` is acyclic.\n        setIn(polluted, path, node);\n        pollutedKeysSet.add(path.join(\".\"));\n      }\n      return mergeValues(mapped, opts.mergeStrategy);\n    }\n\n    if (isPlainObject(node)) {\n      /* istanbul ignore if -- defensive: safeDeepClone enforces the same depth limit first */\n      if (depth > opts.maxDepth)\n        throw new Error(`Maximum object depth (${opts.maxDepth}) exceeded`);\n      const out: Record<string, unknown> = {};\n      for (const rawKey of Object.keys(node)) {\n        keyCount++;\n        /* istanbul ignore if -- defensive: opts.maxKeys is always provided by callers */\n        if (keyCount > (opts.maxKeys ?? Number.MAX_SAFE_INTEGER)) {\n          throw new Error(`Maximum key count (${opts.maxKeys}) exceeded`);\n        }\n        const safeKey = sanitizeKey(rawKey, opts.maxKeyLength);\n        /* istanbul ignore if -- defensive: keys already filtered by expandObjectPaths + safeDeepClone */\n        if (!safeKey) continue;\n        const child = (node as Record<string, unknown>)[rawKey];\n        const childPath = path.concat([safeKey]);\n        // Walking into an object key resets `inArray` — each key starts a fresh\n        // path under which a new array site can record pollution exactly once.\n        let value = processNode(child, childPath, depth + 1, false);\n        if (typeof value === \"string\" && opts.trimValues) value = value.trim();\n        out[safeKey] = value;\n      }\n      return out;\n    }\n\n    return node;\n  }\n\n  const cleaned = processNode(cloned, [], 0, false) as Record<string, unknown>;\n  return { cleaned, pollutedTree: polluted, pollutedKeys: Array.from(pollutedKeysSet) };\n}\n\nexport function sanitize<T extends Record<string, unknown>>(\n  input: T,\n  options: SanitizeOptions = {},\n): T {\n  validateSanitizeOptions(options);\n  // Normalize and expand keys prior to sanitization\n  const maxKeyLength = options.maxKeyLength ?? 200;\n  const maxDepthVal = options.maxDepth ?? 20;\n  const expandedInput = isPlainObject(input)\n    ? expandObjectPaths(input, maxKeyLength, maxDepthVal)\n    : input;\n  const whitelist = normalizeWhitelist(options.whitelist);\n  const { isWhitelistedPath } = buildWhitelistHelpers(whitelist);\n  const {\n    mergeStrategy = DEFAULT_STRATEGY,\n    maxDepth = 20,\n    maxKeys = 5000,\n    maxArrayLength = 1000,\n    trimValues = false,\n    preserveNull = true,\n  } = options;\n\n  // First: reduce arrays and collect polluted\n  const { cleaned, pollutedTree } = detectAndReduce(expandedInput, {\n    mergeStrategy,\n    maxDepth,\n    maxKeys,\n    maxArrayLength,\n    maxKeyLength,\n    trimValues,\n    preserveNull,\n  });\n\n  // Second: move back whitelisted arrays\n  moveWhitelistedFromPolluted(cleaned, pollutedTree, isWhitelistedPath);\n\n  return cleaned as T;\n}\n\ntype ExpressLikeNext = (err?: unknown) => void;\n\nfunction validateSanitizeOptions(options: SanitizeOptions): void {\n  if (\n    options.maxDepth !== undefined &&\n    (typeof options.maxDepth !== \"number\" || options.maxDepth < 1 || options.maxDepth > 100)\n  ) {\n    throw new TypeError(\"maxDepth must be a number between 1 and 100\");\n  }\n  if (\n    options.maxKeys !== undefined &&\n    (typeof options.maxKeys !== \"number\" || options.maxKeys < 1)\n  ) {\n    throw new TypeError(\"maxKeys must be a positive number\");\n  }\n  if (\n    options.maxArrayLength !== undefined &&\n    (typeof options.maxArrayLength !== \"number\" || options.maxArrayLength < 1)\n  ) {\n    throw new TypeError(\"maxArrayLength must be a positive number\");\n  }\n  if (\n    options.maxKeyLength !== undefined &&\n    (typeof options.maxKeyLength !== \"number\" ||\n      options.maxKeyLength < 1 ||\n      options.maxKeyLength > 1000)\n  ) {\n    throw new TypeError(\"maxKeyLength must be a number between 1 and 1000\");\n  }\n  if (\n    options.mergeStrategy !== undefined &&\n    ![\"keepFirst\", \"keepLast\", \"combine\"].includes(options.mergeStrategy)\n  ) {\n    throw new TypeError(\"mergeStrategy must be 'keepFirst', 'keepLast', or 'combine'\");\n  }\n  if (options.trimValues !== undefined && typeof options.trimValues !== \"boolean\") {\n    throw new TypeError(\"trimValues must be a boolean\");\n  }\n  if (options.preserveNull !== undefined && typeof options.preserveNull !== \"boolean\") {\n    throw new TypeError(\"preserveNull must be a boolean\");\n  }\n  if (options.whitelist !== undefined) {\n    if (typeof options.whitelist !== \"string\" && !Array.isArray(options.whitelist)) {\n      throw new TypeError(\"whitelist must be a string or an array of strings\");\n    }\n    if (Array.isArray(options.whitelist)) {\n      for (const entry of options.whitelist) {\n        if (typeof entry !== \"string\") {\n          throw new TypeError(\"whitelist must be a string or an array of strings\");\n        }\n      }\n    }\n  }\n}\n\nfunction validateOptions(options: HppxOptions): void {\n  validateSanitizeOptions(options);\n  if (options.sources !== undefined && !Array.isArray(options.sources)) {\n    throw new TypeError(\"sources must be an array\");\n  }\n  if (options.sources !== undefined) {\n    if (options.sources.length === 0) {\n      throw new TypeError(\"sources must contain at least one of 'query', 'body', 'params'\");\n    }\n    for (const source of options.sources) {\n      if (![\"query\", \"body\", \"params\"].includes(source)) {\n        throw new TypeError(\"sources must only contain 'query', 'body', or 'params'\");\n      }\n    }\n  }\n  if (\n    options.checkBodyContentType !== undefined &&\n    ![\"urlencoded\", \"any\", \"none\"].includes(options.checkBodyContentType)\n  ) {\n    throw new TypeError(\"checkBodyContentType must be 'urlencoded', 'any', or 'none'\");\n  }\n  if (options.excludePaths !== undefined) {\n    if (!Array.isArray(options.excludePaths)) {\n      throw new TypeError(\"excludePaths must be an array\");\n    }\n    for (const entry of options.excludePaths) {\n      if (typeof entry !== \"string\") {\n        throw new TypeError(\"excludePaths must contain only strings\");\n      }\n    }\n  }\n  if (options.logger !== undefined && typeof options.logger !== \"function\") {\n    throw new TypeError(\"logger must be a function\");\n  }\n  if (\n    options.onPollutionDetected !== undefined &&\n    typeof options.onPollutionDetected !== \"function\"\n  ) {\n    throw new TypeError(\"onPollutionDetected must be a function\");\n  }\n  if (options.strict !== undefined && typeof options.strict !== \"boolean\") {\n    throw new TypeError(\"strict must be a boolean\");\n  }\n  if (options.logPollution !== undefined && typeof options.logPollution !== \"boolean\") {\n    throw new TypeError(\"logPollution must be a boolean\");\n  }\n}\n\nexport default function hppx(options: HppxOptions = {}) {\n  // Validate options on middleware creation\n  validateOptions(options);\n\n  const {\n    whitelist = [],\n    mergeStrategy = DEFAULT_STRATEGY,\n    sources = DEFAULT_SOURCES,\n    checkBodyContentType = \"urlencoded\",\n    excludePaths = [],\n    maxDepth = 20,\n    maxKeys = 5000,\n    maxArrayLength = 1000,\n    maxKeyLength = 200,\n    trimValues = false,\n    preserveNull = true,\n    strict = false,\n    onPollutionDetected,\n    logger,\n    logPollution = true,\n  } = options;\n\n  const whitelistArr = normalizeWhitelist(whitelist);\n  const { isWhitelistedPath } = buildWhitelistHelpers(whitelistArr);\n\n  return function hppxMiddleware(req: any, res: any, next: ExpressLikeNext) {\n    try {\n      // Read req.path defensively. Some upstream middleware decorates `req`\n      // with a `path` getter that throws under specific conditions; if so,\n      // that error must NOT propagate as a 500 — exclusion lookup is best\n      // effort. On failure, treat the path as unknown (no exclusion match,\n      // proceed to process the request normally) and surface a warning via\n      // the configured logger so the upstream bug stays visible.\n      let pathForExclusion: string | undefined;\n      try {\n        pathForExclusion = req?.path;\n      } catch (pathErr) {\n        const message = `[hppx] Failed to read req.path during exclusion check; proceeding without path-based exclusion. Underlying error: ${\n          pathErr instanceof Error ? pathErr.message : String(pathErr)\n        }`;\n        if (logger) {\n          try {\n            logger(message);\n          } catch (_) {\n            console.warn(message);\n          }\n        } else {\n          console.warn(message);\n        }\n        pathForExclusion = undefined;\n      }\n      if (shouldExcludePath(pathForExclusion, excludePaths)) return next();\n\n      let anyPollutionDetected = false;\n      const allPollutedKeys: string[] = [];\n\n      // Per-request, per-source warning de-dup: don't spam the logger if writes fail\n      // for both the cleaned source and the polluted-tree property on the same request.\n      const warned = new Set<string>();\n      const warn = (message: string) => {\n        if (warned.has(message)) return;\n        warned.add(message);\n        if (logger) {\n          try {\n            logger(message);\n          } catch (_) {\n            // Logger failed; surface via console as a last-resort signal.\n            console.warn(message);\n          }\n        } else {\n          console.warn(message);\n        }\n      };\n\n      for (const source of sources) {\n        /* istanbul ignore next -- defensive: Express always invokes middleware\n           with a non-null request object; this guard exists only so the loop\n           degrades gracefully if a non-Express harness invokes the middleware\n           with a missing/non-object req. */\n        if (!req || typeof req !== \"object\") break;\n        if (req[source] === undefined) continue;\n\n        if (source === \"body\") {\n          if (checkBodyContentType === \"none\") continue;\n          if (checkBodyContentType === \"urlencoded\" && !isUrlEncodedContentType(req)) continue;\n        }\n\n        const part = req[source];\n        if (!isPlainObject(part)) continue;\n\n        // Preprocess: expand dotted and bracketed keys into nested objects\n        const expandedPart = expandObjectPaths(part, maxKeyLength, maxDepth);\n\n        const pollutedKey = `${source}Polluted`;\n        const processedKey = `__hppxProcessed_${source}`;\n        // Use hasOwnProperty.call to avoid prototype-chain traversal — protects against\n        // upstream prototype pollution gadgets that set `Object.prototype.__hppxProcessed_*`.\n        const hasProcessedBefore = Object.prototype.hasOwnProperty.call(req, processedKey);\n\n        if (!hasProcessedBefore) {\n          // First pass for this request part: reduce arrays and collect polluted\n          const { cleaned, pollutedTree, pollutedKeys } = detectAndReduce(expandedPart, {\n            mergeStrategy,\n            maxDepth,\n            maxKeys,\n            maxArrayLength,\n            maxKeyLength,\n            trimValues,\n            preserveNull,\n          });\n\n          // Express 5 exposes `req.query` only as a getter on the prototype chain (no\n          // own descriptor by default), so the standard defineProperty path inside\n          // setReqPropertySafe shadows it cleanly. If a downstream framework version\n          // ever installs a non-configurable, non-writable descriptor, the helper\n          // surfaces a clear warning instead of failing silently.\n          setReqPropertySafe(req, source, cleaned, warn);\n\n          // Attach polluted object (always present as {} when source processed)\n          // Non-enumerable: keeps attacker-controlled duplicate payload out of\n          // generic req serializations (JSON.stringify, spreads, audit middleware)\n          // while remaining readable by name — consistent with __hppxProcessed_*.\n          setReqPropertySafe(req, pollutedKey, pollutedTree, warn, false);\n          // Mark as processed in a tamper-resistant, non-enumerable way so it is not\n          // visible to user code, response serializers, or attackers.\n          try {\n            Object.defineProperty(req, processedKey, {\n              value: true,\n              writable: false,\n              configurable: false,\n              enumerable: false,\n            });\n          } catch (_) {\n            // If req is frozen or defineProperty otherwise fails, fall back to assignment\n            // so behavior is at least correct for the current request.\n            try {\n              req[processedKey] = true;\n            } catch (_assignErr) {\n              // Last resort: skip; downstream middleware will simply re-process.\n            }\n          }\n\n          // Apply whitelist now: move whitelisted arrays back\n          const sourceData = req[source];\n          const pollutedData = req[pollutedKey];\n          if (isPlainObject(sourceData) && isPlainObject(pollutedData)) {\n            moveWhitelistedFromPolluted(sourceData, pollutedData, isWhitelistedPath);\n          }\n\n          if (pollutedKeys.length > 0) {\n            anyPollutionDetected = true;\n            for (const k of pollutedKeys) allPollutedKeys.push(`${source}.${k}`);\n          }\n        } else {\n          // Subsequent middleware: only put back whitelisted entries\n          const sourceData = req[source];\n          const pollutedData = req[pollutedKey];\n          if (isPlainObject(sourceData) && isPlainObject(pollutedData)) {\n            moveWhitelistedFromPolluted(sourceData, pollutedData, isWhitelistedPath);\n          }\n          // pollution already accounted for in previous pass\n        }\n      }\n\n      if (anyPollutionDetected) {\n        // Log pollution detection if enabled\n        if (logPollution) {\n          const logMessage = `[hppx] HTTP Parameter Pollution detected - ${allPollutedKeys.length} parameter(s) affected: ${allPollutedKeys.join(\", \")}`;\n          if (logger) {\n            try {\n              logger(logMessage);\n            } catch (_) {\n              // Fallback to console.warn if logger fails\n              console.warn(logMessage);\n            }\n          } else {\n            console.warn(logMessage);\n          }\n        }\n\n        if (onPollutionDetected) {\n          try {\n            // Determine which sources had pollution\n            for (const source of sources) {\n              const pollutedKey = `${source}Polluted`;\n              const pollutedData = req[pollutedKey];\n              if (pollutedData && Object.keys(pollutedData).length > 0) {\n                const sourcePollutedKeys = allPollutedKeys.filter((k) =>\n                  k.startsWith(`${source}.`),\n                );\n                if (sourcePollutedKeys.length > 0) {\n                  onPollutionDetected(req, {\n                    source: source,\n                    pollutedKeys: sourcePollutedKeys,\n                  });\n                }\n              }\n            }\n          } catch (_) {\n            /* ignore user callback errors */\n          }\n        }\n        if (strict && res && typeof res.status === \"function\") {\n          return res.status(400).json({\n            error: \"Bad Request\",\n            message: \"HTTP Parameter Pollution detected\",\n            pollutedParameters: allPollutedKeys,\n            code: \"HPP_DETECTED\",\n          });\n        }\n      }\n\n      return next();\n    } catch (err) {\n      // Enhanced error handling with detailed logging\n      const error = err instanceof Error ? err : new Error(String(err));\n\n      if (logger) {\n        try {\n          logger(error);\n        } catch (logErr) {\n          // If custom logger fails, surface via console.error so the developer\n          // sees their logger bug regardless of NODE_ENV. Using `process.env`\n          // directly here would crash in edge runtimes (Cloudflare Workers,\n          // Vercel Edge, Deno without Node-compat) where `process` may be\n          // undefined or `process.env` may be a throwing Proxy.\n          console.error(\"[hppx] Logger failed:\", logErr);\n          console.error(\"[hppx] Original error:\", error);\n        }\n      }\n\n      // Pass error to next middleware for proper error handling\n      return next(error);\n    }\n  };\n}\n\nexport { DANGEROUS_KEYS, DEFAULT_STRATEGY, DEFAULT_SOURCES };\n"],"mappings":"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","names":[]}