Guide · Backend
Body-Parser's October 2026 Vulnerability Explained: Upgrade to 2.4.0 or 1.20.9
21 min readUpdated By Saket Bhatnagar
Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.
In short
- The body parser vulnerability lets one compressed request crash a Node.js server that uses body-parser on default settings. body-parser is the library behind express.json(). Security tools list it as GHSA-27r3-jxmh-p828.
- The cause is a missing error listener, a function that handles a failure. body-parser stops listening for errors on its decompression stream, the part that unzips the body, while bytes can still reach it.
- The fix is body-parser 2.4.0 for Express 5 and 1.20.9 for Express 4, both on npm since 22 October 2026. Express 4.16.0 to 4.21.2 pin an exact old body-parser, so npm update body-parser alone misses it.
- If you cannot deploy today, set inflate: false on each body-parser parser you use. Compressed request bodies then get a 415 Unsupported Media Type error before any unzipping starts.
- Beginners can stop after the crash section and one version check. Engineers get the fix read line by line, the lockfile trap with tested npm commands and how to survive the next crash bug.
What goes wrong when one request can stop a Node.js server?
The whole server process stops, and every request it was handling fails with it. That is what the body parser vulnerability fixed in October 2026 allows. One compressed request with extra bytes after the compressed data can end the process.
No login is needed. If your app adds express.json() for every path with app.use(), the request does not even need to match one of your routes. body-parser reads the body before Express picks a route handler.
Picture a common setup. A food delivery startup in Bengaluru runs its order API on Express 4, as three Node.js processes behind a load balancer. Its lockfile was last refreshed in early 2025, so it still installs Express 4.21.2. The setup is made up, but the version trap it shows is real.
If one of those processes receives the bad request, it exits. The load balancer moves traffic to the other two until the attacker sends the same request to them as well. A supervisor can restart each process, but nothing stops the next request.
What is the body parser vulnerability fixed in October 2026?
It is a denial-of-service bug in the npm package body-parser. Denial of service means an attacker stops your service from answering anyone. In this bug, that is all they can do: they cannot read or change your data.
body-parser is the package that reads a request body and turns it into req.body. Express hands it to you as express.json(), express.urlencoded(), express.text() and express.raw(). We checked that express.json is the same function as body-parser's json in Express 4.22.3 and 5.3.0.
The bug has two IDs you will see in security tools. GHSA-27r3-jxmh-p828 is GitHub's ID, and CVE-2026-111181 is the shared public ID. Its severity score, called CVSS, is 7.5 out of 10, rated High.
| Field | Value |
|---|---|
| Advisory and CVE | GHSA-27r3-jxmh-p828, CVE-2026-111181 |
| Severity | High, 7.5 out of 10 (CVSS 3.1): works over the network, needs no login or user action, and only stops the service |
| Kind of bug | An uncaught exception, listed as CWE-248 |
| Affected | body-parser below 1.20.9, and 2.0.0 up to but not including 2.4.0 |
| Fixed | 1.20.9 and 2.4.0, published to npm on 22 October 2026 |
| Trigger | A compressed request body followed by trailing bytes, with default options |
| Workaround | inflate: false, if you do not need compressed request bodies |
Which words do you need before following a request through body-parser?
Each term below gets a plain meaning here and keeps that name for the rest of the guide.
- Middleware: a function Express runs on a request before your route code. express.json() is middleware.
- Stream: data that arrives in pieces over time. A request body uploaded in chunks is a stream.
- Pipe: a link from one stream to another, so every piece that arrives on the first is written into the second.
- Content-Encoding: a request header that says the body is compressed, for example gzip or deflate. Compression makes the body smaller to send.
- Decompression stream, or decoder: a stream that takes compressed pieces in and gives the original bytes out. Node.js builds these with its zlib module.
- Event and listener: Node.js objects announce things by emitting named events such as data, end or error. A listener is a function attached to hear one event.
- Uncaught exception: an error that no code catches. By default Node.js prints the stack trace and exits with code 1.
Why does an unhandled error event crash a Node.js process?
Because Node.js treats the error event as special. The Node.js events documentation says that if an emitter has no error listener and emits error, the error is thrown, a stack trace is printed and the process exits.
Every stream is an event emitter, so this rule covers sockets, files and zlib decoders too. You can watch it happen in a few lines. This demo touches nothing outside its own process.
const { EventEmitter } = require('node:events')
const decoder = new EventEmitter()
if (process.argv[2] === 'listener') {
decoder.on('error', (err) => console.log('handled:', err.message))
}
decoder.emit('error', new Error('decoder failed'))
console.log('still running')Save it as demo.js. Run node demo.js and you get a stack trace with the note Unhandled 'error' event, then exit code 1. Run node demo.js listener and it prints handled: decoder failed, then still running, with exit code 0.
What just happened? Both runs emitted the same error. With no listener, Node.js threw it and stopped the process. With one, the listener logged it and the program carried on. We ran both on Node.js 22.23.4.
A picture for the rule
Think of an office with one strict rule: an emergency call must be answered, or the whole building is evacuated. The error event is the emergency call. The listener is the person assigned to pick it up.
The process exit is the evacuation. Everyone in the building goes home, not just the person who caused the call. In Node.js terms, every request on that process fails, not just the bad one.
Where the picture breaks: there is no ringing and no waiting. The throw happens at once, inside the emit call, so nobody gets a chance to walk over.
Why try/catch does not save you
Pause and predict: if you wrap your code in try/catch, will the process survive the decoder's error? Decide before you read on.
It will not. The decoder emits its error later, from inside the stream, after your try block has finished. We tested a try/catch around an error emitted one tick later, and the process still exited with code 1. A watch-only listener added with events.errorMonitor does not help either.
How does a compressed request body reach the crash inside body-parser?
body-parser pipes the request into a decoder, reads the decoder until the compressed data ends, then removes its error listener. Bytes that arrive after that reach a finished decoder with nobody listening.
We read this path in body-parser 2.3.0's lib/read.js and in raw-body 3.0.2, the helper that collects the body. The 1.20.8 code follows the same steps with raw-body 2.5.3.
Pick a decoder
read.js checks the Content-Encoding header. For gzip it creates a zlib gunzip stream and runs req.pipe(stream), so request bytes flow into the decoder.
Read until the decoder ends
raw-body adds data, end and error listeners to the decoder. A gzip body carries its own end marker, so the decoder can end before the request does.
Clean up the listeners
When the decoder ends, raw-body's cleanup function removes its listeners, the error listener included. Your route runs with req.body filled in.
Trailing bytes arrive
The request is still piped into the decoder. Bytes after the compressed data reach the finished decoder, which raises an error. With no listener, Node.js throws and the process exits.
How trailing bytes reach a decoder nobody is listening to
Drag to exploreNotice that the pipe from the request into the decoder outlives raw-body's listeners. The late bytes travel down that same pipe after the error listener is gone.
Diagram connections:
- Client to Request stream (body plus trailing bytes)
- Request stream to Gunzip decoder (pipe)
- Gunzip decoder to raw-body reader (decoded bytes, then end)
- raw-body reader to Your route handler (body parsed)
- Gunzip decoder to Error on decoder (late bytes)
- Error on decoder to Process exits (no error listener)
- Client: Sends a gzip body plus extra bytes
- Request stream: Carries the body into body-parser
- Gunzip decoder: req.pipe(decoder) keeps feeding it
- raw-body reader: Removes its error listener at end
- Your route handler: Runs with req.body set
- Error on decoder: Late bytes hit a finished decoder
- Process exits: Uncaught exception, exit code 1
Who can send it, and to which routes
The advisory says no login and no matching route are needed, because parsing happens before routing. That holds when the parser is mounted with app.use() for every path.
Two limits apply. A parser only reads bodies whose Content-Type it accepts, application/json by default for express.json(). If you mount a parser on one route, only that route reads bodies.
The parser must also accept compressed bodies, and it does by default: inflate is true in both 1.x and 2.x. body-parser 1.x unzips gzip and deflate. 2.x, from 2.0.0, adds br, and 2.4.0 adds zstd where Node.js supports it.
What did body-parser 2.4.0 and 1.20.9 change to stop the crash?
The fix keeps the decoder covered until it is gone. After the body is read, body-parser now detaches the decoder from the request, gives it an empty error listener, destroys it and drains the request.
We confirmed this by downloading the npm tarballs and comparing 2.3.0 with 2.4.0, and 1.20.8 with 1.20.9. The new lines sit in lib/read.js, right after the body is read.
| New line | What it does | Why it matters |
|---|---|---|
| req.unpipe(stream) | Stops feeding request bytes into the decoder | Late bytes no longer reach a finished decoder |
| stream.on('error', () => {}) | Adds an empty error listener | An error during teardown is ignored, not thrown |
| stream.destroy() | Closes the decoder for good | No half-alive decoder is left behind |
| req.resume() | Reads and drops the rest of the request | The HTTP request can finish, per the code comment |
| Charset check moved earlier | When a verify option is set, rejects an unsupported charset with 415 before any decoder exists | In older versions this 415 was a second way to leave a piped decoder unguarded |
1.20.9 does the same work, with a fallback for Node.js 0.8 streams that lack unpipe(dest). Its changelog lists only the security fix. 2.4.0 also adds zstd support and two small performance changes, per its release notes.
The project's new tests send trailing bytes after gzip and deflate bodies with plain http.request calls, so the bytes really arrive after the compressed data. The 1.x fix's tests for this case expect a clean result only on Node.js 11 and later. We did not run them, because they send the crash input.
How do Express apps get body-parser, and why does Express 4.21.2 matter?
Most apps never install body-parser by hand. It arrives as a transitive dependency, a package that another package pulls in, through Express. The version you run depends on Express's version range and on your lockfile.
A range like ^2.3.0 accepts any later 2.x release. A range like ~1.20.5 accepts later 1.20.x releases only. An exact pin like 1.20.3 accepts nothing else.
A lockfile, package-lock.json for npm, records the exact version installed. npm install keeps the locked version while it still fits the range. We saw this with Express 5.3.0 holding body-parser 2.3.0 until we ran an update.
| Express version | body-parser range | Fresh install gets | How to reach the fix |
|---|---|---|---|
| 5.0.0 to 5.3.0 | ^2.0.1 up to ^2.3.0 | 2.4.0 (tested on 5.0.0 and 5.3.0) | npm update body-parser if the lockfile holds an older 2.x |
| 4.22.0 to 4.22.3 | ~1.20.3 or ~1.20.5 | 1.20.9 (tested on 4.22.0 and 4.22.3) | npm update body-parser if the lockfile holds an older 1.20.x |
| 4.16.0 to 4.21.2 | Exact pin, 1.20.3 in 4.21.2 | The pinned old version, 1.18.2 on 4.16.0 | npm update express to 4.22.3, or an npm override |
| Below 4.16.0 | No body-parser dependency | Nothing from Express | Upgrade body-parser itself if you installed it |
Deciding how to reach a fixed body-parser
Drag to exploreNotice that the path depends on who pulls body-parser in. Only the Express 4.21.2 and older branch needs more than an npm update or install of body-parser.
Diagram connections:
- Run npm ls body-parser to 1.20.9 or 2.4.0 or later?
- 1.20.9 or 2.4.0 or later? to Patched (Yes)
- 1.20.9 or 2.4.0 or later? to Who pulls it in? (No)
- Who pulls it in? to Your own package.json (Direct dependency)
- Who pulls it in? to Express 5 or 4.22.x (Express with a range)
- Who pulls it in? to Express 4.21.2 or older (Express with exact pin)
- Your own package.json to Run npm ls body-parser (check again)
- Express 5 or 4.22.x to Patched (npm ls shows the fix)
- Express 4.21.2 or older to Patched (npm ls shows the fix)
- Run npm ls body-parser: Lists every copy and its parent
- 1.20.9 or 2.4.0 or later?: Compare each copy's version
- Who pulls it in?: Read the line above body-parser
- Your own package.json: npm install [email protected] or @1.20.9
- Express 5 or 4.22.x: npm update body-parser
- Express 4.21.2 or older: npm update express, or add an override
- Patched: Commit package-lock.json and deploy
How do you check whether your app runs a vulnerable body-parser?
Run npm ls body-parser in the project folder, ideally inside the image you deploy. It prints every copy in the dependency tree, with its version and the package that pulled it in.
$ npm ls body-parser
[email protected] /app
`-- [email protected]
`-- [email protected]This output means the app is exposed, because 1.20.3 is below 1.20.9. Any 1.x below 1.20.9, or any 2.x below 2.4.0, is affected. A large project can show several copies, and each one counts.
npm audit checks the same tree against the GitHub Advisory Database. On our Express 5.3.0 project locked to body-parser 2.3.0, it listed GHSA-27r3-jxmh-p828 as high severity. After the update it printed found 0 vulnerabilities.
On Express 4.21.2, npm audit also listed a second body-parser advisory, GHSA-v422-hmwv-36x6, about the limit option. In our run, body-parser 1.20.9 cleared both, though other Express 4.21.2 packages were still flagged.
How do you upgrade body-parser in Express 4 and Express 5 apps?
Update the package that pulls body-parser in, then confirm with npm ls. Every command below was run on 25 October 2026 with npm 10.9.9.
Find every copy
Run npm ls body-parser. Note each version and the package above it.
Express 5 or Express 4.22.x
Run npm update body-parser. It moved our locked 2.3.0 to 2.4.0, the newest version Express 5.3.0's range allows.
Express 4.21.2 or older
If package.json lists Express as ^4 or similar, run npm update express. Our 4.21.2 project moved to Express 4.22.3 with body-parser 1.20.9.
Express pinned exactly
If package.json pins an exact old Express, npm update body-parser changes nothing. Raise the Express version, or add the npm override shown below.
body-parser as your own dependency
Run npm install [email protected] or npm install [email protected], keeping your current major version.
Check and commit
Run npm ls body-parser again and your tests. Commit package-lock.json so CI and production install the same versions.
An npm override forces a package version that a dependency would not choose. Add this to package.json and run npm install. The nested form pins only the body-parser under Express.
{
"overrides": {
"express": {
"body-parser": "1.20.9"
}
}
}Afterwards npm ls body-parser shows both [email protected] and [email protected] marked overridden. We ran Express 4.21.2 with this override, and plain and gzip JSON bodies still parsed with status 200.
A flat override, with body-parser at the top level, would force 1.20.9 on every copy, including any 2.x copy another package needs. An override is also a pin you must remember to remove. Prefer moving to Express 4.22.3, whose own range already allows 1.20.9.
When should you set inflate: false instead of upgrading?
Use inflate: false as a stopgap when you cannot deploy an upgrade today and your clients never send compressed request bodies. body-parser then rejects compressed bodies with a 415 error before it creates any decoder.
We read this in the unpatched 1.20.8 source: the inflate check throws before the decoder is built. Each parser has its own options, so set it on every one you mount.
app.use(express.json({ inflate: false }))
app.use(express.urlencoded({ extended: false, inflate: false }))We sent well-formed requests to Express 5.3.0 with the unpatched body-parser 2.3.0, and to the patched versions. The results matched on every version we tried.
default, plain JSON -> 200 {"got":{"city":"Pune"}}
default, gzip JSON -> 200 {"got":{"city":"Pune"}}
inflate:false, plain -> 200 {"got":{"city":"Pune"}}
inflate:false, gzip -> 415 {"status":415,"type":"encoding.unsupported"}The 415 body shape comes from our test's error handler; yours will look different. To know whether clients compress bodies, search your access or proxy logs for requests with a Content-Encoding header.
How do you keep one crash bug from taking the whole service down?
Run several processes, restart them automatically and alert on restarts. The upgrade fixes this bug; these habits limit the damage of the next one.
- Restart automatically. A process manager such as systemd, PM2 or Kubernetes starts a new process when one exits. The Node.js docs recommend an external monitor in a separate process for this.
- Run more than one process. Behind a load balancer, one exit drops only that process's in-flight requests. With a single process, every user waits for the restart.
- Alert on restart counts. One crash may be noise; the same crash every few seconds is an attack or a bad deploy. A quiet restart loop hides the bug.
- Handle uncaughtException only to log and exit. The Node.js docs say it is not safe to resume normal operation after an uncaught exception.
- Give every stream you create an error listener for its whole life. stream.pipeline() helps: in our test it passed an error from the last stream to one callback, and the process kept running.
Here is the log-and-exit pattern, run on Node.js 22.23.4. It printed fatal: decoder failed and exited with code 1, so a supervisor sees a failure and restarts it.
// Last-resort handler: log, then exit non-zero so a supervisor restarts the process.
process.on('uncaughtException', (err) => {
console.error('fatal:', err.message)
process.exit(1)
})
const { EventEmitter } = require('node:events')
new EventEmitter().emit('error', new Error('decoder failed'))Restarts do not stop an attacker who repeats the request. They shorten each outage and buy time for the upgrade, nothing more.
Is the October 2026 undici advisory the same bug?
It is the same kind of bug in a different package, pointing the other way. undici is the HTTP client behind the fetch() function built into Node.js. Its advisory, GHSA-xjj6-f33q-929c, is about responses, not requests.
A malicious server sends a response with more than five content codings and closes the connection before the body. undici rejects the response, but an internal stream it already made has no error listener. The process crashes even if your code catches the fetch() rejection.
Affected undici versions run from 6.23.0, 7.18.0 and 8.0.0 up to the fixes in 6.29.1, 7.30.1 and 8.12.2. Upgrading body-parser does nothing for undici, and the reverse is also true. The undici inside Node.js ships with Node.js itself; process.versions.undici shows its version.
What does the advisory not tell you yet?
The advisory leaves four questions open as of 25 October 2026. They are which encodings trigger it, whether Express will require the fix, which Node.js versions are affected and whether anyone exploits it.
- Which encodings trigger it. The advisory says such as gzip, and the fix's tests cover gzip and deflate. Treat every encoding your parser accepts as in scope.
- Whether Express will publish releases that require the fix. Express 5.3.0 and 4.22.3 allow it, but an old lockfile can still hold an old body-parser.
- Which Node.js versions are affected. The advisory names none, and the 1.x fix's tests expect a clean result only on Node.js 11 and later.
- Whether anyone is exploiting it. The advisory mentions no attacks seen in the wild. That can change, so do not wait for it.
How do you explain the body parser vulnerability in a system design interview?
Frame it as a lifetime bug in a shared library, then give the fix at three levels: code, dependencies and operations. The steps below are an answer frame you can say in about two minutes.
State the impact
One request with no login crashes a Node.js process whose body-parser, on default options, reads that request. Availability only, CVSS 7.5.
Explain the mechanism
The decoder stays piped from the request after the reader removes its error listener. Late bytes cause an error with no listener, so Node.js throws.
Give the code fix
Detach the decoder, add an error listener, destroy it and drain the request, as body-parser 2.4.0 and 1.20.9 do.
Give the dependency fix
Find every copy with npm ls, update or override, and commit the lockfile. Mention that Express 4.21.2 pins an exact old version.
Give the operational fix
Several processes, automatic restarts, alerts on restart loops and inflate: false where compression is not needed.
Name the general lesson
Any stream that can still receive bytes needs an error listener for as long as it lives.
Questions people ask
- Is my app affected if no client ever sends compressed requests?
- Yes, if body-parser runs with default options. The sender chooses the Content-Encoding header, not you, and inflate defaults to true. An attacker can send a gzip body even if no real client ever does. Setting inflate: false or upgrading closes that path.
- Does this affect apps that do not use Express?
- Yes, if they use body-parser directly, for example with a plain Node.js http server or another framework that accepts Express middleware. The bug sits in body-parser's read code, not in Express. Run npm ls body-parser to see whether anything in your tree pulls it in.
- Will a try/catch or an Express error handler stop the crash?
- No. The decoder emits its error later, outside your route code and outside Express's error middleware. Our test of a try/catch around a delayed error event still exited with code 1. Only an error listener on that stream stops it, and that is what the fixed versions add.
- Is a process.on('uncaughtException') handler a safe fix?
- No. The Node.js docs call it a crude last resort and say it is not safe to resume normal operation after an uncaught exception. Use the handler to log and exit with a non-zero code, and let a supervisor restart the process. The real fix is upgrading body-parser.
- Why did npm update body-parser not change my version?
- Most likely you run an Express version from 4.16.0 to 4.21.2, which pins an exact body-parser version. npm will not move a pinned dependency. Update Express to 4.22.3, which allows 1.20.9, or add an npm override. Then run npm ls body-parser to confirm the new version.
- Do I need to upgrade Node.js to fix this?
- No. The fix is in body-parser, an npm package inside node_modules, and upgrading Node.js does not change it. Keep Node.js current anyway for its own security releases. The undici behind fetch() is one part that comes with Node.js, not from npm.
- What does the CVSS score of 7.5 mean here?
- It is a high severity score out of 10. The vector says the attack works over the network, needs low effort, no login and no user action. It harms availability only, not confidentiality or integrity. In plain words, attackers can stop your app but cannot read or change your data through this bug.
Sources and further reading
- GitHub security advisory GHSA-27r3-jxmh-p828 for body-parser (severity, CVE, affected and patched versions, workaround)
- OSV record for GHSA-27r3-jxmh-p828 (CVSS vector, affected ranges, fix commits, publish dates)
- body-parser v2.4.0 release notes (security fix, zstd support and other changes)
- body-parser 1.20.9 release notes (security-only release)
- body-parser fix commit 1260c38: prevent process crash from trailing bytes after a compressed body
- body-parser fix commit f7075ea, the second fix commit linked from the advisory
- Node.js events documentation: Error events
- Node.js process documentation: using uncaughtException correctly
- Node.js globals documentation: fetch is based on undici
- undici security advisory GHSA-xjj6-f33q-929c (excessive content-encodings crash)
- npm registry metadata for body-parser (release dates and versions)
Related guides
- TanStack npm Hack Explained: What JavaScript Developers Must Do NowBackend · 18 min read
- Axios Supply Chain Attack 2026: Malicious npm Versions, Postinstall Malware, and How to Protect Your SystemsBackend · 16 min read
- Backpressure Explained: Slowing Producers Before Queues ExplodeSystem design · 20 min read