# One Request Can Crash Your Express App: The body-parser and multer DoS Fixes You Need This Week

---

A single HTTP request. No auth, no matching route, no clever chaining. That's all it takes to drop an Express process with the body-parser and multer bugs that got patched on October 31st. If you run a MERN backend, this is the kind of advisory that's easy to scroll past and then regret, because the fix is a one-liner and the blast radius is your whole API.

I spent a morning actually reproducing these in a sandbox so I could tell you what matters and what's noise. Here's the short version, the mechanism, and a copy-paste checklist I ran across both bash and zsh.

## What actually shipped

Two CVEs, both rated High, both plain denial-of-service, both in the request-parsing layer every Express app leans on:

- **CVE-2026-111181** in **body-parser** — fixed in `1.20.9` and `2.4.0`.
- **CVE-2026-113872** in **multer** — fixed in `2.5.0`.

The Express team summed up body-parser bluntly: *"A vulnerability in Express body-parser middleware enables a malicious client to crash a server with a single request."* And the part that should make you check today rather than next sprint: *"A single unauthenticated request is enough, and no matching route or authentication is required because parsing runs before routing."*

That last clause is the whole story. `express.json()` sits in front of your router. It doesn't care whether the URL exists or whether the caller is logged in. If the body parser crashes, your auth middleware never even runs.

![](https://cdn.hashnode.com/uploads/covers/69d007f5e466e2b7625cd1df/96c2d17a-1109-44dd-a939-5285d4584157.png align="center")

## The body-parser bug, in one breath

body-parser accepts compressed request bodies by default (`inflate: true`). When a client sends a gzip body, body-parser pipes the raw request through a decompression stream.

The bug: a payload that is a *valid* gzip member followed by some trailing junk decodes fine, your handler responds `200`, and everyone's happy — until those trailing bytes reach the decompression stream that has *already finished*. The Express write-up puts it as *"the decompression stream outlives the request parsing."* The decoder throws an error after parsing is over, and by then nobody is listening for it.

This is a classic Node streams footgun, and you can feel it with twelve lines of plain Node — no Express, no exploit payload, just `.pipe()` versus `pipeline()`:

```javascript
const { PassThrough, pipeline } = require('node:stream');

const mode = process.argv[2]; // "pipe" or "pipeline"
const src = new PassThrough();
const dst = new PassThrough();
dst.resume();

if (mode === 'pipe') {
  src.pipe(dst);              // forwards data, but NOT errors
} else {
  pipeline(src, dst, (err) => {
    console.log('pipeline caught:', err ? err.message : 'no error');
  });
}

// the "request" is already done; the stream fails a moment later
setTimeout(() => src.destroy(new Error('late decoder error')), 10);
setTimeout(() => console.log('still alive'), 50);
```

Run `node stream-demo.js pipe` and the process dies with an unhandled `'error'` event. Run `node stream-demo.js pipeline` and it prints `pipeline caught: late decoder error` and keeps going. That's the exact shape of the body-parser fix: it detaches and destroys the decompression stream once parsing is done, and attaches a no-op error handler so a late error can't become an uncaught exception. I diffed `1.20.8` against `1.20.9` and that's precisely what the patch adds to `lib/read.js`.

The takeaway for your own code: if you ever `.pipe()` a stream whose lifetime you don't fully control, you own its errors. `pipeline()` (or `stream.pipeline` / `pipelinePromise`) wires up cleanup and error propagation for you. Reach for it by default.

## The multer bug is the same family

Different package, same root cause. multer's `CVE-2026-113872` only bites if you use an **asynchronous** `fileFilter`. Per the advisory: *"When fileFilter is asynchronous and a request is aborted... while another file's filter is still pending, the pending callback later resumes a parsing stream that multer has already torn down. busboy then emits an error on a stream that no longer has an error listener, which Node raises as an unhandled exception that exits the process."*

Synchronous `fileFilter` callbacks are not affected. But if yours does an async lookup — checking a DB, calling S3, hitting an auth service to validate the upload — you're in range on any multer `>= 2.0.0, < 2.5.0`.

## The fix, and how to be sure it took

The upgrade itself is boring, which is the best kind of security work. I ran this in both bash and zsh (zsh being the macOS default) on a fresh Express 4 install and confirmed it pulls `body-parser@1.20.9` and `multer@2.5.0`:

```bash
npm update body-parser multer
npm ls body-parser multer
```

If a transitive dependency pins you to an old body-parser, force the floor with an override in `package.json`. I tested this against both Express 4 (which bundles body-parser 1.x) and Express 5 (which bundles 2.x), and it resolved correctly in both:

```json
{
  "overrides": {
    "body-parser@1": "^1.20.9",
    "body-parser@2": "^2.4.0"
  }
}
```

Then `npm install` again and re-check with `npm ls body-parser`.

For CI or a quick audit across a monorepo, here's a lockfile scanner I wrote and ran — it walks every `body-parser` and `multer` entry in `package-lock.json`, including nested ones, and exits non-zero if anything is in range. I verified it flags vulnerable fixtures and prints a clean line when there's nothing to find, so silence never looks like a broken command:

```javascript
// check-express-cves.js  —  run: node check-express-cves.js [package-lock.json]
const fs = require('fs');

const lock = JSON.parse(fs.readFileSync(process.argv[2] || 'package-lock.json', 'utf8'));
const packages = lock.packages || {};

const parse = (v) => v.split('-')[0].split('.').map(Number);
const lt = (a, b) => {
  const [x, y] = [parse(a), parse(b)];
  for (let i = 0; i < 3; i++) if (x[i] !== y[i]) return x[i] < y[i];
  return false;
};

const rules = {
  'body-parser': (v) => (parse(v)[0] >= 2 ? lt(v, '2.4.0') : lt(v, '1.20.9')), // CVE-2026-111181
  multer: (v) => parse(v)[0] === 2 && lt(v, '2.5.0'),                          // CVE-2026-113872
};

let hits = 0;
for (const [path, meta] of Object.entries(packages)) {
  const name = path.split('node_modules/').pop();
  if (rules[name] && meta.version && rules[name](meta.version)) {
    console.log(`VULNERABLE  ${name}@${meta.version}  (${path})`);
    hits++;
  }
}
if (!hits) console.log('nothing vulnerable found');
process.exitCode = hits ? 1 : 0;
```

## Don't forget Brotli — the fix lives in Node, not Express

While you're in here: the same release notes flag `CVE-2026-111497`, an out-of-bounds write in the **Brotli decoder** (the one nicknamed Node2Shell). With default options, `express.json()`, `express.urlencoded()`, and friends will decompress a `Content-Encoding: br` body through Node's zlib. The real fix is **upgrading Node.js** to a release that ships the patched decoder — this one is not something an npm bump can solve.

If you can't upgrade Node immediately and you don't actually accept Brotli-encoded request bodies, the cheapest mitigation is to reject unexpected encodings *before* the body parsers run. This middleware runs clean and returns `415` for `br` while letting normal JSON through — I tested both paths:

```javascript
const express = require('express');
const app = express();

// Only let through the encodings you actually expect. Register BEFORE the body parsers.
const ALLOWED = new Set(['identity', 'gzip', 'deflate']);
app.use((req, res, next) => {
  const enc = (req.headers['content-encoding'] || 'identity').toLowerCase().trim();
  if (!ALLOWED.has(enc)) {
    return res.status(415).json({ error: `Unsupported Content-Encoding: ${enc}` });
  }
  next();
});

app.use(express.json({ limit: '100kb' }));
```

That's defense in depth, not the fix. Patch body-parser and Node; keep the allowlist as a seatbelt.

![](https://cdn.hashnode.com/uploads/covers/69d007f5e466e2b7625cd1df/cd8b6edb-5107-4a09-a5e2-92567514a864.png align="center")

## The real lesson for MERN devs

None of these are exotic. They're all one unlistened-for error event on a stream that got torn down at the wrong moment — the single most common way a long-running Node process dies. Three things to walk away with:

1. **Patch today.** `npm update body-parser multer`, then confirm with `npm ls`. It's a five-minute job and the exploit needs zero auth.
2. **Prefer** `pipeline()` **over** `.pipe()` in any code you own. The entire bug class disappears when something is actually listening for late errors.
3. **Audit your middleware order.** Anything that runs before your router is internet-facing with no auth in front of it. Treat it like the front door it is.

Run the scanner against your lockfiles this week — including the services you haven't touched in months. Those are the ones still on the vulnerable version.

*Sources: the* [*Express October 2026 security releases*](https://expressjs.com/en/blog/2026-10-31-security-releases)*, the* [*body-parser CVE write-up*](https://expressjs.com/en/blog/2026-10-28-cve-2026-111181/)*, and* [*Node Weekly #645*](https://nodeweekly.com/issues/645)*.*
