Leaving the door unlocked is bad, but this is more like a doorbell that blows out every window in the building
CVE-2026-111181 affects body-parser, the ubiquitous HTTP request body parsing middleware for Express.js, with roughly 147 million weekly npm downloads and over 26,000 direct dependents. When a client sends a request with a Content-Encoding header (gzip, deflate, brotli, or zstd), body-parser automatically decompresses it before parsing. If the compressed payload contains a valid compressed frame followed by *any* trailing garbage bytes, the decompression stream raises an error after it finishes reading the legitimate data. That error propagates as an uncaught exception (CWE-248), and in Node.js, an uncaught exception terminates the entire process. One HTTP request, zero authentication, crash. Affected versions are body-parser < 1.20.9 (1.x line) and >= 2.0.0, < 2.4.0 (2.x line). The fix lands in 1.20.9 and 2.4.0, released October 22, 2026.
The vendor scored this HIGH (7.5) and that assessment is honest. The CVSS vector AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H is textbook-correct: network-reachable, no complexity, no auth, full availability kill. There's no confidentiality or integrity impact — this is pure DoS. Some teams will be tempted to downgrade it because 'it's just a crash,' but the bar for exploitation is so absurdly low (a curl one-liner with a crafted gzip payload) and the default config is vulnerable out of the box. The vendor got this right.
4 steps from start to impact.
Identify an Express.js endpoint
X-Powered-By: Express) identifies targets.- Target runs Express.js with body-parser (default in Express 4.16+ and all Express 5.x)
- The
inflateoption is not explicitly set tofalse(it defaults totrue)
- Some reverse proxies strip
Content-Encodingbefore forwarding to the app, which would neutralize the attack - Hardened deployments may disable
X-Powered-Byheader, making fingerprinting slightly harder (but not preventing exploitation)
http.component:express, or X-Powered-By: Express header scanningCraft a compressed payload with trailing bytes
echo -n 'garbage' | gzip | cat - <(echo -n 'JUNK') produces a valid payload. This is not a novel technique; it's a well-understood property of stream compression formats.- Ability to send HTTP requests to the target (network reachability)
- No meaningful friction — the payload is trivially constructable with standard command-line tools
Send the request with Content-Encoding header
Content-Encoding: gzip header and the crafted payload as the body. body-parser's decompression pipeline processes the valid compressed frame, then encounters the trailing bytes. The zlib stream emits an error event that body-parser does not catch, causing it to propagate as an uncaught exception. Node.js terminates the process.- The request reaches body-parser before any middleware strips or rejects the Content-Encoding header
- Process managers (PM2, systemd, container orchestrators like Kubernetes) will restart the process, limiting sustained outage *if* restart is fast
- Rate limiting on the target may slow repeated crash attempts, but a single request is enough for one crash
Sustain the outage with repeated requests
- Sustained network access to the target
- CDN/WAF rate limiting may throttle request volume, though a low rate (1 req/sec) is sufficient to keep a single-process app down
- Kubernetes liveness probes and restart policies provide resilience but introduce restart latency
inflate: false on all body-parser instances — Adding { inflate: false } to express.json(), express.urlencoded(), express.raw(), and express.text() middleware calls rejects any request with a Content-Encoding header (returning HTTP 415), completely eliminating the decompression attack surface. This is the fastest compensating control — deploy within 30 days per the noisgate mitigation SLA for HIGH. Be aware this breaks any client legitimately sending compressed request bodies (uncommon in most API designs, but verify with your API consumers first).Content-Encoding header from incoming requests or reject requests that include it. This neutralizes the attack at the network edge before it reaches body-parser. Example nginx: proxy_set_header Content-Encoding '';. Deploy within 30 days per the noisgate mitigation SLA. This is preferable when you cannot modify application code quickly.node --cluster with multiple workers, or deploying in Kubernetes with restartPolicy: Always and short liveness probe intervals, limits the blast radius of a single crash to one worker and reduces downtime to seconds. This does not *prevent* the crash but makes sustained DoS significantly harder. Not a substitute for patching — an attacker can still crash all workers simultaneously with concurrent requests.body-parser in your package.json and package-lock.json, run npm audit fix or npm install body-parser@latest, and redeploy. For Express 5.x apps, ensure your Express version pins body-parser >= 2.4.0. Apply within 180 days per the noisgate remediation SLA for HIGH, but given the trivial exploitability, aim for the next regular deployment cycle.- Rate limiting alone — a single request causes a crash. Rate limiting at 1 req/min still allows one crash per minute, which is sufficient for sustained disruption against a single-process app.
- WAF JSON/SQL injection rules — the malicious payload is not in the parsed body content; it's in the raw compressed stream structure. WAFs inspect decompressed content, not the compression frame integrity.
- Node.js
--unhandled-rejections=warn— this flag affects Promise rejections, not synchronous uncaught exceptions from stream error events. The crash path is a traditionalthrow, not a rejected Promise. express-rate-limitmiddleware — body-parser fires *before* most middleware in the stack, including rate limiters added viaapp.use(). The crash occurs during body parsing, before the rate limiter'snext()is ever called.
The supporting signals.
| In-the-Wild Exploitation | No known exploitation. Not listed on CISA KEV. Disclosed today (October 22, 2026). No GreyNoise or Shadowserver scanning activity reported yet. Given the trivial exploit complexity, weaponization is expected within days. |
|---|---|
| Proof of Concept | No public PoC repository found on GitHub or pocindex.io as of disclosure date. However, the exploit is trivially constructable — a single curl command with a crafted gzip payload suffices. No specialized tooling or reverse engineering required. Expect PoC publication within 24–48 hours. |
| EPSS Score | Not yet scored — CVE disclosed today (2026-10-22). FIRST EPSS API returns no data. Given the AV:N/AC:L/PR:N/UI:N vector and the massive install base, expect a high EPSS percentile (>90th) once scored. |
| CISA KEV Status | Not listed. No known exploitation in the wild as of disclosure date. |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H — 7.5 HIGH. Network attack vector, zero complexity, zero privileges, zero interaction. Scope unchanged. Pure availability impact. This vector correctly reflects the single-request crash behavior. |
| Affected Versions | body-parser < 1.20.9 (entire 1.x line) and >= 2.0.0, < 2.4.0 (2.x line). Express 4.16+ bundles body-parser internally. Express 5.x uses body-parser 2.x. Virtually every Express application not pinned to a patched version is affected. |
| Fixed Versions | body-parser 1.20.9 (1.x backport) and 2.4.0 (2.x line). Express 5.x users should update to the Express release that pins body-parser >= 2.4.0. No distro backports expected — this is an npm ecosystem package. |
| Install Base / Exposure | ~147 million weekly npm downloads, 26,495+ direct dependents on npm. body-parser is a transitive dependency of Express.js, the dominant Node.js web framework. Shodan shows millions of internet-facing Express instances (identifiable via X-Powered-By: Express header when not stripped). |
| Disclosure Date | October 22, 2026 — published today via GHSA-27r3-jxmh-p828 on the expressjs/body-parser repository. |
| Reporter / Credits | Reported by blue2cat. Coordinated by UlisesGascon (Express.js security lead). Developers: UlisesGascon, bjohansebas. Reviewer: Phillip9587. |
Sources.
- GHSA-27r3-jxmh-p828 — GitHub Security Advisory
- body-parser v2.4.0 Release Notes
- body-parser Security Advisories (all)
- Express.js July 2026 Security Releases
- Node.js zlib API — rejectGarbageAfterEnd documentation
- body-parser on npm
- body-parser on Snyk Vulnerability DB
- Express.js September 2026 Security Releases
Why this verdict
- Zero-friction attack path: Unauthenticated, pre-routing, single HTTP request, default configuration vulnerable. No exploit chain to assemble — this is a one-shot kill against the Node.js process. Every friction checkpoint in the attack path comes back clean: no auth needed, no special position, no user interaction, no complexity.
- Massive blast radius by install base: body-parser has ~147M weekly npm downloads and is the default body parsing middleware in Express 4.16+ and Express 5.x. The fraction of Node.js web applications affected is effectively 'most of them.' This is not a niche plugin — it is foundational plumbing.
- Role multiplier: body-parser is *application middleware*, not canonical high-value-role infrastructure (not an IdP, hypervisor, CA, or network edge). Even in high-value deployments (API gateways, auth services built on Express), the blast radius is per-process availability loss — not domain takeover, fleet compromise, or data exfiltration. The outcome across all roles is DoS, which caps the ceiling at HIGH.
- DoS-only impact correctly bounds severity: The CVSS vector has C:N/I:N/A:H. There is no path from this vulnerability to code execution, privilege escalation, or data theft. Process managers and container orchestrators provide automatic recovery, limiting sustained impact in well-architected environments.
- Default-on attack surface offsets all friction: The
inflate: truedefault means teams must have *opted out* of decompression to be safe. Virtually no one does. This eliminates the usual 'requires non-default config' downgrade argument.
Why not higher?
This cannot be CRITICAL because the impact is strictly availability — no confidentiality or integrity loss, no code execution, no lateral movement capability. body-parser is application middleware, not infrastructure software in the canonical high-value-role catalog (not a hypervisor, IdP, domain controller, CA, backup system, or network edge appliance). Even a fleet-wide crash wave against every Express app is a temporary outage recoverable by restart, not a domain compromise or data breach. The CVSS ceiling for pure DoS with Scope:Unchanged is 7.5, which the vendor already assigned.
Why not lower?
This cannot be MEDIUM because the exploit requires exactly zero prerequisites beyond network reachability — no authentication, no special configuration, no user interaction, and the default settings are vulnerable. The attack fires before routing, meaning *any* path on the application works. A single curl command crashes production. Combined with 147 million weekly downloads, the probability of encountering a vulnerable target approaches certainty, and the skill bar for exploitation is script-kiddie level. Downgrading a default-on, unauthenticated, single-request process kill affecting the most popular Node.js middleware to MEDIUM would be malpractice.
Crowdsourced verification payload.
Run this script on any host where a Node.js project with body-parser is installed, or point it at a project directory. Invoke as: bash check_bodyparser_cve2026111181.sh /path/to/project. Requires node and npm on PATH. No elevated privileges needed — it reads node_modules only.
#!/usr/bin/env bash
# check_bodyparser_cve2026111181.sh
# Checks whether a Node.js project uses a body-parser version vulnerable to
# CVE-2026-111181 (DoS via trailing bytes after compressed request body).
# Exit codes: 1 = VULNERABLE, 0 = PATCHED, 2 = UNKNOWN
set -euo pipefail
PROJECT_DIR="${1:-.}"
if [ ! -d "$PROJECT_DIR" ]; then
echo "UNKNOWN — directory '$PROJECT_DIR' does not exist"
exit 2
fi
cd "$PROJECT_DIR"
# Try npm list first (works if node_modules is installed)
if command -v npm &>/dev/null && [ -d node_modules ]; then
BP_VERSION=$(npm list body-parser --depth=10 --json 2>/dev/null | \
node -e "
const j = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));
function find(deps) {
if (!deps) return null;
for (const [k,v] of Object.entries(deps)) {
if (k === 'body-parser') return v.version;
const r = find(v.dependencies);
if (r) return r;
}
return null;
}
const v = find(j.dependencies);
if (v) process.stdout.write(v);
else process.exit(1);
" 2>/dev/null) || BP_VERSION=""
else
BP_VERSION=""
fi
# Fallback: read package.json in node_modules directly
if [ -z "$BP_VERSION" ] && [ -f node_modules/body-parser/package.json ]; then
BP_VERSION=$(node -e "console.log(require('./node_modules/body-parser/package.json').version)" 2>/dev/null) || BP_VERSION=""
fi
if [ -z "$BP_VERSION" ]; then
echo "UNKNOWN — body-parser not found in $PROJECT_DIR"
exit 2
fi
echo "Found body-parser version: $BP_VERSION"
# Compare versions using node's semver-compatible comparison
RESULT=$(node -e "
const v = '$BP_VERSION';
const parts = v.split('.').map(Number);
const major = parts[0], minor = parts[1], patch = parts[2] || 0;
// Vulnerable: < 1.20.9 (1.x line) OR >= 2.0.0 and < 2.4.0 (2.x line)
if (major === 1) {
if (minor < 20 || (minor === 20 && patch < 9)) {
console.log('VULNERABLE');
} else {
console.log('PATCHED');
}
} else if (major === 2) {
if (minor < 4) {
console.log('VULNERABLE');
} else {
console.log('PATCHED');
}
} else if (major === 0) {
console.log('VULNERABLE');
} else {
console.log('PATCHED');
}
" 2>/dev/null) || RESULT="UNKNOWN"
echo "$RESULT — body-parser $BP_VERSION vs CVE-2026-111181 (fixed in 1.20.9 / 2.4.0)"
case "$RESULT" in
VULNERABLE) exit 1 ;;
PATCHED) exit 0 ;;
*) exit 2 ;;
esac