hppx 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,7 +29,9 @@ npm install hppx
29
29
 
30
30
  ## Usage
31
31
 
32
- ```ts
32
+ ### ESM (ES Modules)
33
+
34
+ ```typescript
33
35
  import express from "express";
34
36
  import hppx from "hppx";
35
37
 
@@ -55,6 +57,34 @@ app.get("/search", (req, res) => {
55
57
  });
56
58
  ```
57
59
 
60
+ ### CommonJS
61
+
62
+ ```javascript
63
+ const express = require("express");
64
+ const hppx = require("hppx");
65
+
66
+ const app = express();
67
+ app.use(express.urlencoded({ extended: true }));
68
+ app.use(express.json());
69
+
70
+ app.use(
71
+ hppx({
72
+ whitelist: ["tags", "user.roles", "ids"],
73
+ mergeStrategy: "keepLast",
74
+ sources: ["query", "body"],
75
+ }),
76
+ );
77
+
78
+ app.get("/search", (req, res) => {
79
+ res.json({
80
+ query: req.query,
81
+ queryPolluted: req.queryPolluted ?? {},
82
+ body: req.body ?? {},
83
+ bodyPolluted: req.bodyPolluted ?? {},
84
+ });
85
+ });
86
+ ```
87
+
58
88
  ## API
59
89
 
60
90
  ### default export: `hppx(options?: HppxOptions)`
@@ -87,7 +117,8 @@ Creates an Express-compatible middleware. Applies sanitization to each selected
87
117
  - `preserveNull?: boolean` — preserve null values (default: true)
88
118
  - `strict?: boolean` — if pollution detected, immediately respond with 400 error
89
119
  - `onPollutionDetected?: (req, info) => void` — callback on pollution detection
90
- - `logger?: (err: Error) => void` — custom error logger
120
+ - `logger?: (err: Error | string) => void` — custom logger for errors and pollution warnings
121
+ - `logPollution?: boolean` — enable automatic logging when pollution is detected (default: true)
91
122
 
92
123
  ### named export: `sanitize(input, options)`
93
124
 
@@ -95,28 +126,48 @@ Sanitize an arbitrary object using the same rules as the middleware. Useful for
95
126
 
96
127
  ## Advanced usage
97
128
 
98
- - Strict mode (respond 400 on pollution):
129
+ ### Strict mode (respond 400 on pollution)
99
130
 
100
- ```ts
131
+ ```typescript
101
132
  app.use(hppx({ strict: true }));
102
133
  ```
103
134
 
104
- - Process JSON bodies too:
135
+ ### Process JSON bodies too
105
136
 
106
- ```ts
137
+ ```typescript
107
138
  app.use(express.json());
108
139
  app.use(hppx({ checkBodyContentType: "any" }));
109
140
  ```
110
141
 
111
- - Exclude specific paths (supports `*` suffix):
142
+ ### Exclude specific paths (supports `*` suffix)
112
143
 
113
- ```ts
144
+ ```typescript
114
145
  app.use(hppx({ excludePaths: ["/public", "/assets*"] }));
115
146
  ```
116
147
 
117
- - Use the sanitizer directly:
148
+ ### Custom logging for pollution detection
118
149
 
119
- ```ts
150
+ ```typescript
151
+ // Use your application's logger
152
+ app.use(
153
+ hppx({
154
+ logger: (message) => {
155
+ if (typeof message === "string") {
156
+ myLogger.warn(message); // Pollution warnings
157
+ } else {
158
+ myLogger.error(message); // Errors
159
+ }
160
+ },
161
+ }),
162
+ );
163
+
164
+ // Disable automatic pollution logging
165
+ app.use(hppx({ logPollution: false }));
166
+ ```
167
+
168
+ ### Use the sanitizer directly
169
+
170
+ ```typescript
120
171
  import { sanitize } from "hppx";
121
172
 
122
173
  const clean = sanitize(payload, {
@@ -125,6 +176,17 @@ const clean = sanitize(payload, {
125
176
  });
126
177
  ```
127
178
 
179
+ **CommonJS:**
180
+
181
+ ```javascript
182
+ const { sanitize } = require("hppx");
183
+
184
+ const clean = sanitize(payload, {
185
+ whitelist: ["user.tags"],
186
+ mergeStrategy: "keepFirst",
187
+ });
188
+ ```
189
+
128
190
  ## Security Best Practices
129
191
 
130
192
  ### Input Validation
package/dist/index.cjs CHANGED
@@ -337,7 +337,8 @@ function hppx(options = {}) {
337
337
  preserveNull = true,
338
338
  strict = false,
339
339
  onPollutionDetected,
340
- logger
340
+ logger,
341
+ logPollution = true
341
342
  } = options;
342
343
  const whitelistArr = normalizeWhitelist(whitelist);
343
344
  const { isWhitelistedPath } = buildWhitelistHelpers(whitelistArr);
@@ -390,6 +391,18 @@ function hppx(options = {}) {
390
391
  }
391
392
  }
392
393
  if (anyPollutionDetected) {
394
+ if (logPollution) {
395
+ const logMessage = `[hppx] HTTP Parameter Pollution detected - ${allPollutedKeys.length} parameter(s) affected: ${allPollutedKeys.join(", ")}`;
396
+ if (logger) {
397
+ try {
398
+ logger(logMessage);
399
+ } catch (_) {
400
+ console.warn(logMessage);
401
+ }
402
+ } else {
403
+ console.warn(logMessage);
404
+ }
405
+ }
393
406
  if (onPollutionDetected) {
394
407
  try {
395
408
  for (const source of sources) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\r\n * hppx — Superior HTTP Parameter Pollution protection middleware\r\n *\r\n * - Protects against parameter and prototype pollution\r\n * - Supports nested whitelists via dot-notation and leaf matching\r\n * - Merge strategies: keepFirst | keepLast | combine\r\n * - Multiple middleware compatibility: arrays are \"put aside\" once and selectively restored\r\n * - Exposes req.queryPolluted / req.bodyPolluted / req.paramsPolluted\r\n * - TypeScript-first API\r\n */\r\n\r\nexport type RequestSource = \"query\" | \"body\" | \"params\";\r\nexport type MergeStrategy = \"keepFirst\" | \"keepLast\" | \"combine\";\r\n\r\nexport interface SanitizeOptions {\r\n whitelist?: string[] | string;\r\n mergeStrategy?: MergeStrategy;\r\n maxDepth?: number;\r\n maxKeys?: number;\r\n maxArrayLength?: number;\r\n maxKeyLength?: number;\r\n trimValues?: boolean;\r\n preserveNull?: boolean;\r\n}\r\n\r\nexport interface HppxOptions extends SanitizeOptions {\r\n sources?: RequestSource[];\r\n /** When to process req.body */\r\n checkBodyContentType?: \"urlencoded\" | \"any\" | \"none\";\r\n excludePaths?: string[];\r\n strict?: boolean;\r\n onPollutionDetected?: (\r\n req: Record<string, unknown>,\r\n info: { source: RequestSource; pollutedKeys: string[] },\r\n ) => void;\r\n logger?: (err: Error | unknown) => void;\r\n}\r\n\r\nexport interface SanitizedResult<T> {\r\n cleaned: T;\r\n pollutedTree: Record<string, unknown>;\r\n pollutedKeys: string[];\r\n}\r\n\r\nconst DEFAULT_SOURCES: RequestSource[] = [\"query\", \"body\", \"params\"];\r\nconst DEFAULT_STRATEGY: MergeStrategy = \"keepLast\";\r\nconst DANGEROUS_KEYS = new Set([\"__proto__\", \"prototype\", \"constructor\"]);\r\n\r\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\r\n if (value === null || typeof value !== \"object\") return false;\r\n const proto = Object.getPrototypeOf(value);\r\n return proto === Object.prototype || proto === null;\r\n}\r\n\r\nfunction sanitizeKey(key: string, maxKeyLength?: number): string | null {\r\n /* istanbul ignore next */ if (typeof key !== \"string\") return null;\r\n if (DANGEROUS_KEYS.has(key)) return null;\r\n if (key.includes(\"\\u0000\")) return null;\r\n // Prevent excessively long keys that could cause DoS\r\n const maxLen = maxKeyLength ?? 200;\r\n if (key.length > maxLen) return null;\r\n // Prevent keys that are only dots or brackets (malformed) - but allow single dot as it's valid\r\n if (key.length > 1 && /^[.\\[\\]]+$/.test(key)) return null;\r\n return key;\r\n}\r\n\r\n// Cache for parsed path segments to improve performance\r\nconst pathSegmentCache = new Map<string, string[]>();\r\n\r\nfunction parsePathSegments(key: string): string[] {\r\n // Check cache first\r\n const cached = pathSegmentCache.get(key);\r\n if (cached) return cached;\r\n\r\n // Convert bracket notation to dots, then split\r\n // a[b][c] -> a.b.c\r\n const dotted = key.replace(/\\]/g, \"\").replace(/\\[/g, \".\");\r\n const result = dotted.split(\".\").filter((s) => s.length > 0);\r\n\r\n // Cache the result (limit cache size)\r\n if (pathSegmentCache.size < 500) {\r\n pathSegmentCache.set(key, result);\r\n }\r\n\r\n return result;\r\n}\r\n\r\nfunction expandObjectPaths(\r\n obj: Record<string, unknown>,\r\n maxKeyLength?: number,\r\n): Record<string, unknown> {\r\n const result: Record<string, unknown> = {};\r\n for (const rawKey of Object.keys(obj)) {\r\n const safeKey = sanitizeKey(rawKey, maxKeyLength);\r\n if (!safeKey) continue;\r\n const value = obj[rawKey];\r\n\r\n // Recursively expand nested objects first\r\n const expandedValue = isPlainObject(value)\r\n ? expandObjectPaths(value as Record<string, unknown>, maxKeyLength)\r\n : value;\r\n\r\n if (safeKey.includes(\".\") || safeKey.includes(\"[\")) {\r\n const segments = parsePathSegments(safeKey);\r\n if (segments.length > 0) {\r\n setIn(result, segments, expandedValue);\r\n continue;\r\n }\r\n }\r\n result[safeKey] = expandedValue;\r\n }\r\n return result;\r\n}\r\n\r\nfunction setReqPropertySafe(target: Record<string, unknown>, key: string, value: unknown): void {\r\n try {\r\n const desc = Object.getOwnPropertyDescriptor(target, key);\r\n if (desc && desc.configurable === false && desc.writable === false) {\r\n // Non-configurable and not writable: skip\r\n return;\r\n }\r\n if (!desc || desc.configurable !== false) {\r\n Object.defineProperty(target, key, {\r\n value,\r\n writable: true,\r\n configurable: true,\r\n enumerable: true,\r\n });\r\n return;\r\n }\r\n } catch (_) {\r\n // fall back to assignment below\r\n }\r\n try {\r\n target[key] = value;\r\n } catch (_) {\r\n // last resort: skip if cannot assign\r\n }\r\n}\r\n\r\nfunction safeDeepClone<T>(input: T, maxKeyLength?: number, maxArrayLength?: number): T {\r\n if (Array.isArray(input)) {\r\n // Limit array length to prevent memory exhaustion\r\n const limit = maxArrayLength ?? 1000;\r\n const limited = input.slice(0, limit);\r\n return limited.map((v) => safeDeepClone(v, maxKeyLength, maxArrayLength)) as T;\r\n }\r\n if (isPlainObject(input)) {\r\n const out: Record<string, unknown> = {};\r\n for (const k of Object.keys(input)) {\r\n if (!sanitizeKey(k, maxKeyLength)) continue;\r\n out[k] = safeDeepClone((input as Record<string, unknown>)[k], maxKeyLength, maxArrayLength);\r\n }\r\n return out as T;\r\n }\r\n return input;\r\n}\r\n\r\nfunction mergeValues(values: unknown[], strategy: MergeStrategy): unknown {\r\n switch (strategy) {\r\n case \"keepFirst\":\r\n return values[0];\r\n case \"keepLast\":\r\n return values[values.length - 1];\r\n case \"combine\":\r\n return values.reduce<unknown[]>((acc, v) => {\r\n if (Array.isArray(v)) acc.push(...v);\r\n else acc.push(v);\r\n return acc;\r\n }, []);\r\n default:\r\n return values[values.length - 1];\r\n }\r\n}\r\n\r\nfunction isUrlEncodedContentType(req: any): boolean {\r\n const ct = String(req?.headers?.[\"content-type\"] || \"\").toLowerCase();\r\n return ct.startsWith(\"application/x-www-form-urlencoded\");\r\n}\r\n\r\nfunction shouldExcludePath(path: string | undefined, excludePaths: string[]): boolean {\r\n if (!path || excludePaths.length === 0) return false;\r\n const currentPath = path;\r\n for (const p of excludePaths) {\r\n if (p.endsWith(\"*\")) {\r\n if (currentPath.startsWith(p.slice(0, -1))) return true;\r\n } else if (currentPath === p) {\r\n return true;\r\n }\r\n }\r\n return false;\r\n}\r\n\r\nfunction normalizeWhitelist(whitelist?: string[] | string): string[] {\r\n if (!whitelist) return [];\r\n if (typeof whitelist === \"string\") return [whitelist];\r\n return whitelist.filter((w) => typeof w === \"string\");\r\n}\r\n\r\nfunction buildWhitelistHelpers(whitelist: string[]) {\r\n const exact = new Set(whitelist);\r\n const prefixes = whitelist.filter((w) => w.length > 0);\r\n // Pre-build a cache for commonly checked paths for performance\r\n const pathCache = new Map<string, boolean>();\r\n\r\n return {\r\n exact,\r\n prefixes,\r\n isWhitelistedPath(pathParts: string[]): boolean {\r\n if (pathParts.length === 0) return false;\r\n const full = pathParts.join(\".\");\r\n\r\n // Check cache first for performance\r\n const cached = pathCache.get(full);\r\n if (cached !== undefined) return cached;\r\n\r\n let result = false;\r\n\r\n // Exact match\r\n if (exact.has(full)) {\r\n result = true;\r\n }\r\n // Leaf match\r\n else if (exact.has(pathParts[pathParts.length - 1]!)) {\r\n result = true;\r\n }\r\n // Prefix match (treat any listed segment as prefix of a subtree)\r\n else {\r\n for (const p of prefixes) {\r\n if (full === p || full.startsWith(p + \".\")) {\r\n result = true;\r\n break;\r\n }\r\n }\r\n }\r\n\r\n // Cache the result (limit cache size to prevent memory issues)\r\n if (pathCache.size < 1000) {\r\n pathCache.set(full, result);\r\n }\r\n\r\n return result;\r\n },\r\n };\r\n}\r\n\r\nfunction setIn(target: Record<string, unknown>, path: string[], value: unknown): void {\r\n /* istanbul ignore if */\r\n if (path.length === 0) {\r\n return;\r\n }\r\n let cur: Record<string, unknown> = target;\r\n for (let i = 0; i < path.length - 1; i++) {\r\n const k = path[i]!;\r\n // Additional prototype pollution protection\r\n if (DANGEROUS_KEYS.has(k)) return;\r\n if (!isPlainObject(cur[k])) {\r\n // Create a new plain object to avoid pollution\r\n cur[k] = {};\r\n }\r\n cur = cur[k] as Record<string, unknown>;\r\n }\r\n const lastKey = path[path.length - 1]!;\r\n // Final check on the last key\r\n if (DANGEROUS_KEYS.has(lastKey)) return;\r\n cur[lastKey] = value;\r\n}\r\n\r\nfunction moveWhitelistedFromPolluted(\r\n reqPart: Record<string, unknown>,\r\n polluted: Record<string, unknown>,\r\n isWhitelisted: (path: string[]) => boolean,\r\n): void {\r\n function walk(node: Record<string, unknown>, path: string[] = []) {\r\n for (const k of Object.keys(node)) {\r\n const v = node[k];\r\n const curPath = [...path, k];\r\n if (isPlainObject(v)) {\r\n walk(v as Record<string, unknown>, curPath);\r\n // prune empty objects\r\n if (Object.keys(v as Record<string, unknown>).length === 0) {\r\n delete node[k];\r\n }\r\n } else {\r\n if (isWhitelisted(curPath)) {\r\n // put back into request\r\n const normalizedPath = curPath.flatMap((seg) =>\r\n seg.includes(\".\") ? seg.split(\".\") : [seg],\r\n );\r\n setIn(reqPart, normalizedPath, v);\r\n delete node[k];\r\n }\r\n }\r\n }\r\n }\r\n walk(polluted);\r\n}\r\n\r\nfunction detectAndReduce(\r\n input: Record<string, unknown>,\r\n opts: Required<\r\n Pick<\r\n SanitizeOptions,\r\n | \"mergeStrategy\"\r\n | \"maxDepth\"\r\n | \"maxKeys\"\r\n | \"maxArrayLength\"\r\n | \"maxKeyLength\"\r\n | \"trimValues\"\r\n | \"preserveNull\"\r\n >\r\n >,\r\n): SanitizedResult<Record<string, unknown>> {\r\n let keyCount = 0;\r\n const polluted: Record<string, unknown> = {};\r\n const pollutedKeys: string[] = [];\r\n\r\n function processNode(node: unknown, path: string[] = [], depth = 0): unknown {\r\n if (node === null || node === undefined) return opts.preserveNull ? node : node;\r\n\r\n if (Array.isArray(node)) {\r\n // Limit array length to prevent DoS\r\n const limit = opts.maxArrayLength ?? 1000;\r\n const limitedNode = node.slice(0, limit);\r\n\r\n const mapped = limitedNode.map((v) => processNode(v, path, depth));\r\n if (opts.mergeStrategy === \"combine\") {\r\n // combine: do not record pollution, but flatten using mergeValues\r\n return mergeValues(mapped, \"combine\");\r\n }\r\n // Other strategies: record pollution and reduce\r\n setIn(polluted, path, safeDeepClone(limitedNode, opts.maxKeyLength, opts.maxArrayLength));\r\n pollutedKeys.push(path.join(\".\"));\r\n const reduced = mergeValues(mapped, opts.mergeStrategy);\r\n return reduced;\r\n }\r\n\r\n if (isPlainObject(node)) {\r\n if (depth > opts.maxDepth)\r\n throw new Error(`Maximum object depth (${opts.maxDepth}) exceeded`);\r\n const out: Record<string, unknown> = {};\r\n for (const rawKey of Object.keys(node)) {\r\n keyCount++;\r\n if (keyCount > (opts.maxKeys ?? Number.MAX_SAFE_INTEGER)) {\r\n throw new Error(`Maximum key count (${opts.maxKeys}) exceeded`);\r\n }\r\n const safeKey = sanitizeKey(rawKey, opts.maxKeyLength);\r\n if (!safeKey) continue;\r\n const child = (node as Record<string, unknown>)[rawKey];\r\n const childPath = path.concat([safeKey]);\r\n let value = processNode(child, childPath, depth + 1);\r\n if (typeof value === \"string\" && opts.trimValues) value = value.trim();\r\n out[safeKey] = value;\r\n }\r\n return out;\r\n }\r\n\r\n return node;\r\n }\r\n\r\n const cloned = safeDeepClone(input, opts.maxKeyLength, opts.maxArrayLength);\r\n const cleaned = processNode(cloned, [], 0) as Record<string, unknown>;\r\n return { cleaned, pollutedTree: polluted, pollutedKeys };\r\n}\r\n\r\nexport function sanitize<T extends Record<string, unknown>>(\r\n input: T,\r\n options: SanitizeOptions = {},\r\n): T {\r\n // Normalize and expand keys prior to sanitization\r\n const maxKeyLength = options.maxKeyLength ?? 200;\r\n const expandedInput = isPlainObject(input) ? expandObjectPaths(input, maxKeyLength) : input;\r\n const whitelist = normalizeWhitelist(options.whitelist);\r\n const { isWhitelistedPath } = buildWhitelistHelpers(whitelist);\r\n const {\r\n mergeStrategy = DEFAULT_STRATEGY,\r\n maxDepth = 20,\r\n maxKeys = 5000,\r\n maxArrayLength = 1000,\r\n trimValues = false,\r\n preserveNull = true,\r\n } = options;\r\n\r\n // First: reduce arrays and collect polluted\r\n const { cleaned, pollutedTree } = detectAndReduce(expandedInput, {\r\n mergeStrategy,\r\n maxDepth,\r\n maxKeys,\r\n maxArrayLength,\r\n maxKeyLength,\r\n trimValues,\r\n preserveNull,\r\n });\r\n\r\n // Second: move back whitelisted arrays\r\n moveWhitelistedFromPolluted(cleaned, pollutedTree, isWhitelistedPath);\r\n\r\n return cleaned as T;\r\n}\r\n\r\ntype ExpressLikeNext = (err?: unknown) => void;\r\n\r\nfunction validateOptions(options: HppxOptions): void {\r\n if (\r\n options.maxDepth !== undefined &&\r\n (typeof options.maxDepth !== \"number\" || options.maxDepth < 1 || options.maxDepth > 100)\r\n ) {\r\n throw new TypeError(\"maxDepth must be a number between 1 and 100\");\r\n }\r\n if (\r\n options.maxKeys !== undefined &&\r\n (typeof options.maxKeys !== \"number\" || options.maxKeys < 1)\r\n ) {\r\n throw new TypeError(\"maxKeys must be a positive number\");\r\n }\r\n if (\r\n options.maxArrayLength !== undefined &&\r\n (typeof options.maxArrayLength !== \"number\" || options.maxArrayLength < 1)\r\n ) {\r\n throw new TypeError(\"maxArrayLength must be a positive number\");\r\n }\r\n if (\r\n options.maxKeyLength !== undefined &&\r\n (typeof options.maxKeyLength !== \"number\" ||\r\n options.maxKeyLength < 1 ||\r\n options.maxKeyLength > 1000)\r\n ) {\r\n throw new TypeError(\"maxKeyLength must be a number between 1 and 1000\");\r\n }\r\n if (\r\n options.mergeStrategy !== undefined &&\r\n ![\"keepFirst\", \"keepLast\", \"combine\"].includes(options.mergeStrategy)\r\n ) {\r\n throw new TypeError(\"mergeStrategy must be 'keepFirst', 'keepLast', or 'combine'\");\r\n }\r\n if (options.sources !== undefined && !Array.isArray(options.sources)) {\r\n throw new TypeError(\"sources must be an array\");\r\n }\r\n if (options.sources !== undefined) {\r\n for (const source of options.sources) {\r\n if (![\"query\", \"body\", \"params\"].includes(source)) {\r\n throw new TypeError(\"sources must only contain 'query', 'body', or 'params'\");\r\n }\r\n }\r\n }\r\n if (\r\n options.checkBodyContentType !== undefined &&\r\n ![\"urlencoded\", \"any\", \"none\"].includes(options.checkBodyContentType)\r\n ) {\r\n throw new TypeError(\"checkBodyContentType must be 'urlencoded', 'any', or 'none'\");\r\n }\r\n if (options.excludePaths !== undefined && !Array.isArray(options.excludePaths)) {\r\n throw new TypeError(\"excludePaths must be an array\");\r\n }\r\n}\r\n\r\nexport default function hppx(options: HppxOptions = {}) {\r\n // Validate options on middleware creation\r\n validateOptions(options);\r\n\r\n const {\r\n whitelist = [],\r\n mergeStrategy = DEFAULT_STRATEGY,\r\n sources = DEFAULT_SOURCES,\r\n checkBodyContentType = \"urlencoded\",\r\n excludePaths = [],\r\n maxDepth = 20,\r\n maxKeys = 5000,\r\n maxArrayLength = 1000,\r\n maxKeyLength = 200,\r\n trimValues = false,\r\n preserveNull = true,\r\n strict = false,\r\n onPollutionDetected,\r\n logger,\r\n } = options;\r\n\r\n const whitelistArr = normalizeWhitelist(whitelist);\r\n const { isWhitelistedPath } = buildWhitelistHelpers(whitelistArr);\r\n\r\n return function hppxMiddleware(req: any, res: any, next: ExpressLikeNext) {\r\n try {\r\n if (shouldExcludePath(req?.path, excludePaths)) return next();\r\n\r\n let anyPollutionDetected = false;\r\n const allPollutedKeys: string[] = [];\r\n\r\n for (const source of sources) {\r\n /* istanbul ignore next */\r\n if (!req || typeof req !== \"object\") break;\r\n if (req[source] === undefined) continue;\r\n\r\n if (source === \"body\") {\r\n if (checkBodyContentType === \"none\") continue;\r\n if (checkBodyContentType === \"urlencoded\" && !isUrlEncodedContentType(req)) continue;\r\n }\r\n\r\n const part = req[source];\r\n if (!isPlainObject(part)) continue;\r\n\r\n // Preprocess: expand dotted and bracketed keys into nested objects\r\n const expandedPart = expandObjectPaths(part, maxKeyLength);\r\n\r\n const pollutedKey = `${source}Polluted`;\r\n const processedKey = `__hppxProcessed_${source}`;\r\n const hasProcessedBefore = Boolean(req[processedKey]);\r\n\r\n if (!hasProcessedBefore) {\r\n // First pass for this request part: reduce arrays and collect polluted\r\n const { cleaned, pollutedTree, pollutedKeys } = detectAndReduce(expandedPart, {\r\n mergeStrategy,\r\n maxDepth,\r\n maxKeys,\r\n maxArrayLength,\r\n maxKeyLength,\r\n trimValues,\r\n preserveNull,\r\n });\r\n\r\n setReqPropertySafe(req, source, cleaned);\r\n\r\n // Attach polluted object (always present as {} when source processed)\r\n setReqPropertySafe(req, pollutedKey, pollutedTree);\r\n req[processedKey] = true;\r\n\r\n // Apply whitelist now: move whitelisted arrays back\r\n const sourceData = req[source];\r\n const pollutedData = req[pollutedKey];\r\n if (isPlainObject(sourceData) && isPlainObject(pollutedData)) {\r\n moveWhitelistedFromPolluted(sourceData, pollutedData, isWhitelistedPath);\r\n }\r\n\r\n if (pollutedKeys.length > 0) {\r\n anyPollutionDetected = true;\r\n for (const k of pollutedKeys) allPollutedKeys.push(`${source}.${k}`);\r\n }\r\n } else {\r\n // Subsequent middleware: only put back whitelisted entries\r\n const sourceData = req[source];\r\n const pollutedData = req[pollutedKey];\r\n if (isPlainObject(sourceData) && isPlainObject(pollutedData)) {\r\n moveWhitelistedFromPolluted(sourceData, pollutedData, isWhitelistedPath);\r\n }\r\n // pollution already accounted for in previous pass\r\n }\r\n }\r\n\r\n if (anyPollutionDetected) {\r\n if (onPollutionDetected) {\r\n try {\r\n // Determine which sources had pollution\r\n for (const source of sources) {\r\n const pollutedKey = `${source}Polluted`;\r\n const pollutedData = req[pollutedKey];\r\n if (pollutedData && Object.keys(pollutedData).length > 0) {\r\n const sourcePollutedKeys = allPollutedKeys.filter((k) =>\r\n k.startsWith(`${source}.`),\r\n );\r\n if (sourcePollutedKeys.length > 0) {\r\n onPollutionDetected(req, {\r\n source: source,\r\n pollutedKeys: sourcePollutedKeys,\r\n });\r\n }\r\n }\r\n }\r\n } catch (_) {\r\n /* ignore user callback errors */\r\n }\r\n }\r\n if (strict && res && typeof res.status === \"function\") {\r\n return res.status(400).json({\r\n error: \"Bad Request\",\r\n message: \"HTTP Parameter Pollution detected\",\r\n pollutedParameters: allPollutedKeys,\r\n code: \"HPP_DETECTED\",\r\n });\r\n }\r\n }\r\n\r\n return next();\r\n } catch (err) {\r\n // Enhanced error handling with detailed logging\r\n const error = err instanceof Error ? err : new Error(String(err));\r\n\r\n if (logger) {\r\n try {\r\n logger(error);\r\n } catch (logErr) {\r\n // If custom logger fails, use console.error as fallback in development\r\n if (process.env.NODE_ENV !== \"production\") {\r\n console.error(\"[hppx] Logger failed:\", logErr);\r\n console.error(\"[hppx] Original error:\", error);\r\n }\r\n }\r\n }\r\n\r\n // Pass error to next middleware for proper error handling\r\n return next(error);\r\n }\r\n };\r\n}\r\n\r\nexport { DANGEROUS_KEYS, DEFAULT_STRATEGY, DEFAULT_SOURCES };\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\r\n * hppx — Superior HTTP Parameter Pollution protection middleware\r\n *\r\n * - Protects against parameter and prototype pollution\r\n * - Supports nested whitelists via dot-notation and leaf matching\r\n * - Merge strategies: keepFirst | keepLast | combine\r\n * - Multiple middleware compatibility: arrays are \"put aside\" once and selectively restored\r\n * - Exposes req.queryPolluted / req.bodyPolluted / req.paramsPolluted\r\n * - TypeScript-first API\r\n */\r\n\r\nexport type RequestSource = \"query\" | \"body\" | \"params\";\r\nexport type MergeStrategy = \"keepFirst\" | \"keepLast\" | \"combine\";\r\n\r\nexport interface SanitizeOptions {\r\n whitelist?: string[] | string;\r\n mergeStrategy?: MergeStrategy;\r\n maxDepth?: number;\r\n maxKeys?: number;\r\n maxArrayLength?: number;\r\n maxKeyLength?: number;\r\n trimValues?: boolean;\r\n preserveNull?: boolean;\r\n}\r\n\r\nexport interface HppxOptions extends SanitizeOptions {\r\n sources?: RequestSource[];\r\n /** When to process req.body */\r\n checkBodyContentType?: \"urlencoded\" | \"any\" | \"none\";\r\n excludePaths?: string[];\r\n strict?: boolean;\r\n onPollutionDetected?: (\r\n req: Record<string, unknown>,\r\n info: { source: RequestSource; pollutedKeys: string[] },\r\n ) => void;\r\n logger?: (err: Error | unknown) => void;\r\n /** Enable logging when pollution is detected (default: true) */\r\n logPollution?: boolean;\r\n}\r\n\r\nexport interface SanitizedResult<T> {\r\n cleaned: T;\r\n pollutedTree: Record<string, unknown>;\r\n pollutedKeys: string[];\r\n}\r\n\r\nconst DEFAULT_SOURCES: RequestSource[] = [\"query\", \"body\", \"params\"];\r\nconst DEFAULT_STRATEGY: MergeStrategy = \"keepLast\";\r\nconst DANGEROUS_KEYS = new Set([\"__proto__\", \"prototype\", \"constructor\"]);\r\n\r\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\r\n if (value === null || typeof value !== \"object\") return false;\r\n const proto = Object.getPrototypeOf(value);\r\n return proto === Object.prototype || proto === null;\r\n}\r\n\r\nfunction sanitizeKey(key: string, maxKeyLength?: number): string | null {\r\n /* istanbul ignore next */ if (typeof key !== \"string\") return null;\r\n if (DANGEROUS_KEYS.has(key)) return null;\r\n if (key.includes(\"\\u0000\")) return null;\r\n // Prevent excessively long keys that could cause DoS\r\n const maxLen = maxKeyLength ?? 200;\r\n if (key.length > maxLen) return null;\r\n // Prevent keys that are only dots or brackets (malformed) - but allow single dot as it's valid\r\n if (key.length > 1 && /^[.\\[\\]]+$/.test(key)) return null;\r\n return key;\r\n}\r\n\r\n// Cache for parsed path segments to improve performance\r\nconst pathSegmentCache = new Map<string, string[]>();\r\n\r\nfunction parsePathSegments(key: string): string[] {\r\n // Check cache first\r\n const cached = pathSegmentCache.get(key);\r\n if (cached) return cached;\r\n\r\n // Convert bracket notation to dots, then split\r\n // a[b][c] -> a.b.c\r\n const dotted = key.replace(/\\]/g, \"\").replace(/\\[/g, \".\");\r\n const result = dotted.split(\".\").filter((s) => s.length > 0);\r\n\r\n // Cache the result (limit cache size)\r\n if (pathSegmentCache.size < 500) {\r\n pathSegmentCache.set(key, result);\r\n }\r\n\r\n return result;\r\n}\r\n\r\nfunction expandObjectPaths(\r\n obj: Record<string, unknown>,\r\n maxKeyLength?: number,\r\n): Record<string, unknown> {\r\n const result: Record<string, unknown> = {};\r\n for (const rawKey of Object.keys(obj)) {\r\n const safeKey = sanitizeKey(rawKey, maxKeyLength);\r\n if (!safeKey) continue;\r\n const value = obj[rawKey];\r\n\r\n // Recursively expand nested objects first\r\n const expandedValue = isPlainObject(value)\r\n ? expandObjectPaths(value as Record<string, unknown>, maxKeyLength)\r\n : value;\r\n\r\n if (safeKey.includes(\".\") || safeKey.includes(\"[\")) {\r\n const segments = parsePathSegments(safeKey);\r\n if (segments.length > 0) {\r\n setIn(result, segments, expandedValue);\r\n continue;\r\n }\r\n }\r\n result[safeKey] = expandedValue;\r\n }\r\n return result;\r\n}\r\n\r\nfunction setReqPropertySafe(target: Record<string, unknown>, key: string, value: unknown): void {\r\n try {\r\n const desc = Object.getOwnPropertyDescriptor(target, key);\r\n if (desc && desc.configurable === false && desc.writable === false) {\r\n // Non-configurable and not writable: skip\r\n return;\r\n }\r\n if (!desc || desc.configurable !== false) {\r\n Object.defineProperty(target, key, {\r\n value,\r\n writable: true,\r\n configurable: true,\r\n enumerable: true,\r\n });\r\n return;\r\n }\r\n } catch (_) {\r\n // fall back to assignment below\r\n }\r\n try {\r\n target[key] = value;\r\n } catch (_) {\r\n // last resort: skip if cannot assign\r\n }\r\n}\r\n\r\nfunction safeDeepClone<T>(input: T, maxKeyLength?: number, maxArrayLength?: number): T {\r\n if (Array.isArray(input)) {\r\n // Limit array length to prevent memory exhaustion\r\n const limit = maxArrayLength ?? 1000;\r\n const limited = input.slice(0, limit);\r\n return limited.map((v) => safeDeepClone(v, maxKeyLength, maxArrayLength)) as T;\r\n }\r\n if (isPlainObject(input)) {\r\n const out: Record<string, unknown> = {};\r\n for (const k of Object.keys(input)) {\r\n if (!sanitizeKey(k, maxKeyLength)) continue;\r\n out[k] = safeDeepClone((input as Record<string, unknown>)[k], maxKeyLength, maxArrayLength);\r\n }\r\n return out as T;\r\n }\r\n return input;\r\n}\r\n\r\nfunction mergeValues(values: unknown[], strategy: MergeStrategy): unknown {\r\n switch (strategy) {\r\n case \"keepFirst\":\r\n return values[0];\r\n case \"keepLast\":\r\n return values[values.length - 1];\r\n case \"combine\":\r\n return values.reduce<unknown[]>((acc, v) => {\r\n if (Array.isArray(v)) acc.push(...v);\r\n else acc.push(v);\r\n return acc;\r\n }, []);\r\n default:\r\n return values[values.length - 1];\r\n }\r\n}\r\n\r\nfunction isUrlEncodedContentType(req: any): boolean {\r\n const ct = String(req?.headers?.[\"content-type\"] || \"\").toLowerCase();\r\n return ct.startsWith(\"application/x-www-form-urlencoded\");\r\n}\r\n\r\nfunction shouldExcludePath(path: string | undefined, excludePaths: string[]): boolean {\r\n if (!path || excludePaths.length === 0) return false;\r\n const currentPath = path;\r\n for (const p of excludePaths) {\r\n if (p.endsWith(\"*\")) {\r\n if (currentPath.startsWith(p.slice(0, -1))) return true;\r\n } else if (currentPath === p) {\r\n return true;\r\n }\r\n }\r\n return false;\r\n}\r\n\r\nfunction normalizeWhitelist(whitelist?: string[] | string): string[] {\r\n if (!whitelist) return [];\r\n if (typeof whitelist === \"string\") return [whitelist];\r\n return whitelist.filter((w) => typeof w === \"string\");\r\n}\r\n\r\nfunction buildWhitelistHelpers(whitelist: string[]) {\r\n const exact = new Set(whitelist);\r\n const prefixes = whitelist.filter((w) => w.length > 0);\r\n // Pre-build a cache for commonly checked paths for performance\r\n const pathCache = new Map<string, boolean>();\r\n\r\n return {\r\n exact,\r\n prefixes,\r\n isWhitelistedPath(pathParts: string[]): boolean {\r\n if (pathParts.length === 0) return false;\r\n const full = pathParts.join(\".\");\r\n\r\n // Check cache first for performance\r\n const cached = pathCache.get(full);\r\n if (cached !== undefined) return cached;\r\n\r\n let result = false;\r\n\r\n // Exact match\r\n if (exact.has(full)) {\r\n result = true;\r\n }\r\n // Leaf match\r\n else if (exact.has(pathParts[pathParts.length - 1]!)) {\r\n result = true;\r\n }\r\n // Prefix match (treat any listed segment as prefix of a subtree)\r\n else {\r\n for (const p of prefixes) {\r\n if (full === p || full.startsWith(p + \".\")) {\r\n result = true;\r\n break;\r\n }\r\n }\r\n }\r\n\r\n // Cache the result (limit cache size to prevent memory issues)\r\n if (pathCache.size < 1000) {\r\n pathCache.set(full, result);\r\n }\r\n\r\n return result;\r\n },\r\n };\r\n}\r\n\r\nfunction setIn(target: Record<string, unknown>, path: string[], value: unknown): void {\r\n /* istanbul ignore if */\r\n if (path.length === 0) {\r\n return;\r\n }\r\n let cur: Record<string, unknown> = target;\r\n for (let i = 0; i < path.length - 1; i++) {\r\n const k = path[i]!;\r\n // Additional prototype pollution protection\r\n if (DANGEROUS_KEYS.has(k)) return;\r\n if (!isPlainObject(cur[k])) {\r\n // Create a new plain object to avoid pollution\r\n cur[k] = {};\r\n }\r\n cur = cur[k] as Record<string, unknown>;\r\n }\r\n const lastKey = path[path.length - 1]!;\r\n // Final check on the last key\r\n if (DANGEROUS_KEYS.has(lastKey)) return;\r\n cur[lastKey] = value;\r\n}\r\n\r\nfunction moveWhitelistedFromPolluted(\r\n reqPart: Record<string, unknown>,\r\n polluted: Record<string, unknown>,\r\n isWhitelisted: (path: string[]) => boolean,\r\n): void {\r\n function walk(node: Record<string, unknown>, path: string[] = []) {\r\n for (const k of Object.keys(node)) {\r\n const v = node[k];\r\n const curPath = [...path, k];\r\n if (isPlainObject(v)) {\r\n walk(v as Record<string, unknown>, curPath);\r\n // prune empty objects\r\n if (Object.keys(v as Record<string, unknown>).length === 0) {\r\n delete node[k];\r\n }\r\n } else {\r\n if (isWhitelisted(curPath)) {\r\n // put back into request\r\n const normalizedPath = curPath.flatMap((seg) =>\r\n seg.includes(\".\") ? seg.split(\".\") : [seg],\r\n );\r\n setIn(reqPart, normalizedPath, v);\r\n delete node[k];\r\n }\r\n }\r\n }\r\n }\r\n walk(polluted);\r\n}\r\n\r\nfunction detectAndReduce(\r\n input: Record<string, unknown>,\r\n opts: Required<\r\n Pick<\r\n SanitizeOptions,\r\n | \"mergeStrategy\"\r\n | \"maxDepth\"\r\n | \"maxKeys\"\r\n | \"maxArrayLength\"\r\n | \"maxKeyLength\"\r\n | \"trimValues\"\r\n | \"preserveNull\"\r\n >\r\n >,\r\n): SanitizedResult<Record<string, unknown>> {\r\n let keyCount = 0;\r\n const polluted: Record<string, unknown> = {};\r\n const pollutedKeys: string[] = [];\r\n\r\n function processNode(node: unknown, path: string[] = [], depth = 0): unknown {\r\n if (node === null || node === undefined) return opts.preserveNull ? node : node;\r\n\r\n if (Array.isArray(node)) {\r\n // Limit array length to prevent DoS\r\n const limit = opts.maxArrayLength ?? 1000;\r\n const limitedNode = node.slice(0, limit);\r\n\r\n const mapped = limitedNode.map((v) => processNode(v, path, depth));\r\n if (opts.mergeStrategy === \"combine\") {\r\n // combine: do not record pollution, but flatten using mergeValues\r\n return mergeValues(mapped, \"combine\");\r\n }\r\n // Other strategies: record pollution and reduce\r\n setIn(polluted, path, safeDeepClone(limitedNode, opts.maxKeyLength, opts.maxArrayLength));\r\n pollutedKeys.push(path.join(\".\"));\r\n const reduced = mergeValues(mapped, opts.mergeStrategy);\r\n return reduced;\r\n }\r\n\r\n if (isPlainObject(node)) {\r\n if (depth > opts.maxDepth)\r\n throw new Error(`Maximum object depth (${opts.maxDepth}) exceeded`);\r\n const out: Record<string, unknown> = {};\r\n for (const rawKey of Object.keys(node)) {\r\n keyCount++;\r\n if (keyCount > (opts.maxKeys ?? Number.MAX_SAFE_INTEGER)) {\r\n throw new Error(`Maximum key count (${opts.maxKeys}) exceeded`);\r\n }\r\n const safeKey = sanitizeKey(rawKey, opts.maxKeyLength);\r\n if (!safeKey) continue;\r\n const child = (node as Record<string, unknown>)[rawKey];\r\n const childPath = path.concat([safeKey]);\r\n let value = processNode(child, childPath, depth + 1);\r\n if (typeof value === \"string\" && opts.trimValues) value = value.trim();\r\n out[safeKey] = value;\r\n }\r\n return out;\r\n }\r\n\r\n return node;\r\n }\r\n\r\n const cloned = safeDeepClone(input, opts.maxKeyLength, opts.maxArrayLength);\r\n const cleaned = processNode(cloned, [], 0) as Record<string, unknown>;\r\n return { cleaned, pollutedTree: polluted, pollutedKeys };\r\n}\r\n\r\nexport function sanitize<T extends Record<string, unknown>>(\r\n input: T,\r\n options: SanitizeOptions = {},\r\n): T {\r\n // Normalize and expand keys prior to sanitization\r\n const maxKeyLength = options.maxKeyLength ?? 200;\r\n const expandedInput = isPlainObject(input) ? expandObjectPaths(input, maxKeyLength) : input;\r\n const whitelist = normalizeWhitelist(options.whitelist);\r\n const { isWhitelistedPath } = buildWhitelistHelpers(whitelist);\r\n const {\r\n mergeStrategy = DEFAULT_STRATEGY,\r\n maxDepth = 20,\r\n maxKeys = 5000,\r\n maxArrayLength = 1000,\r\n trimValues = false,\r\n preserveNull = true,\r\n } = options;\r\n\r\n // First: reduce arrays and collect polluted\r\n const { cleaned, pollutedTree } = detectAndReduce(expandedInput, {\r\n mergeStrategy,\r\n maxDepth,\r\n maxKeys,\r\n maxArrayLength,\r\n maxKeyLength,\r\n trimValues,\r\n preserveNull,\r\n });\r\n\r\n // Second: move back whitelisted arrays\r\n moveWhitelistedFromPolluted(cleaned, pollutedTree, isWhitelistedPath);\r\n\r\n return cleaned as T;\r\n}\r\n\r\ntype ExpressLikeNext = (err?: unknown) => void;\r\n\r\nfunction validateOptions(options: HppxOptions): void {\r\n if (\r\n options.maxDepth !== undefined &&\r\n (typeof options.maxDepth !== \"number\" || options.maxDepth < 1 || options.maxDepth > 100)\r\n ) {\r\n throw new TypeError(\"maxDepth must be a number between 1 and 100\");\r\n }\r\n if (\r\n options.maxKeys !== undefined &&\r\n (typeof options.maxKeys !== \"number\" || options.maxKeys < 1)\r\n ) {\r\n throw new TypeError(\"maxKeys must be a positive number\");\r\n }\r\n if (\r\n options.maxArrayLength !== undefined &&\r\n (typeof options.maxArrayLength !== \"number\" || options.maxArrayLength < 1)\r\n ) {\r\n throw new TypeError(\"maxArrayLength must be a positive number\");\r\n }\r\n if (\r\n options.maxKeyLength !== undefined &&\r\n (typeof options.maxKeyLength !== \"number\" ||\r\n options.maxKeyLength < 1 ||\r\n options.maxKeyLength > 1000)\r\n ) {\r\n throw new TypeError(\"maxKeyLength must be a number between 1 and 1000\");\r\n }\r\n if (\r\n options.mergeStrategy !== undefined &&\r\n ![\"keepFirst\", \"keepLast\", \"combine\"].includes(options.mergeStrategy)\r\n ) {\r\n throw new TypeError(\"mergeStrategy must be 'keepFirst', 'keepLast', or 'combine'\");\r\n }\r\n if (options.sources !== undefined && !Array.isArray(options.sources)) {\r\n throw new TypeError(\"sources must be an array\");\r\n }\r\n if (options.sources !== undefined) {\r\n for (const source of options.sources) {\r\n if (![\"query\", \"body\", \"params\"].includes(source)) {\r\n throw new TypeError(\"sources must only contain 'query', 'body', or 'params'\");\r\n }\r\n }\r\n }\r\n if (\r\n options.checkBodyContentType !== undefined &&\r\n ![\"urlencoded\", \"any\", \"none\"].includes(options.checkBodyContentType)\r\n ) {\r\n throw new TypeError(\"checkBodyContentType must be 'urlencoded', 'any', or 'none'\");\r\n }\r\n if (options.excludePaths !== undefined && !Array.isArray(options.excludePaths)) {\r\n throw new TypeError(\"excludePaths must be an array\");\r\n }\r\n}\r\n\r\nexport default function hppx(options: HppxOptions = {}) {\r\n // Validate options on middleware creation\r\n validateOptions(options);\r\n\r\n const {\r\n whitelist = [],\r\n mergeStrategy = DEFAULT_STRATEGY,\r\n sources = DEFAULT_SOURCES,\r\n checkBodyContentType = \"urlencoded\",\r\n excludePaths = [],\r\n maxDepth = 20,\r\n maxKeys = 5000,\r\n maxArrayLength = 1000,\r\n maxKeyLength = 200,\r\n trimValues = false,\r\n preserveNull = true,\r\n strict = false,\r\n onPollutionDetected,\r\n logger,\r\n logPollution = true,\r\n } = options;\r\n\r\n const whitelistArr = normalizeWhitelist(whitelist);\r\n const { isWhitelistedPath } = buildWhitelistHelpers(whitelistArr);\r\n\r\n return function hppxMiddleware(req: any, res: any, next: ExpressLikeNext) {\r\n try {\r\n if (shouldExcludePath(req?.path, excludePaths)) return next();\r\n\r\n let anyPollutionDetected = false;\r\n const allPollutedKeys: string[] = [];\r\n\r\n for (const source of sources) {\r\n /* istanbul ignore next */\r\n if (!req || typeof req !== \"object\") break;\r\n if (req[source] === undefined) continue;\r\n\r\n if (source === \"body\") {\r\n if (checkBodyContentType === \"none\") continue;\r\n if (checkBodyContentType === \"urlencoded\" && !isUrlEncodedContentType(req)) continue;\r\n }\r\n\r\n const part = req[source];\r\n if (!isPlainObject(part)) continue;\r\n\r\n // Preprocess: expand dotted and bracketed keys into nested objects\r\n const expandedPart = expandObjectPaths(part, maxKeyLength);\r\n\r\n const pollutedKey = `${source}Polluted`;\r\n const processedKey = `__hppxProcessed_${source}`;\r\n const hasProcessedBefore = Boolean(req[processedKey]);\r\n\r\n if (!hasProcessedBefore) {\r\n // First pass for this request part: reduce arrays and collect polluted\r\n const { cleaned, pollutedTree, pollutedKeys } = detectAndReduce(expandedPart, {\r\n mergeStrategy,\r\n maxDepth,\r\n maxKeys,\r\n maxArrayLength,\r\n maxKeyLength,\r\n trimValues,\r\n preserveNull,\r\n });\r\n\r\n setReqPropertySafe(req, source, cleaned);\r\n\r\n // Attach polluted object (always present as {} when source processed)\r\n setReqPropertySafe(req, pollutedKey, pollutedTree);\r\n req[processedKey] = true;\r\n\r\n // Apply whitelist now: move whitelisted arrays back\r\n const sourceData = req[source];\r\n const pollutedData = req[pollutedKey];\r\n if (isPlainObject(sourceData) && isPlainObject(pollutedData)) {\r\n moveWhitelistedFromPolluted(sourceData, pollutedData, isWhitelistedPath);\r\n }\r\n\r\n if (pollutedKeys.length > 0) {\r\n anyPollutionDetected = true;\r\n for (const k of pollutedKeys) allPollutedKeys.push(`${source}.${k}`);\r\n }\r\n } else {\r\n // Subsequent middleware: only put back whitelisted entries\r\n const sourceData = req[source];\r\n const pollutedData = req[pollutedKey];\r\n if (isPlainObject(sourceData) && isPlainObject(pollutedData)) {\r\n moveWhitelistedFromPolluted(sourceData, pollutedData, isWhitelistedPath);\r\n }\r\n // pollution already accounted for in previous pass\r\n }\r\n }\r\n\r\n if (anyPollutionDetected) {\r\n // Log pollution detection if enabled\r\n if (logPollution) {\r\n const logMessage = `[hppx] HTTP Parameter Pollution detected - ${allPollutedKeys.length} parameter(s) affected: ${allPollutedKeys.join(\", \")}`;\r\n if (logger) {\r\n try {\r\n logger(logMessage);\r\n } catch (_) {\r\n // Fallback to console.warn if logger fails\r\n console.warn(logMessage);\r\n }\r\n } else {\r\n console.warn(logMessage);\r\n }\r\n }\r\n\r\n if (onPollutionDetected) {\r\n try {\r\n // Determine which sources had pollution\r\n for (const source of sources) {\r\n const pollutedKey = `${source}Polluted`;\r\n const pollutedData = req[pollutedKey];\r\n if (pollutedData && Object.keys(pollutedData).length > 0) {\r\n const sourcePollutedKeys = allPollutedKeys.filter((k) =>\r\n k.startsWith(`${source}.`),\r\n );\r\n if (sourcePollutedKeys.length > 0) {\r\n onPollutionDetected(req, {\r\n source: source,\r\n pollutedKeys: sourcePollutedKeys,\r\n });\r\n }\r\n }\r\n }\r\n } catch (_) {\r\n /* ignore user callback errors */\r\n }\r\n }\r\n if (strict && res && typeof res.status === \"function\") {\r\n return res.status(400).json({\r\n error: \"Bad Request\",\r\n message: \"HTTP Parameter Pollution detected\",\r\n pollutedParameters: allPollutedKeys,\r\n code: \"HPP_DETECTED\",\r\n });\r\n }\r\n }\r\n\r\n return next();\r\n } catch (err) {\r\n // Enhanced error handling with detailed logging\r\n const error = err instanceof Error ? err : new Error(String(err));\r\n\r\n if (logger) {\r\n try {\r\n logger(error);\r\n } catch (logErr) {\r\n // If custom logger fails, use console.error as fallback in development\r\n if (process.env.NODE_ENV !== \"production\") {\r\n console.error(\"[hppx] Logger failed:\", logErr);\r\n console.error(\"[hppx] Original error:\", error);\r\n }\r\n }\r\n }\r\n\r\n // Pass error to next middleware for proper error handling\r\n return next(error);\r\n }\r\n };\r\n}\r\n\r\nexport { DANGEROUS_KEYS, DEFAULT_STRATEGY, DEFAULT_SOURCES };\r\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -31,6 +31,8 @@ interface HppxOptions extends SanitizeOptions {
31
31
  pollutedKeys: string[];
32
32
  }) => void;
33
33
  logger?: (err: Error | unknown) => void;
34
+ /** Enable logging when pollution is detected (default: true) */
35
+ logPollution?: boolean;
34
36
  }
35
37
  interface SanitizedResult<T> {
36
38
  cleaned: T;
@@ -44,4 +46,6 @@ declare function sanitize<T extends Record<string, unknown>>(input: T, options?:
44
46
  type ExpressLikeNext = (err?: unknown) => void;
45
47
  declare function hppx(options?: HppxOptions): (req: any, res: any, next: ExpressLikeNext) => any;
46
48
 
47
- export { DANGEROUS_KEYS, DEFAULT_SOURCES, DEFAULT_STRATEGY, type HppxOptions, type MergeStrategy, type RequestSource, type SanitizeOptions, type SanitizedResult, hppx as default, sanitize };
49
+ // @ts-ignore
50
+ export = hppx;
51
+ export { DANGEROUS_KEYS, DEFAULT_SOURCES, DEFAULT_STRATEGY, type HppxOptions, type MergeStrategy, type RequestSource, type SanitizeOptions, type SanitizedResult, sanitize };
package/dist/index.d.ts CHANGED
@@ -31,6 +31,8 @@ interface HppxOptions extends SanitizeOptions {
31
31
  pollutedKeys: string[];
32
32
  }) => void;
33
33
  logger?: (err: Error | unknown) => void;
34
+ /** Enable logging when pollution is detected (default: true) */
35
+ logPollution?: boolean;
34
36
  }
35
37
  interface SanitizedResult<T> {
36
38
  cleaned: T;
@@ -309,7 +309,8 @@ function hppx(options = {}) {
309
309
  preserveNull = true,
310
310
  strict = false,
311
311
  onPollutionDetected,
312
- logger
312
+ logger,
313
+ logPollution = true
313
314
  } = options;
314
315
  const whitelistArr = normalizeWhitelist(whitelist);
315
316
  const { isWhitelistedPath } = buildWhitelistHelpers(whitelistArr);
@@ -362,6 +363,18 @@ function hppx(options = {}) {
362
363
  }
363
364
  }
364
365
  if (anyPollutionDetected) {
366
+ if (logPollution) {
367
+ const logMessage = `[hppx] HTTP Parameter Pollution detected - ${allPollutedKeys.length} parameter(s) affected: ${allPollutedKeys.join(", ")}`;
368
+ if (logger) {
369
+ try {
370
+ logger(logMessage);
371
+ } catch (_) {
372
+ console.warn(logMessage);
373
+ }
374
+ } else {
375
+ console.warn(logMessage);
376
+ }
377
+ }
365
378
  if (onPollutionDetected) {
366
379
  try {
367
380
  for (const source of sources) {
@@ -415,4 +428,4 @@ export {
415
428
  hppx as default,
416
429
  sanitize
417
430
  };
418
- //# sourceMappingURL=index.js.map
431
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\r\n * hppx — Superior HTTP Parameter Pollution protection middleware\r\n *\r\n * - Protects against parameter and prototype pollution\r\n * - Supports nested whitelists via dot-notation and leaf matching\r\n * - Merge strategies: keepFirst | keepLast | combine\r\n * - Multiple middleware compatibility: arrays are \"put aside\" once and selectively restored\r\n * - Exposes req.queryPolluted / req.bodyPolluted / req.paramsPolluted\r\n * - TypeScript-first API\r\n */\r\n\r\nexport type RequestSource = \"query\" | \"body\" | \"params\";\r\nexport type MergeStrategy = \"keepFirst\" | \"keepLast\" | \"combine\";\r\n\r\nexport interface SanitizeOptions {\r\n whitelist?: string[] | string;\r\n mergeStrategy?: MergeStrategy;\r\n maxDepth?: number;\r\n maxKeys?: number;\r\n maxArrayLength?: number;\r\n maxKeyLength?: number;\r\n trimValues?: boolean;\r\n preserveNull?: boolean;\r\n}\r\n\r\nexport interface HppxOptions extends SanitizeOptions {\r\n sources?: RequestSource[];\r\n /** When to process req.body */\r\n checkBodyContentType?: \"urlencoded\" | \"any\" | \"none\";\r\n excludePaths?: string[];\r\n strict?: boolean;\r\n onPollutionDetected?: (\r\n req: Record<string, unknown>,\r\n info: { source: RequestSource; pollutedKeys: string[] },\r\n ) => void;\r\n logger?: (err: Error | unknown) => void;\r\n /** Enable logging when pollution is detected (default: true) */\r\n logPollution?: boolean;\r\n}\r\n\r\nexport interface SanitizedResult<T> {\r\n cleaned: T;\r\n pollutedTree: Record<string, unknown>;\r\n pollutedKeys: string[];\r\n}\r\n\r\nconst DEFAULT_SOURCES: RequestSource[] = [\"query\", \"body\", \"params\"];\r\nconst DEFAULT_STRATEGY: MergeStrategy = \"keepLast\";\r\nconst DANGEROUS_KEYS = new Set([\"__proto__\", \"prototype\", \"constructor\"]);\r\n\r\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\r\n if (value === null || typeof value !== \"object\") return false;\r\n const proto = Object.getPrototypeOf(value);\r\n return proto === Object.prototype || proto === null;\r\n}\r\n\r\nfunction sanitizeKey(key: string, maxKeyLength?: number): string | null {\r\n /* istanbul ignore next */ if (typeof key !== \"string\") return null;\r\n if (DANGEROUS_KEYS.has(key)) return null;\r\n if (key.includes(\"\\u0000\")) return null;\r\n // Prevent excessively long keys that could cause DoS\r\n const maxLen = maxKeyLength ?? 200;\r\n if (key.length > maxLen) return null;\r\n // Prevent keys that are only dots or brackets (malformed) - but allow single dot as it's valid\r\n if (key.length > 1 && /^[.\\[\\]]+$/.test(key)) return null;\r\n return key;\r\n}\r\n\r\n// Cache for parsed path segments to improve performance\r\nconst pathSegmentCache = new Map<string, string[]>();\r\n\r\nfunction parsePathSegments(key: string): string[] {\r\n // Check cache first\r\n const cached = pathSegmentCache.get(key);\r\n if (cached) return cached;\r\n\r\n // Convert bracket notation to dots, then split\r\n // a[b][c] -> a.b.c\r\n const dotted = key.replace(/\\]/g, \"\").replace(/\\[/g, \".\");\r\n const result = dotted.split(\".\").filter((s) => s.length > 0);\r\n\r\n // Cache the result (limit cache size)\r\n if (pathSegmentCache.size < 500) {\r\n pathSegmentCache.set(key, result);\r\n }\r\n\r\n return result;\r\n}\r\n\r\nfunction expandObjectPaths(\r\n obj: Record<string, unknown>,\r\n maxKeyLength?: number,\r\n): Record<string, unknown> {\r\n const result: Record<string, unknown> = {};\r\n for (const rawKey of Object.keys(obj)) {\r\n const safeKey = sanitizeKey(rawKey, maxKeyLength);\r\n if (!safeKey) continue;\r\n const value = obj[rawKey];\r\n\r\n // Recursively expand nested objects first\r\n const expandedValue = isPlainObject(value)\r\n ? expandObjectPaths(value as Record<string, unknown>, maxKeyLength)\r\n : value;\r\n\r\n if (safeKey.includes(\".\") || safeKey.includes(\"[\")) {\r\n const segments = parsePathSegments(safeKey);\r\n if (segments.length > 0) {\r\n setIn(result, segments, expandedValue);\r\n continue;\r\n }\r\n }\r\n result[safeKey] = expandedValue;\r\n }\r\n return result;\r\n}\r\n\r\nfunction setReqPropertySafe(target: Record<string, unknown>, key: string, value: unknown): void {\r\n try {\r\n const desc = Object.getOwnPropertyDescriptor(target, key);\r\n if (desc && desc.configurable === false && desc.writable === false) {\r\n // Non-configurable and not writable: skip\r\n return;\r\n }\r\n if (!desc || desc.configurable !== false) {\r\n Object.defineProperty(target, key, {\r\n value,\r\n writable: true,\r\n configurable: true,\r\n enumerable: true,\r\n });\r\n return;\r\n }\r\n } catch (_) {\r\n // fall back to assignment below\r\n }\r\n try {\r\n target[key] = value;\r\n } catch (_) {\r\n // last resort: skip if cannot assign\r\n }\r\n}\r\n\r\nfunction safeDeepClone<T>(input: T, maxKeyLength?: number, maxArrayLength?: number): T {\r\n if (Array.isArray(input)) {\r\n // Limit array length to prevent memory exhaustion\r\n const limit = maxArrayLength ?? 1000;\r\n const limited = input.slice(0, limit);\r\n return limited.map((v) => safeDeepClone(v, maxKeyLength, maxArrayLength)) as T;\r\n }\r\n if (isPlainObject(input)) {\r\n const out: Record<string, unknown> = {};\r\n for (const k of Object.keys(input)) {\r\n if (!sanitizeKey(k, maxKeyLength)) continue;\r\n out[k] = safeDeepClone((input as Record<string, unknown>)[k], maxKeyLength, maxArrayLength);\r\n }\r\n return out as T;\r\n }\r\n return input;\r\n}\r\n\r\nfunction mergeValues(values: unknown[], strategy: MergeStrategy): unknown {\r\n switch (strategy) {\r\n case \"keepFirst\":\r\n return values[0];\r\n case \"keepLast\":\r\n return values[values.length - 1];\r\n case \"combine\":\r\n return values.reduce<unknown[]>((acc, v) => {\r\n if (Array.isArray(v)) acc.push(...v);\r\n else acc.push(v);\r\n return acc;\r\n }, []);\r\n default:\r\n return values[values.length - 1];\r\n }\r\n}\r\n\r\nfunction isUrlEncodedContentType(req: any): boolean {\r\n const ct = String(req?.headers?.[\"content-type\"] || \"\").toLowerCase();\r\n return ct.startsWith(\"application/x-www-form-urlencoded\");\r\n}\r\n\r\nfunction shouldExcludePath(path: string | undefined, excludePaths: string[]): boolean {\r\n if (!path || excludePaths.length === 0) return false;\r\n const currentPath = path;\r\n for (const p of excludePaths) {\r\n if (p.endsWith(\"*\")) {\r\n if (currentPath.startsWith(p.slice(0, -1))) return true;\r\n } else if (currentPath === p) {\r\n return true;\r\n }\r\n }\r\n return false;\r\n}\r\n\r\nfunction normalizeWhitelist(whitelist?: string[] | string): string[] {\r\n if (!whitelist) return [];\r\n if (typeof whitelist === \"string\") return [whitelist];\r\n return whitelist.filter((w) => typeof w === \"string\");\r\n}\r\n\r\nfunction buildWhitelistHelpers(whitelist: string[]) {\r\n const exact = new Set(whitelist);\r\n const prefixes = whitelist.filter((w) => w.length > 0);\r\n // Pre-build a cache for commonly checked paths for performance\r\n const pathCache = new Map<string, boolean>();\r\n\r\n return {\r\n exact,\r\n prefixes,\r\n isWhitelistedPath(pathParts: string[]): boolean {\r\n if (pathParts.length === 0) return false;\r\n const full = pathParts.join(\".\");\r\n\r\n // Check cache first for performance\r\n const cached = pathCache.get(full);\r\n if (cached !== undefined) return cached;\r\n\r\n let result = false;\r\n\r\n // Exact match\r\n if (exact.has(full)) {\r\n result = true;\r\n }\r\n // Leaf match\r\n else if (exact.has(pathParts[pathParts.length - 1]!)) {\r\n result = true;\r\n }\r\n // Prefix match (treat any listed segment as prefix of a subtree)\r\n else {\r\n for (const p of prefixes) {\r\n if (full === p || full.startsWith(p + \".\")) {\r\n result = true;\r\n break;\r\n }\r\n }\r\n }\r\n\r\n // Cache the result (limit cache size to prevent memory issues)\r\n if (pathCache.size < 1000) {\r\n pathCache.set(full, result);\r\n }\r\n\r\n return result;\r\n },\r\n };\r\n}\r\n\r\nfunction setIn(target: Record<string, unknown>, path: string[], value: unknown): void {\r\n /* istanbul ignore if */\r\n if (path.length === 0) {\r\n return;\r\n }\r\n let cur: Record<string, unknown> = target;\r\n for (let i = 0; i < path.length - 1; i++) {\r\n const k = path[i]!;\r\n // Additional prototype pollution protection\r\n if (DANGEROUS_KEYS.has(k)) return;\r\n if (!isPlainObject(cur[k])) {\r\n // Create a new plain object to avoid pollution\r\n cur[k] = {};\r\n }\r\n cur = cur[k] as Record<string, unknown>;\r\n }\r\n const lastKey = path[path.length - 1]!;\r\n // Final check on the last key\r\n if (DANGEROUS_KEYS.has(lastKey)) return;\r\n cur[lastKey] = value;\r\n}\r\n\r\nfunction moveWhitelistedFromPolluted(\r\n reqPart: Record<string, unknown>,\r\n polluted: Record<string, unknown>,\r\n isWhitelisted: (path: string[]) => boolean,\r\n): void {\r\n function walk(node: Record<string, unknown>, path: string[] = []) {\r\n for (const k of Object.keys(node)) {\r\n const v = node[k];\r\n const curPath = [...path, k];\r\n if (isPlainObject(v)) {\r\n walk(v as Record<string, unknown>, curPath);\r\n // prune empty objects\r\n if (Object.keys(v as Record<string, unknown>).length === 0) {\r\n delete node[k];\r\n }\r\n } else {\r\n if (isWhitelisted(curPath)) {\r\n // put back into request\r\n const normalizedPath = curPath.flatMap((seg) =>\r\n seg.includes(\".\") ? seg.split(\".\") : [seg],\r\n );\r\n setIn(reqPart, normalizedPath, v);\r\n delete node[k];\r\n }\r\n }\r\n }\r\n }\r\n walk(polluted);\r\n}\r\n\r\nfunction detectAndReduce(\r\n input: Record<string, unknown>,\r\n opts: Required<\r\n Pick<\r\n SanitizeOptions,\r\n | \"mergeStrategy\"\r\n | \"maxDepth\"\r\n | \"maxKeys\"\r\n | \"maxArrayLength\"\r\n | \"maxKeyLength\"\r\n | \"trimValues\"\r\n | \"preserveNull\"\r\n >\r\n >,\r\n): SanitizedResult<Record<string, unknown>> {\r\n let keyCount = 0;\r\n const polluted: Record<string, unknown> = {};\r\n const pollutedKeys: string[] = [];\r\n\r\n function processNode(node: unknown, path: string[] = [], depth = 0): unknown {\r\n if (node === null || node === undefined) return opts.preserveNull ? node : node;\r\n\r\n if (Array.isArray(node)) {\r\n // Limit array length to prevent DoS\r\n const limit = opts.maxArrayLength ?? 1000;\r\n const limitedNode = node.slice(0, limit);\r\n\r\n const mapped = limitedNode.map((v) => processNode(v, path, depth));\r\n if (opts.mergeStrategy === \"combine\") {\r\n // combine: do not record pollution, but flatten using mergeValues\r\n return mergeValues(mapped, \"combine\");\r\n }\r\n // Other strategies: record pollution and reduce\r\n setIn(polluted, path, safeDeepClone(limitedNode, opts.maxKeyLength, opts.maxArrayLength));\r\n pollutedKeys.push(path.join(\".\"));\r\n const reduced = mergeValues(mapped, opts.mergeStrategy);\r\n return reduced;\r\n }\r\n\r\n if (isPlainObject(node)) {\r\n if (depth > opts.maxDepth)\r\n throw new Error(`Maximum object depth (${opts.maxDepth}) exceeded`);\r\n const out: Record<string, unknown> = {};\r\n for (const rawKey of Object.keys(node)) {\r\n keyCount++;\r\n if (keyCount > (opts.maxKeys ?? Number.MAX_SAFE_INTEGER)) {\r\n throw new Error(`Maximum key count (${opts.maxKeys}) exceeded`);\r\n }\r\n const safeKey = sanitizeKey(rawKey, opts.maxKeyLength);\r\n if (!safeKey) continue;\r\n const child = (node as Record<string, unknown>)[rawKey];\r\n const childPath = path.concat([safeKey]);\r\n let value = processNode(child, childPath, depth + 1);\r\n if (typeof value === \"string\" && opts.trimValues) value = value.trim();\r\n out[safeKey] = value;\r\n }\r\n return out;\r\n }\r\n\r\n return node;\r\n }\r\n\r\n const cloned = safeDeepClone(input, opts.maxKeyLength, opts.maxArrayLength);\r\n const cleaned = processNode(cloned, [], 0) as Record<string, unknown>;\r\n return { cleaned, pollutedTree: polluted, pollutedKeys };\r\n}\r\n\r\nexport function sanitize<T extends Record<string, unknown>>(\r\n input: T,\r\n options: SanitizeOptions = {},\r\n): T {\r\n // Normalize and expand keys prior to sanitization\r\n const maxKeyLength = options.maxKeyLength ?? 200;\r\n const expandedInput = isPlainObject(input) ? expandObjectPaths(input, maxKeyLength) : input;\r\n const whitelist = normalizeWhitelist(options.whitelist);\r\n const { isWhitelistedPath } = buildWhitelistHelpers(whitelist);\r\n const {\r\n mergeStrategy = DEFAULT_STRATEGY,\r\n maxDepth = 20,\r\n maxKeys = 5000,\r\n maxArrayLength = 1000,\r\n trimValues = false,\r\n preserveNull = true,\r\n } = options;\r\n\r\n // First: reduce arrays and collect polluted\r\n const { cleaned, pollutedTree } = detectAndReduce(expandedInput, {\r\n mergeStrategy,\r\n maxDepth,\r\n maxKeys,\r\n maxArrayLength,\r\n maxKeyLength,\r\n trimValues,\r\n preserveNull,\r\n });\r\n\r\n // Second: move back whitelisted arrays\r\n moveWhitelistedFromPolluted(cleaned, pollutedTree, isWhitelistedPath);\r\n\r\n return cleaned as T;\r\n}\r\n\r\ntype ExpressLikeNext = (err?: unknown) => void;\r\n\r\nfunction validateOptions(options: HppxOptions): void {\r\n if (\r\n options.maxDepth !== undefined &&\r\n (typeof options.maxDepth !== \"number\" || options.maxDepth < 1 || options.maxDepth > 100)\r\n ) {\r\n throw new TypeError(\"maxDepth must be a number between 1 and 100\");\r\n }\r\n if (\r\n options.maxKeys !== undefined &&\r\n (typeof options.maxKeys !== \"number\" || options.maxKeys < 1)\r\n ) {\r\n throw new TypeError(\"maxKeys must be a positive number\");\r\n }\r\n if (\r\n options.maxArrayLength !== undefined &&\r\n (typeof options.maxArrayLength !== \"number\" || options.maxArrayLength < 1)\r\n ) {\r\n throw new TypeError(\"maxArrayLength must be a positive number\");\r\n }\r\n if (\r\n options.maxKeyLength !== undefined &&\r\n (typeof options.maxKeyLength !== \"number\" ||\r\n options.maxKeyLength < 1 ||\r\n options.maxKeyLength > 1000)\r\n ) {\r\n throw new TypeError(\"maxKeyLength must be a number between 1 and 1000\");\r\n }\r\n if (\r\n options.mergeStrategy !== undefined &&\r\n ![\"keepFirst\", \"keepLast\", \"combine\"].includes(options.mergeStrategy)\r\n ) {\r\n throw new TypeError(\"mergeStrategy must be 'keepFirst', 'keepLast', or 'combine'\");\r\n }\r\n if (options.sources !== undefined && !Array.isArray(options.sources)) {\r\n throw new TypeError(\"sources must be an array\");\r\n }\r\n if (options.sources !== undefined) {\r\n for (const source of options.sources) {\r\n if (![\"query\", \"body\", \"params\"].includes(source)) {\r\n throw new TypeError(\"sources must only contain 'query', 'body', or 'params'\");\r\n }\r\n }\r\n }\r\n if (\r\n options.checkBodyContentType !== undefined &&\r\n ![\"urlencoded\", \"any\", \"none\"].includes(options.checkBodyContentType)\r\n ) {\r\n throw new TypeError(\"checkBodyContentType must be 'urlencoded', 'any', or 'none'\");\r\n }\r\n if (options.excludePaths !== undefined && !Array.isArray(options.excludePaths)) {\r\n throw new TypeError(\"excludePaths must be an array\");\r\n }\r\n}\r\n\r\nexport default function hppx(options: HppxOptions = {}) {\r\n // Validate options on middleware creation\r\n validateOptions(options);\r\n\r\n const {\r\n whitelist = [],\r\n mergeStrategy = DEFAULT_STRATEGY,\r\n sources = DEFAULT_SOURCES,\r\n checkBodyContentType = \"urlencoded\",\r\n excludePaths = [],\r\n maxDepth = 20,\r\n maxKeys = 5000,\r\n maxArrayLength = 1000,\r\n maxKeyLength = 200,\r\n trimValues = false,\r\n preserveNull = true,\r\n strict = false,\r\n onPollutionDetected,\r\n logger,\r\n logPollution = true,\r\n } = options;\r\n\r\n const whitelistArr = normalizeWhitelist(whitelist);\r\n const { isWhitelistedPath } = buildWhitelistHelpers(whitelistArr);\r\n\r\n return function hppxMiddleware(req: any, res: any, next: ExpressLikeNext) {\r\n try {\r\n if (shouldExcludePath(req?.path, excludePaths)) return next();\r\n\r\n let anyPollutionDetected = false;\r\n const allPollutedKeys: string[] = [];\r\n\r\n for (const source of sources) {\r\n /* istanbul ignore next */\r\n if (!req || typeof req !== \"object\") break;\r\n if (req[source] === undefined) continue;\r\n\r\n if (source === \"body\") {\r\n if (checkBodyContentType === \"none\") continue;\r\n if (checkBodyContentType === \"urlencoded\" && !isUrlEncodedContentType(req)) continue;\r\n }\r\n\r\n const part = req[source];\r\n if (!isPlainObject(part)) continue;\r\n\r\n // Preprocess: expand dotted and bracketed keys into nested objects\r\n const expandedPart = expandObjectPaths(part, maxKeyLength);\r\n\r\n const pollutedKey = `${source}Polluted`;\r\n const processedKey = `__hppxProcessed_${source}`;\r\n const hasProcessedBefore = Boolean(req[processedKey]);\r\n\r\n if (!hasProcessedBefore) {\r\n // First pass for this request part: reduce arrays and collect polluted\r\n const { cleaned, pollutedTree, pollutedKeys } = detectAndReduce(expandedPart, {\r\n mergeStrategy,\r\n maxDepth,\r\n maxKeys,\r\n maxArrayLength,\r\n maxKeyLength,\r\n trimValues,\r\n preserveNull,\r\n });\r\n\r\n setReqPropertySafe(req, source, cleaned);\r\n\r\n // Attach polluted object (always present as {} when source processed)\r\n setReqPropertySafe(req, pollutedKey, pollutedTree);\r\n req[processedKey] = true;\r\n\r\n // Apply whitelist now: move whitelisted arrays back\r\n const sourceData = req[source];\r\n const pollutedData = req[pollutedKey];\r\n if (isPlainObject(sourceData) && isPlainObject(pollutedData)) {\r\n moveWhitelistedFromPolluted(sourceData, pollutedData, isWhitelistedPath);\r\n }\r\n\r\n if (pollutedKeys.length > 0) {\r\n anyPollutionDetected = true;\r\n for (const k of pollutedKeys) allPollutedKeys.push(`${source}.${k}`);\r\n }\r\n } else {\r\n // Subsequent middleware: only put back whitelisted entries\r\n const sourceData = req[source];\r\n const pollutedData = req[pollutedKey];\r\n if (isPlainObject(sourceData) && isPlainObject(pollutedData)) {\r\n moveWhitelistedFromPolluted(sourceData, pollutedData, isWhitelistedPath);\r\n }\r\n // pollution already accounted for in previous pass\r\n }\r\n }\r\n\r\n if (anyPollutionDetected) {\r\n // Log pollution detection if enabled\r\n if (logPollution) {\r\n const logMessage = `[hppx] HTTP Parameter Pollution detected - ${allPollutedKeys.length} parameter(s) affected: ${allPollutedKeys.join(\", \")}`;\r\n if (logger) {\r\n try {\r\n logger(logMessage);\r\n } catch (_) {\r\n // Fallback to console.warn if logger fails\r\n console.warn(logMessage);\r\n }\r\n } else {\r\n console.warn(logMessage);\r\n }\r\n }\r\n\r\n if (onPollutionDetected) {\r\n try {\r\n // Determine which sources had pollution\r\n for (const source of sources) {\r\n const pollutedKey = `${source}Polluted`;\r\n const pollutedData = req[pollutedKey];\r\n if (pollutedData && Object.keys(pollutedData).length > 0) {\r\n const sourcePollutedKeys = allPollutedKeys.filter((k) =>\r\n k.startsWith(`${source}.`),\r\n );\r\n if (sourcePollutedKeys.length > 0) {\r\n onPollutionDetected(req, {\r\n source: source,\r\n pollutedKeys: sourcePollutedKeys,\r\n });\r\n }\r\n }\r\n }\r\n } catch (_) {\r\n /* ignore user callback errors */\r\n }\r\n }\r\n if (strict && res && typeof res.status === \"function\") {\r\n return res.status(400).json({\r\n error: \"Bad Request\",\r\n message: \"HTTP Parameter Pollution detected\",\r\n pollutedParameters: allPollutedKeys,\r\n code: \"HPP_DETECTED\",\r\n });\r\n }\r\n }\r\n\r\n return next();\r\n } catch (err) {\r\n // Enhanced error handling with detailed logging\r\n const error = err instanceof Error ? err : new Error(String(err));\r\n\r\n if (logger) {\r\n try {\r\n logger(error);\r\n } catch (logErr) {\r\n // If custom logger fails, use console.error as fallback in development\r\n if (process.env.NODE_ENV !== \"production\") {\r\n console.error(\"[hppx] Logger failed:\", logErr);\r\n console.error(\"[hppx] Original error:\", error);\r\n }\r\n }\r\n }\r\n\r\n // Pass error to next middleware for proper error handling\r\n return next(error);\r\n }\r\n };\r\n}\r\n\r\nexport { DANGEROUS_KEYS, DEFAULT_STRATEGY, DEFAULT_SOURCES };\r\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hppx",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Superior HTTP Parameter Pollution protection middleware with modern TypeScript, robust sanitizer, and extensive tests.",
5
5
  "license": "MIT",
6
6
  "author": "Hiprax",
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\r\n * hppx — Superior HTTP Parameter Pollution protection middleware\r\n *\r\n * - Protects against parameter and prototype pollution\r\n * - Supports nested whitelists via dot-notation and leaf matching\r\n * - Merge strategies: keepFirst | keepLast | combine\r\n * - Multiple middleware compatibility: arrays are \"put aside\" once and selectively restored\r\n * - Exposes req.queryPolluted / req.bodyPolluted / req.paramsPolluted\r\n * - TypeScript-first API\r\n */\r\n\r\nexport type RequestSource = \"query\" | \"body\" | \"params\";\r\nexport type MergeStrategy = \"keepFirst\" | \"keepLast\" | \"combine\";\r\n\r\nexport interface SanitizeOptions {\r\n whitelist?: string[] | string;\r\n mergeStrategy?: MergeStrategy;\r\n maxDepth?: number;\r\n maxKeys?: number;\r\n maxArrayLength?: number;\r\n maxKeyLength?: number;\r\n trimValues?: boolean;\r\n preserveNull?: boolean;\r\n}\r\n\r\nexport interface HppxOptions extends SanitizeOptions {\r\n sources?: RequestSource[];\r\n /** When to process req.body */\r\n checkBodyContentType?: \"urlencoded\" | \"any\" | \"none\";\r\n excludePaths?: string[];\r\n strict?: boolean;\r\n onPollutionDetected?: (\r\n req: Record<string, unknown>,\r\n info: { source: RequestSource; pollutedKeys: string[] },\r\n ) => void;\r\n logger?: (err: Error | unknown) => void;\r\n}\r\n\r\nexport interface SanitizedResult<T> {\r\n cleaned: T;\r\n pollutedTree: Record<string, unknown>;\r\n pollutedKeys: string[];\r\n}\r\n\r\nconst DEFAULT_SOURCES: RequestSource[] = [\"query\", \"body\", \"params\"];\r\nconst DEFAULT_STRATEGY: MergeStrategy = \"keepLast\";\r\nconst DANGEROUS_KEYS = new Set([\"__proto__\", \"prototype\", \"constructor\"]);\r\n\r\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\r\n if (value === null || typeof value !== \"object\") return false;\r\n const proto = Object.getPrototypeOf(value);\r\n return proto === Object.prototype || proto === null;\r\n}\r\n\r\nfunction sanitizeKey(key: string, maxKeyLength?: number): string | null {\r\n /* istanbul ignore next */ if (typeof key !== \"string\") return null;\r\n if (DANGEROUS_KEYS.has(key)) return null;\r\n if (key.includes(\"\\u0000\")) return null;\r\n // Prevent excessively long keys that could cause DoS\r\n const maxLen = maxKeyLength ?? 200;\r\n if (key.length > maxLen) return null;\r\n // Prevent keys that are only dots or brackets (malformed) - but allow single dot as it's valid\r\n if (key.length > 1 && /^[.\\[\\]]+$/.test(key)) return null;\r\n return key;\r\n}\r\n\r\n// Cache for parsed path segments to improve performance\r\nconst pathSegmentCache = new Map<string, string[]>();\r\n\r\nfunction parsePathSegments(key: string): string[] {\r\n // Check cache first\r\n const cached = pathSegmentCache.get(key);\r\n if (cached) return cached;\r\n\r\n // Convert bracket notation to dots, then split\r\n // a[b][c] -> a.b.c\r\n const dotted = key.replace(/\\]/g, \"\").replace(/\\[/g, \".\");\r\n const result = dotted.split(\".\").filter((s) => s.length > 0);\r\n\r\n // Cache the result (limit cache size)\r\n if (pathSegmentCache.size < 500) {\r\n pathSegmentCache.set(key, result);\r\n }\r\n\r\n return result;\r\n}\r\n\r\nfunction expandObjectPaths(\r\n obj: Record<string, unknown>,\r\n maxKeyLength?: number,\r\n): Record<string, unknown> {\r\n const result: Record<string, unknown> = {};\r\n for (const rawKey of Object.keys(obj)) {\r\n const safeKey = sanitizeKey(rawKey, maxKeyLength);\r\n if (!safeKey) continue;\r\n const value = obj[rawKey];\r\n\r\n // Recursively expand nested objects first\r\n const expandedValue = isPlainObject(value)\r\n ? expandObjectPaths(value as Record<string, unknown>, maxKeyLength)\r\n : value;\r\n\r\n if (safeKey.includes(\".\") || safeKey.includes(\"[\")) {\r\n const segments = parsePathSegments(safeKey);\r\n if (segments.length > 0) {\r\n setIn(result, segments, expandedValue);\r\n continue;\r\n }\r\n }\r\n result[safeKey] = expandedValue;\r\n }\r\n return result;\r\n}\r\n\r\nfunction setReqPropertySafe(target: Record<string, unknown>, key: string, value: unknown): void {\r\n try {\r\n const desc = Object.getOwnPropertyDescriptor(target, key);\r\n if (desc && desc.configurable === false && desc.writable === false) {\r\n // Non-configurable and not writable: skip\r\n return;\r\n }\r\n if (!desc || desc.configurable !== false) {\r\n Object.defineProperty(target, key, {\r\n value,\r\n writable: true,\r\n configurable: true,\r\n enumerable: true,\r\n });\r\n return;\r\n }\r\n } catch (_) {\r\n // fall back to assignment below\r\n }\r\n try {\r\n target[key] = value;\r\n } catch (_) {\r\n // last resort: skip if cannot assign\r\n }\r\n}\r\n\r\nfunction safeDeepClone<T>(input: T, maxKeyLength?: number, maxArrayLength?: number): T {\r\n if (Array.isArray(input)) {\r\n // Limit array length to prevent memory exhaustion\r\n const limit = maxArrayLength ?? 1000;\r\n const limited = input.slice(0, limit);\r\n return limited.map((v) => safeDeepClone(v, maxKeyLength, maxArrayLength)) as T;\r\n }\r\n if (isPlainObject(input)) {\r\n const out: Record<string, unknown> = {};\r\n for (const k of Object.keys(input)) {\r\n if (!sanitizeKey(k, maxKeyLength)) continue;\r\n out[k] = safeDeepClone((input as Record<string, unknown>)[k], maxKeyLength, maxArrayLength);\r\n }\r\n return out as T;\r\n }\r\n return input;\r\n}\r\n\r\nfunction mergeValues(values: unknown[], strategy: MergeStrategy): unknown {\r\n switch (strategy) {\r\n case \"keepFirst\":\r\n return values[0];\r\n case \"keepLast\":\r\n return values[values.length - 1];\r\n case \"combine\":\r\n return values.reduce<unknown[]>((acc, v) => {\r\n if (Array.isArray(v)) acc.push(...v);\r\n else acc.push(v);\r\n return acc;\r\n }, []);\r\n default:\r\n return values[values.length - 1];\r\n }\r\n}\r\n\r\nfunction isUrlEncodedContentType(req: any): boolean {\r\n const ct = String(req?.headers?.[\"content-type\"] || \"\").toLowerCase();\r\n return ct.startsWith(\"application/x-www-form-urlencoded\");\r\n}\r\n\r\nfunction shouldExcludePath(path: string | undefined, excludePaths: string[]): boolean {\r\n if (!path || excludePaths.length === 0) return false;\r\n const currentPath = path;\r\n for (const p of excludePaths) {\r\n if (p.endsWith(\"*\")) {\r\n if (currentPath.startsWith(p.slice(0, -1))) return true;\r\n } else if (currentPath === p) {\r\n return true;\r\n }\r\n }\r\n return false;\r\n}\r\n\r\nfunction normalizeWhitelist(whitelist?: string[] | string): string[] {\r\n if (!whitelist) return [];\r\n if (typeof whitelist === \"string\") return [whitelist];\r\n return whitelist.filter((w) => typeof w === \"string\");\r\n}\r\n\r\nfunction buildWhitelistHelpers(whitelist: string[]) {\r\n const exact = new Set(whitelist);\r\n const prefixes = whitelist.filter((w) => w.length > 0);\r\n // Pre-build a cache for commonly checked paths for performance\r\n const pathCache = new Map<string, boolean>();\r\n\r\n return {\r\n exact,\r\n prefixes,\r\n isWhitelistedPath(pathParts: string[]): boolean {\r\n if (pathParts.length === 0) return false;\r\n const full = pathParts.join(\".\");\r\n\r\n // Check cache first for performance\r\n const cached = pathCache.get(full);\r\n if (cached !== undefined) return cached;\r\n\r\n let result = false;\r\n\r\n // Exact match\r\n if (exact.has(full)) {\r\n result = true;\r\n }\r\n // Leaf match\r\n else if (exact.has(pathParts[pathParts.length - 1]!)) {\r\n result = true;\r\n }\r\n // Prefix match (treat any listed segment as prefix of a subtree)\r\n else {\r\n for (const p of prefixes) {\r\n if (full === p || full.startsWith(p + \".\")) {\r\n result = true;\r\n break;\r\n }\r\n }\r\n }\r\n\r\n // Cache the result (limit cache size to prevent memory issues)\r\n if (pathCache.size < 1000) {\r\n pathCache.set(full, result);\r\n }\r\n\r\n return result;\r\n },\r\n };\r\n}\r\n\r\nfunction setIn(target: Record<string, unknown>, path: string[], value: unknown): void {\r\n /* istanbul ignore if */\r\n if (path.length === 0) {\r\n return;\r\n }\r\n let cur: Record<string, unknown> = target;\r\n for (let i = 0; i < path.length - 1; i++) {\r\n const k = path[i]!;\r\n // Additional prototype pollution protection\r\n if (DANGEROUS_KEYS.has(k)) return;\r\n if (!isPlainObject(cur[k])) {\r\n // Create a new plain object to avoid pollution\r\n cur[k] = {};\r\n }\r\n cur = cur[k] as Record<string, unknown>;\r\n }\r\n const lastKey = path[path.length - 1]!;\r\n // Final check on the last key\r\n if (DANGEROUS_KEYS.has(lastKey)) return;\r\n cur[lastKey] = value;\r\n}\r\n\r\nfunction moveWhitelistedFromPolluted(\r\n reqPart: Record<string, unknown>,\r\n polluted: Record<string, unknown>,\r\n isWhitelisted: (path: string[]) => boolean,\r\n): void {\r\n function walk(node: Record<string, unknown>, path: string[] = []) {\r\n for (const k of Object.keys(node)) {\r\n const v = node[k];\r\n const curPath = [...path, k];\r\n if (isPlainObject(v)) {\r\n walk(v as Record<string, unknown>, curPath);\r\n // prune empty objects\r\n if (Object.keys(v as Record<string, unknown>).length === 0) {\r\n delete node[k];\r\n }\r\n } else {\r\n if (isWhitelisted(curPath)) {\r\n // put back into request\r\n const normalizedPath = curPath.flatMap((seg) =>\r\n seg.includes(\".\") ? seg.split(\".\") : [seg],\r\n );\r\n setIn(reqPart, normalizedPath, v);\r\n delete node[k];\r\n }\r\n }\r\n }\r\n }\r\n walk(polluted);\r\n}\r\n\r\nfunction detectAndReduce(\r\n input: Record<string, unknown>,\r\n opts: Required<\r\n Pick<\r\n SanitizeOptions,\r\n | \"mergeStrategy\"\r\n | \"maxDepth\"\r\n | \"maxKeys\"\r\n | \"maxArrayLength\"\r\n | \"maxKeyLength\"\r\n | \"trimValues\"\r\n | \"preserveNull\"\r\n >\r\n >,\r\n): SanitizedResult<Record<string, unknown>> {\r\n let keyCount = 0;\r\n const polluted: Record<string, unknown> = {};\r\n const pollutedKeys: string[] = [];\r\n\r\n function processNode(node: unknown, path: string[] = [], depth = 0): unknown {\r\n if (node === null || node === undefined) return opts.preserveNull ? node : node;\r\n\r\n if (Array.isArray(node)) {\r\n // Limit array length to prevent DoS\r\n const limit = opts.maxArrayLength ?? 1000;\r\n const limitedNode = node.slice(0, limit);\r\n\r\n const mapped = limitedNode.map((v) => processNode(v, path, depth));\r\n if (opts.mergeStrategy === \"combine\") {\r\n // combine: do not record pollution, but flatten using mergeValues\r\n return mergeValues(mapped, \"combine\");\r\n }\r\n // Other strategies: record pollution and reduce\r\n setIn(polluted, path, safeDeepClone(limitedNode, opts.maxKeyLength, opts.maxArrayLength));\r\n pollutedKeys.push(path.join(\".\"));\r\n const reduced = mergeValues(mapped, opts.mergeStrategy);\r\n return reduced;\r\n }\r\n\r\n if (isPlainObject(node)) {\r\n if (depth > opts.maxDepth)\r\n throw new Error(`Maximum object depth (${opts.maxDepth}) exceeded`);\r\n const out: Record<string, unknown> = {};\r\n for (const rawKey of Object.keys(node)) {\r\n keyCount++;\r\n if (keyCount > (opts.maxKeys ?? Number.MAX_SAFE_INTEGER)) {\r\n throw new Error(`Maximum key count (${opts.maxKeys}) exceeded`);\r\n }\r\n const safeKey = sanitizeKey(rawKey, opts.maxKeyLength);\r\n if (!safeKey) continue;\r\n const child = (node as Record<string, unknown>)[rawKey];\r\n const childPath = path.concat([safeKey]);\r\n let value = processNode(child, childPath, depth + 1);\r\n if (typeof value === \"string\" && opts.trimValues) value = value.trim();\r\n out[safeKey] = value;\r\n }\r\n return out;\r\n }\r\n\r\n return node;\r\n }\r\n\r\n const cloned = safeDeepClone(input, opts.maxKeyLength, opts.maxArrayLength);\r\n const cleaned = processNode(cloned, [], 0) as Record<string, unknown>;\r\n return { cleaned, pollutedTree: polluted, pollutedKeys };\r\n}\r\n\r\nexport function sanitize<T extends Record<string, unknown>>(\r\n input: T,\r\n options: SanitizeOptions = {},\r\n): T {\r\n // Normalize and expand keys prior to sanitization\r\n const maxKeyLength = options.maxKeyLength ?? 200;\r\n const expandedInput = isPlainObject(input) ? expandObjectPaths(input, maxKeyLength) : input;\r\n const whitelist = normalizeWhitelist(options.whitelist);\r\n const { isWhitelistedPath } = buildWhitelistHelpers(whitelist);\r\n const {\r\n mergeStrategy = DEFAULT_STRATEGY,\r\n maxDepth = 20,\r\n maxKeys = 5000,\r\n maxArrayLength = 1000,\r\n trimValues = false,\r\n preserveNull = true,\r\n } = options;\r\n\r\n // First: reduce arrays and collect polluted\r\n const { cleaned, pollutedTree } = detectAndReduce(expandedInput, {\r\n mergeStrategy,\r\n maxDepth,\r\n maxKeys,\r\n maxArrayLength,\r\n maxKeyLength,\r\n trimValues,\r\n preserveNull,\r\n });\r\n\r\n // Second: move back whitelisted arrays\r\n moveWhitelistedFromPolluted(cleaned, pollutedTree, isWhitelistedPath);\r\n\r\n return cleaned as T;\r\n}\r\n\r\ntype ExpressLikeNext = (err?: unknown) => void;\r\n\r\nfunction validateOptions(options: HppxOptions): void {\r\n if (\r\n options.maxDepth !== undefined &&\r\n (typeof options.maxDepth !== \"number\" || options.maxDepth < 1 || options.maxDepth > 100)\r\n ) {\r\n throw new TypeError(\"maxDepth must be a number between 1 and 100\");\r\n }\r\n if (\r\n options.maxKeys !== undefined &&\r\n (typeof options.maxKeys !== \"number\" || options.maxKeys < 1)\r\n ) {\r\n throw new TypeError(\"maxKeys must be a positive number\");\r\n }\r\n if (\r\n options.maxArrayLength !== undefined &&\r\n (typeof options.maxArrayLength !== \"number\" || options.maxArrayLength < 1)\r\n ) {\r\n throw new TypeError(\"maxArrayLength must be a positive number\");\r\n }\r\n if (\r\n options.maxKeyLength !== undefined &&\r\n (typeof options.maxKeyLength !== \"number\" ||\r\n options.maxKeyLength < 1 ||\r\n options.maxKeyLength > 1000)\r\n ) {\r\n throw new TypeError(\"maxKeyLength must be a number between 1 and 1000\");\r\n }\r\n if (\r\n options.mergeStrategy !== undefined &&\r\n ![\"keepFirst\", \"keepLast\", \"combine\"].includes(options.mergeStrategy)\r\n ) {\r\n throw new TypeError(\"mergeStrategy must be 'keepFirst', 'keepLast', or 'combine'\");\r\n }\r\n if (options.sources !== undefined && !Array.isArray(options.sources)) {\r\n throw new TypeError(\"sources must be an array\");\r\n }\r\n if (options.sources !== undefined) {\r\n for (const source of options.sources) {\r\n if (![\"query\", \"body\", \"params\"].includes(source)) {\r\n throw new TypeError(\"sources must only contain 'query', 'body', or 'params'\");\r\n }\r\n }\r\n }\r\n if (\r\n options.checkBodyContentType !== undefined &&\r\n ![\"urlencoded\", \"any\", \"none\"].includes(options.checkBodyContentType)\r\n ) {\r\n throw new TypeError(\"checkBodyContentType must be 'urlencoded', 'any', or 'none'\");\r\n }\r\n if (options.excludePaths !== undefined && !Array.isArray(options.excludePaths)) {\r\n throw new TypeError(\"excludePaths must be an array\");\r\n }\r\n}\r\n\r\nexport default function hppx(options: HppxOptions = {}) {\r\n // Validate options on middleware creation\r\n validateOptions(options);\r\n\r\n const {\r\n whitelist = [],\r\n mergeStrategy = DEFAULT_STRATEGY,\r\n sources = DEFAULT_SOURCES,\r\n checkBodyContentType = \"urlencoded\",\r\n excludePaths = [],\r\n maxDepth = 20,\r\n maxKeys = 5000,\r\n maxArrayLength = 1000,\r\n maxKeyLength = 200,\r\n trimValues = false,\r\n preserveNull = true,\r\n strict = false,\r\n onPollutionDetected,\r\n logger,\r\n } = options;\r\n\r\n const whitelistArr = normalizeWhitelist(whitelist);\r\n const { isWhitelistedPath } = buildWhitelistHelpers(whitelistArr);\r\n\r\n return function hppxMiddleware(req: any, res: any, next: ExpressLikeNext) {\r\n try {\r\n if (shouldExcludePath(req?.path, excludePaths)) return next();\r\n\r\n let anyPollutionDetected = false;\r\n const allPollutedKeys: string[] = [];\r\n\r\n for (const source of sources) {\r\n /* istanbul ignore next */\r\n if (!req || typeof req !== \"object\") break;\r\n if (req[source] === undefined) continue;\r\n\r\n if (source === \"body\") {\r\n if (checkBodyContentType === \"none\") continue;\r\n if (checkBodyContentType === \"urlencoded\" && !isUrlEncodedContentType(req)) continue;\r\n }\r\n\r\n const part = req[source];\r\n if (!isPlainObject(part)) continue;\r\n\r\n // Preprocess: expand dotted and bracketed keys into nested objects\r\n const expandedPart = expandObjectPaths(part, maxKeyLength);\r\n\r\n const pollutedKey = `${source}Polluted`;\r\n const processedKey = `__hppxProcessed_${source}`;\r\n const hasProcessedBefore = Boolean(req[processedKey]);\r\n\r\n if (!hasProcessedBefore) {\r\n // First pass for this request part: reduce arrays and collect polluted\r\n const { cleaned, pollutedTree, pollutedKeys } = detectAndReduce(expandedPart, {\r\n mergeStrategy,\r\n maxDepth,\r\n maxKeys,\r\n maxArrayLength,\r\n maxKeyLength,\r\n trimValues,\r\n preserveNull,\r\n });\r\n\r\n setReqPropertySafe(req, source, cleaned);\r\n\r\n // Attach polluted object (always present as {} when source processed)\r\n setReqPropertySafe(req, pollutedKey, pollutedTree);\r\n req[processedKey] = true;\r\n\r\n // Apply whitelist now: move whitelisted arrays back\r\n const sourceData = req[source];\r\n const pollutedData = req[pollutedKey];\r\n if (isPlainObject(sourceData) && isPlainObject(pollutedData)) {\r\n moveWhitelistedFromPolluted(sourceData, pollutedData, isWhitelistedPath);\r\n }\r\n\r\n if (pollutedKeys.length > 0) {\r\n anyPollutionDetected = true;\r\n for (const k of pollutedKeys) allPollutedKeys.push(`${source}.${k}`);\r\n }\r\n } else {\r\n // Subsequent middleware: only put back whitelisted entries\r\n const sourceData = req[source];\r\n const pollutedData = req[pollutedKey];\r\n if (isPlainObject(sourceData) && isPlainObject(pollutedData)) {\r\n moveWhitelistedFromPolluted(sourceData, pollutedData, isWhitelistedPath);\r\n }\r\n // pollution already accounted for in previous pass\r\n }\r\n }\r\n\r\n if (anyPollutionDetected) {\r\n if (onPollutionDetected) {\r\n try {\r\n // Determine which sources had pollution\r\n for (const source of sources) {\r\n const pollutedKey = `${source}Polluted`;\r\n const pollutedData = req[pollutedKey];\r\n if (pollutedData && Object.keys(pollutedData).length > 0) {\r\n const sourcePollutedKeys = allPollutedKeys.filter((k) =>\r\n k.startsWith(`${source}.`),\r\n );\r\n if (sourcePollutedKeys.length > 0) {\r\n onPollutionDetected(req, {\r\n source: source,\r\n pollutedKeys: sourcePollutedKeys,\r\n });\r\n }\r\n }\r\n }\r\n } catch (_) {\r\n /* ignore user callback errors */\r\n }\r\n }\r\n if (strict && res && typeof res.status === \"function\") {\r\n return res.status(400).json({\r\n error: \"Bad Request\",\r\n message: \"HTTP Parameter Pollution detected\",\r\n pollutedParameters: allPollutedKeys,\r\n code: \"HPP_DETECTED\",\r\n });\r\n }\r\n }\r\n\r\n return next();\r\n } catch (err) {\r\n // Enhanced error handling with detailed logging\r\n const error = err instanceof Error ? err : new Error(String(err));\r\n\r\n if (logger) {\r\n try {\r\n logger(error);\r\n } catch (logErr) {\r\n // If custom logger fails, use console.error as fallback in development\r\n if (process.env.NODE_ENV !== \"production\") {\r\n console.error(\"[hppx] Logger failed:\", logErr);\r\n console.error(\"[hppx] Original error:\", error);\r\n }\r\n }\r\n }\r\n\r\n // Pass error to next middleware for proper error handling\r\n return next(error);\r\n }\r\n };\r\n}\r\n\r\nexport { DANGEROUS_KEYS, DEFAULT_STRATEGY, DEFAULT_SOURCES };\r\n"],"mappings":"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","names":[]}