← Back to Feed CACHED · 2026-10-22 14:51:55 · CACHE_KEY CVE-2026-111181
CVE-2026-111181 · CWE-248 · Disclosed 2026-10-22

Express body-parser uncaught exception via trailing bytes after compressed request body

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

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.

"One malformed gzip request crashes any default Express app. Patch body-parser now."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Identify an Express.js endpoint

The attacker locates any HTTP endpoint served by an Express.js application using body-parser with default settings. No specific route is needed — body-parser parses the request body *before* routing occurs, so even a 404 path triggers the vulnerability. Shodan, Wappalyzer, or simple header fingerprinting (X-Powered-By: Express) identifies targets.
Conditions required:
  • Target runs Express.js with body-parser (default in Express 4.16+ and all Express 5.x)
  • The inflate option is not explicitly set to false (it defaults to true)
Where this breaks in practice:
  • Some reverse proxies strip Content-Encoding before forwarding to the app, which would neutralize the attack
  • Hardened deployments may disable X-Powered-By header, making fingerprinting slightly harder (but not preventing exploitation)
Detection/coverage: Wappalyzer, Shodan http.component:express, or X-Powered-By: Express header scanning
STEP 02

Craft a compressed payload with trailing bytes

The attacker creates a valid gzip (or deflate/brotli/zstd) compressed payload — the content itself is irrelevant, it can be a single byte — and appends arbitrary trailing bytes after the compressed stream's end marker. No special tooling is required: 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.
Conditions required:
  • Ability to send HTTP requests to the target (network reachability)
Where this breaks in practice:
  • No meaningful friction — the payload is trivially constructable with standard command-line tools
Detection/coverage: WAFs generally do not inspect or validate the internal structure of compressed request bodies for trailing data anomalies
STEP 03

Send the request with Content-Encoding header

The attacker sends an HTTP POST (or PUT/PATCH) request to any path on the target with the 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.
Conditions required:
  • The request reaches body-parser before any middleware strips or rejects the Content-Encoding header
Where this breaks in practice:
  • 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
Detection/coverage: Application crash logs will show an uncaught exception from zlib/decompression. APM tools (Datadog, New Relic) will flag process restarts. No specific scanner signature exists yet for this CVE.
STEP 04

Sustain the outage with repeated requests

To cause a sustained denial of service, the attacker sends the crash payload repeatedly. Even with process manager restarts, the restart-crash cycle consumes resources and causes intermittent or total unavailability. In a Node.js cluster with N workers, N concurrent crash requests kills the entire cluster simultaneously. Container orchestrators may back off restarts after repeated crashes (CrashLoopBackOff in Kubernetes).
Conditions required:
  • Sustained network access to the target
Where this breaks in practice:
  • 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
Detection/coverage: Monitoring for rapid process restart cycles (>3 restarts in 60 seconds) is the most reliable detection signal
03 · Compensating Control

1
HIGH 7.5→IGNORE 0.0
SEVERITY REDUCED
Set 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).
2
HIGH 7.5→IGNORE 0.0
SEVERITY REDUCED
Deploy a reverse proxy rule to strip or reject Content-Encoding on inbound requests — Configure your reverse proxy (nginx, HAProxy, Envoy, Cloudflare, AWS ALB) to either strip the 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.
3
HIGH 7.5→MEDIUM 5.3
SEVERITY REDUCED
Run Node.js in cluster mode or behind Kubernetes with aggressive restart policies — Running 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.
4
HIGH 7.5→IGNORE 0.0
SEVERITY REDUCED
Upgrade body-parser to 1.20.9 (1.x) or 2.4.0 (2.x) — The definitive fix. Update 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.
What doesn't work
  • 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 traditional throw, not a rejected Promise.
  • express-rate-limit middleware — body-parser fires *before* most middleware in the stack, including rate limiters added via app.use(). The crash occurs during body parsing, before the rate limiter's next() is ever called.
04 · Intelligence Metadata

The supporting signals.

In-the-Wild ExploitationNo 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 ConceptNo 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 ScoreNot 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 StatusNot listed. No known exploitation in the wild as of disclosure date.
CVSS VectorCVSS: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 Versionsbody-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 Versionsbody-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 DateOctober 22, 2026 — published today via GHSA-27r3-jxmh-p828 on the expressjs/body-parser repository.
Reporter / CreditsReported by blue2cat. Coordinated by UlisesGascon (Express.js security lead). Developers: UlisesGascon, bjohansebas. Reviewer: Phillip9587.

Sources.

  1. GHSA-27r3-jxmh-p828 — GitHub Security Advisory
  2. body-parser v2.4.0 Release Notes
  3. body-parser Security Advisories (all)
  4. Express.js July 2026 Security Releases
  5. Node.js zlib API — rejectGarbageAfterEnd documentation
  6. body-parser on npm
  7. body-parser on Snyk Vulnerability DB
  8. Express.js September 2026 Security Releases
05 · The Call

Final Verdict
= UNCHANGED to HIGH (7.5/10)

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: true default 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.

06 · Verification

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.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/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