hppx
v0.1.10
Published
Superior HTTP Parameter Pollution protection middleware with modern TypeScript, robust sanitizer, and extensive tests.
Maintainers
Readme
hppx
Superior HTTP Parameter Pollution protection middleware for Node.js/Express, written in TypeScript. It sanitizes req.query, req.body, and req.params, blocks prototype-pollution keys, supports nested whitelists, multiple merge strategies, and plays nicely with stacked middlewares.
Features
- Zero runtime dependencies — minimal attack surface and bundle size
- Multiple merge strategies —
keepFirst,keepLast(default),combine - Enhanced security:
- Blocks dangerous keys:
__proto__,prototype,constructor - Prevents null-byte injection in keys
- Rejects malformed keys (dot/bracket-only patterns)
- Validates key lengths to prevent DoS attacks
- Limits array sizes to prevent memory exhaustion
- Blocks dangerous keys:
- Flexible whitelisting — nested whitelist with dot-notation and leaf matching
- Pollution tracking — records polluted parameters on the request (
queryPolluted,bodyPolluted,paramsPolluted) - Multi-middleware support — works with multiple middlewares on different routes (whitelists applied incrementally)
- DoS protection —
maxDepth,maxKeys,maxArrayLength,maxKeyLength - Performance optimized — path caching and Set-based lookups for fast whitelist checks
- Fully typed API — TypeScript-first with comprehensive type definitions for both ESM and CommonJS
Installation
npm install hppxQuick Start
ESM (ES Modules)
import express from "express";
import hppx from "hppx";
const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(
hppx({
whitelist: ["tags", "user.roles", "ids"],
mergeStrategy: "keepLast",
sources: ["query", "body"],
}),
);
app.get("/search", (req, res) => {
res.json({
query: req.query,
queryPolluted: req.queryPolluted ?? {},
body: req.body ?? {},
bodyPolluted: req.bodyPolluted ?? {},
});
});CommonJS
const express = require("express");
const hppx = require("hppx");
const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(
hppx({
whitelist: ["tags", "user.roles", "ids"],
mergeStrategy: "keepLast",
sources: ["query", "body"],
}),
);
app.get("/search", (req, res) => {
res.json({
query: req.query,
queryPolluted: req.queryPolluted ?? {},
body: req.body ?? {},
bodyPolluted: req.bodyPolluted ?? {},
});
});API
Default Export: hppx(options?: HppxOptions)
Creates an Express-compatible middleware. Applies sanitization to each selected source and exposes *.Polluted objects on the request.
Note: Invalid options throw a
TypeErrorat middleware creation time, not at request time. This ensures misconfiguration is caught early.
Options
Whitelist & Strategy:
| Option | Type | Default | Description |
| --------------- | ---------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| whitelist | string[] \| string | [] | Keys allowed to remain as arrays. Supports dot-notation ("user.tags") and leaf matching ("tags" matches any path ending in tags). |
| mergeStrategy | 'keepFirst' \| 'keepLast' \| 'combine' | 'keepLast' | How to reduce duplicate/array parameters when not whitelisted. keepFirst takes the first value, keepLast takes the last, combine flattens all values into a single array. |
Source Selection:
| Option | Type | Default | Description |
| ---------------------- | -------------------------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| sources | Array<'query' \| 'body' \| 'params'> | ['query', 'body', 'params'] | Which request parts to sanitize. |
| checkBodyContentType | 'urlencoded' \| 'any' \| 'none' | 'urlencoded' | When to process req.body. urlencoded only processes URL-encoded bodies, any processes all content types, none skips body processing entirely. |
| excludePaths | string[] | [] | Paths to exclude from sanitization. Supports * wildcard suffix (e.g., "/assets*"). |
Security Limits (DoS Protection):
| Option | Type | Default | Range | Description |
| ---------------- | -------- | ------- | -------- | ------------------------------------------------------------------------------------- |
| maxDepth | number | 20 | 1 - 100 | Maximum object nesting depth. Exceeding this throws an error passed to next(). |
| maxKeys | number | 5000 | >= 1 | Maximum number of keys to process. Exceeding this throws an error passed to next(). |
| maxArrayLength | number | 1000 | >= 1 | Maximum array length. Arrays are truncated before processing. |
| maxKeyLength | number | 200 | 1 - 1000 | Maximum key string length. Longer keys are silently dropped. |
Behavior & Callbacks:
| Option | Type | Default | Description |
| --------------------- | --------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| trimValues | boolean | false | Trim whitespace from string values. |
| preserveNull | boolean | true | Preserve null values in the output. |
| strict | boolean | false | Immediately respond with HTTP 400 when pollution is detected. Response includes error, message, pollutedParameters, and code ("HPP_DETECTED") fields. |
| onPollutionDetected | (req, info) => void | — | Callback fired on pollution detection. Called once per polluted source (e.g., fires twice if both query and body are polluted). info contains { source: RequestSource, pollutedKeys: string[] }. |
| logger | (err: Error \| unknown) => void | — | Custom logger for errors and pollution warnings. Receives string for pollution warnings and Error for caught errors. Falls back to console.warn/console.error if the logger throws. |
| logPollution | boolean | true | Enable automatic logging when pollution is detected. |
Named Export: sanitize(input, options?)
function sanitize<T extends Record<string, unknown>>(input: T, options?: SanitizeOptions): T;Sanitize a plain object using the same rules as the middleware. Returns only the cleaned object (polluted data is not returned — use the middleware if you need req.queryPolluted etc.).
ESM:
import { sanitize } from "hppx";
const clean = sanitize(payload, {
whitelist: ["user.tags"],
mergeStrategy: "keepFirst",
});CommonJS:
const { sanitize } = require("hppx");
const clean = sanitize(payload, {
whitelist: ["user.tags"],
mergeStrategy: "keepFirst",
});Exported Types
All types are available for both ESM and CommonJS consumers:
import type {
RequestSource, // "query" | "body" | "params"
MergeStrategy, // "keepFirst" | "keepLast" | "combine"
SanitizeOptions, // Options for sanitize()
HppxOptions, // Full middleware options (extends SanitizeOptions)
SanitizedResult, // { cleaned, pollutedTree, pollutedKeys }
} from "hppx";Exported Constants
import { DANGEROUS_KEYS, DEFAULT_SOURCES, DEFAULT_STRATEGY } from "hppx";
DANGEROUS_KEYS; // Set<string> — {"__proto__", "prototype", "constructor"}
DEFAULT_SOURCES; // ["query", "body", "params"]
DEFAULT_STRATEGY; // "keepLast"Advanced Usage
Strict Mode (Respond 400 on Pollution)
app.use(hppx({ strict: true }));
// Polluted requests receive:
// {
// "error": "Bad Request",
// "message": "HTTP Parameter Pollution detected",
// "pollutedParameters": ["query.x"],
// "code": "HPP_DETECTED"
// }Process JSON Bodies Too
app.use(express.json());
app.use(hppx({ checkBodyContentType: "any" }));Exclude Specific Paths
app.use(hppx({ excludePaths: ["/public", "/assets*"] }));Custom Logging
// Use your application's logger
app.use(
hppx({
logger: (msg) => {
if (typeof msg === "string") {
myLogger.warn(msg); // Pollution warnings
} else {
myLogger.error(msg); // Errors
}
},
}),
);
// Disable automatic pollution logging
app.use(hppx({ logPollution: false }));Multi-Middleware Stacking
hppx supports incremental whitelisting across multiple middleware instances. Each subsequent middleware applies its own whitelist to the already-collected polluted data:
// Global middleware — whitelist "a"
app.use(hppx({ whitelist: ["a"] }));
// Route-level middleware — additionally whitelist "b" and "c"
const router = express.Router();
router.use(hppx({ whitelist: ["b", "c"] }));
// On this route, "a", "b", and "c" are all allowed as arrays
router.get("/data", (req, res) => {
res.json({ query: req.query });
});
app.use("/api", router);Pollution Detection Callback
app.use(
hppx({
onPollutionDetected: (req, info) => {
// Called once per polluted source (query, body, params)
securityLogger.warn("HPP detected", {
source: info.source,
pollutedKeys: info.pollutedKeys,
});
},
}),
);Security
What hppx Protects Against
| Threat | Protection |
| ------------------------ | ---------------------------------------------------------------------------------- |
| Parameter pollution | Duplicate parameters are reduced to a single value via the chosen merge strategy |
| Prototype pollution | __proto__, constructor, prototype keys are blocked at every processing level |
| DoS via deep nesting | maxDepth limit throws error on excessive nesting |
| DoS via key flooding | maxKeys limit throws error when key count is exceeded |
| DoS via large arrays | maxArrayLength truncates arrays before processing |
| DoS via long keys | maxKeyLength silently drops excessively long keys |
| Null-byte injection | Keys containing \u0000 are silently dropped |
| Malformed keys | Keys consisting only of dots/brackets (e.g., "...", "[[") are dropped |
Production Configuration
app.use(
hppx({
maxDepth: 10,
maxKeys: 1000,
maxArrayLength: 100,
maxKeyLength: 100,
strict: true,
onPollutionDetected: (req, info) => {
securityLogger.warn("HPP detected", {
ip: req.ip,
path: req.path,
source: info.source,
pollutedKeys: info.pollutedKeys,
});
},
}),
);What hppx Does NOT Protect Against
hppx is not a complete security solution. You still need:
- SQL injection protection — use parameterized queries
- XSS protection — sanitize output, use CSP headers
- CSRF protection — use CSRF tokens
- Authentication/Authorization — validate user permissions
- Rate limiting — prevent brute-force attacks
- Input validation — use schema validation libraries (Joi, Yup, Zod) alongside hppx
License
MIT License - see LICENSE file for details.
