learn deep. ship safe.Follow ByteMun
Cyber Security🔥 Trending #4

Node2Shell Explained: The Node.js Brotli Bug (CVE-2026-111497) and How to Patch It

A Brotli decoder bug in Node.js can lead to remote code execution, and your app may decode Brotli without you knowing. Here’s who’s exposed and how to patch every Node you run.

Node Brotli Vulnerability Featured 1

The Node.js Brotli vulnerability, CVE-2026-111497, is the most serious bug in the security updates Node.js shipped on October 21 for every supported release line. It sits in the Brotli decompression code that ships inside Node itself, and the project's own advisory says it can lead to remote code execution. The security community has already nicknamed it Node2Shell. If you run anything on Node.js, and in 2026 that is most of the web (see our guide to building modern apps with React and Node.js), this is a week to stop and patch.

The first reaction from a lot of developers to the Node.js Brotli vulnerability was "I don't use Brotli, so I'm fine." That is exactly the assumption this bug punishes. In this guide we'll walk through what actually broke, the quiet ways your app may be decoding Brotli without you ever asking it to, how far the exploit research has gone, and a practical checklist for getting every Node process you run onto a fixed version.

The short version

  • Upgrade to Node.js 22.23.4, 24.22.1 or 26.12.1 (or later). These are the only fixed releases.

  • Node.js 20.x and older get nothing. They are end-of-life, so the fix is a major-version upgrade, not a patch.

  • You are exposed if your code, or any library you use, decompresses Brotli data someone else controls: an incoming request body or a response your server fetches.

  • There is no workaround inside Node.js. Firewall rules help for inbound traffic only.

  • No in-the-wild attacks were reported as of October 22, but working proof-of-concept research is already public.

What is the Node.js Brotli vulnerability?

Brotli is Google's compression format. It is the "br" you see in Content-Encoding: br headers, and it squeezes web content a bit tighter than gzip. Node.js doesn't call out to some system library for it by default. It carries its own copy of Google's Brotli code, compiled straight into the node binary and exposed through the node:zlib module.

The bug is an out-of-bounds write in the decoder. Feed it a malformed Brotli stream and, instead of rejecting it, the decoder keeps going in a state that should be impossible and ends up writing past the end of a memory buffer. Because this is native C code running inside your Node process, that stray write lands in your process's memory. In the worst case, a carefully shaped write becomes code execution with whatever permissions your Node process has.

According to Imperva's analysis, the trigger involves Brotli's "block switch" mechanism. A stream announces the block types it will use as decoding progresses, and the malicious input reaches a block change that the stream's own structure says cannot happen. The vulnerable decoder treated that as "I need more input" and carried on. The fix, a small upstream patch from Google titled roughly "detect and report invalid stream earlier," simply makes the decoder stop with a format error at that point.

Here is the slightly uncomfortable part: that upstream fix was committed to Brotli's source back in February 2026, and its commit message didn't mention security. It only shipped as a security release, Brotli 1.2.1, 1.1.1 and 1.0.10, on October 21, the same day as the Node.js update. That gap is a good reminder that "boring" upstream bug fixes can carry real security weight.

"But I don't use Brotli." You might, without knowing it

Node Brotli Vulnerability Attack Paths 2

This is the heart of the story and the reason the Node.js Brotli vulnerability deserves more than a routine version bump. You don't have to write zlib.brotliDecompress() anywhere to be exposed. There are three common doors into the vulnerable decoder:

Three common paths that hand attacker-controlled Brotli data to Node's bundled decoder.

1. Your server calls fetch()

Node's built-in fetch() is powered by the bundled undici client, and it automatically decompresses any response that says Content-Encoding: br. Crucially, it goes by what the server sends back, not by what your request asked for. One researcher tested this by sending a request that only advertised gzip and deflate to a server that replied with Brotli anyway, and fetch() decoded it without complaint. There is no option on fetch() to turn that off.

Popular HTTP clients behave similarly by default. Axios, node-fetch and got all decompress Brotli responses unless you explicitly disable it with options like decompress: false or compress: false.

That makes the riskiest workloads the ones that fetch URLs someone else chooses:

  • Link previews and URL unfurlers

  • Webhook senders that deliver to customer-configured endpoints

  • RSS and feed readers, crawlers and scrapers

  • Image proxies and "import from URL" features

  • AI agents and tools that browse or fetch arbitrary pages

If an attacker controls the server on the other end, they control the Brotli bytes your process decodes.

2. You run Express 5

Express 5 ships with body-parser 2, which added Brotli support for request bodies. That means express.json(), express.urlencoded(), express.text() and express.raw() will happily decompress a request sent with Content-Encoding: br, from any client, because the inflate option defaults to on. For an internet-facing API on Express 5, an attacker doesn't need any special access. They just POST the payload.

Express 4 users are in a different spot here: body-parser 1.x only understands gzip and deflate, so Brotli bodies get rejected. That doesn't make Express 4 apps safe overall, since fetch() calls are still a door, but it closes this particular one.

A plain Node HTTP server, on its own, does not decompress a Brotli request body just because one arrives. It takes middleware or an explicit decompression call.

3. Your code decompresses Brotli directly

The obvious case: anything that calls brotliDecompress(), brotliDecompressSync() or createBrotliDecompress() on uploads, archives, cached blobs or queue messages that originated outside your trust boundary.

A useful mental model: the question isn't "do I use Brotli?" It's "does any Node process I run ever decompress Brotli bytes that someone else chose?"

How dangerous is the Node.js Brotli vulnerability in practice?

It's worth being precise here, because the research moved quickly over 48 hours and headlines have been inconsistent.

  • Official rating: Node.js rates it High, one step below Critical. As of October 22 the CVE record was still reserved and NVD had published no CVSS score, so treat any "9.8" figures circulating on aggregator sites with caution.

  • First public proof-of-concept (October 22): reached code execution only against a deliberately weakened test build. On a standard build, its author got a crash and a limited read of process memory, but not code execution.

  • Imperva's "Node2Shell" research: went further. Imperva reported running attacker-supplied code on the official Node.js 24.22.0 image for Linux arm64 (the LTS release we covered when it shipped just days earlier), with no debugger and no custom build. It reproduced this through both the Express 5 request-body path and the fetch() response path. It also noted that execution took several attempts and that it did not test other architectures or the 22.x and 26.x lines. The patched 24.22.1 rejected the same data.

  • In the wild: no exploitation had been reported as of October 22, and the flaw was not in CISA's Known Exploited Vulnerabilities catalogue.

Put plainly: this is a patch-before-it-gets-exploited situation, not an active incident. But with a reliable-ish exploit shown on a stock build, the window for "we'll get to it next sprint" has closed.

Is there a public Node2Shell PoC on GitHub?

Yes, but it is less than the search results suggest. Here is where the public research stood as of October 22:

  • The first public proof-of-concept reached code execution only against a deliberately weakened test build of Node.js. On a standard build it produced a crash and a limited read of process memory, not a shell.

  • Imperva's Node2Shell research is the one that matters. It showed code execution on the official Node.js 24.22.0 build for Linux arm64, through both the Express 5 request-body path and the fetch() response path. It needed several attempts, and other architectures and release lines were not tested.

  • No in-the-wild attacks had been reported, and the CVE was not in CISA's Known Exploited Vulnerabilities catalogue.

Expect more "Node2Shell PoC" repositories to appear on GitHub over the coming days. Treat them with real suspicion. Fake proof-of-concept repos for headline CVEs are a long-running trick for spreading malware, and the people most likely to run them are exactly the developers and security teams trying to check their own systems.

The good news is that you don't need an exploit to know whether you're exposed. The patched decoder adds a new error constant, so this one-line check tells you whether the fix is compiled in. It decompresses nothing and is safe to run in production:

node -p "require('zlib').constants.BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH || 'missing'"
# -17      → fix included
# missing  → not patched

If you need to prove the fix to an auditor, a version check plus this constant is stronger evidence than a third-party exploit script that you can't fully read.

The other nine fixes you shouldn't skip

Brotli is getting the attention, but the same release carries three more High-severity bugs, and several of them matter most for exactly the same kinds of services: things that connect to hosts someone else picks.

CVEWhat it isSeverityAffected linesCVE-2026-111497Out-of-bounds write in the bundled Brotli decoder (Node2Shell)High22, 24, 26CVE-2026-106069TLS hostname check can be bypassed when a host loosely resolves to an IP in a non-standard formHigh22, 24, 26CVE-2026-105483TLS hostname check bypass when an IP is written in fullwidth digitsHigh24, 26CVE-2026-105482dns.resolve silently cuts a hostname short at an embedded null byteHigh22, 24, 26CVE-2026-105481Keep-alive socket can accept a response written before the request was sentMedium22, 24, 26CVE-2026-106072DNS names in a certificate can authenticate IP endpoints written in non-canonical IPv4Medium22, 24, 26CVE-2026-106074randomUUID() and randomInt() repeat the same values across processes in startup snapshots and single-executable appsMedium22, 24, 26CVE-2026-106070ZipFile returns attacker-controlled bytes after a failed header checkLow26CVE-2026-106071Permission Model grants too much file access via a wildcard lookupLow26CVE-2026-106073Permission Model bypass using fs.realpathSyncLow22, 24, 26

Two of these deserve a quick human translation:

  • The TLS bypasses. When Node connects over HTTPS, it checks that the certificate actually belongs to the host you asked for. These bugs let an oddly written IP address, for example one using fullwidth Unicode digits, slip past that check. If your service connects to hosts users supply, such as webhooks or integrations, this is the kind of gap SSRF protections can trip over.

  • The predictable randomness bug. If you build your app as a startup snapshot or a single-executable application (SEA), every process could hand out the same "random" UUIDs and integers. Anything relying on those being unpredictable, like tokens, IDs or nonces, quietly loses that guarantee.

The release also refreshes bundled dependencies: undici (the client behind fetch()), which picks up fixes for three moderate advisories including a memory-exhaustion issue in the built-in WebSocket client, plus c-ares on all lines, OpenSSL on 22.x and 24.x, and SQLite on 22.x.

The India angle: Node now blocks the compromised eMudhra keys

Tucked into the same update is something close to home for Indian developers. Node.js now refuses to trust eight intermediate certificate-authority keys from Bengaluru-based eMudhra that were found to be compromised days earlier.

Researchers discovered on October 17 that these eight intermediate certificates, created during an April 2025 key ceremony, had absurdly weak private keys. Some were tiny numbers; one was reportedly just the number 4. Anyone holding such a key could, in principle, sign certificates that browsers would trust. Chrome, Mozilla, Apple and Microsoft moved to block them between October 17 and 20, and eMudhra revoked the certificates. India's MeitY said the impact was very limited and that domestic Digital Signature Certificates and eSign services were not affected.

Why does Node need its own fix? Because Node uses Mozilla's root list but does not check certificate revocation by default. Before this update, a chain built on one of those compromised keys would still have passed. The new releases add a built-in block on the eight keys and drop three emSign roots from the bundled store. Two caveats: TLS sessions resumed from before the update aren't re-checked until they time out, and end-of-life Node versions don't get the block at all.

How to patch the Node.js Brotli vulnerability: a checklist

Bumping a version number is easy. Finding every place an old Node binary is actually running is the real work. Here's a practical order of operations.

Step 1: Confirm which builds are actually fixed

Start with the obvious check:

node -v
# Want: v22.23.4+, v24.22.1+ or v26.12.1+

Researchers also pointed out a handy secondary check. The fix introduced a new Brotli error constant, so its presence tells you the patched decoder is compiled in. It only reads a constant and decompresses nothing, so it's safe to run anywhere:

node -p "require('zlib').constants.BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH || 'missing'"
# -17      → fix included
# missing  → not patched

Machines often have more than one Node installed, so check them all, not just the first one on your PATH:

which -a node

Step 2: Patch the riskiest services first

Prioritise anything that accepts Brotli request bodies (Express 5 APIs) or fetches URLs that users supply. Those are the paths an attacker can reach directly.

Step 3: Rebuild every container image

A container keeps whatever Node it was built with, so an image built last month still carries the Node.js Brotli vulnerability even after your host is patched. Rebuild with --pull so Docker fetches the fresh base image rather than a cached one. The same rollout discipline from our PHP2Shell Docker update and rollout checks applies here too:

docker build --pull -t your-app:patched .

Watch for these traps:

  • Digest-pinned images like node:22@sha256:... never move on their own. Update the digest by hand.

  • Alpine images lagged. The official Debian-based images were rebuilt within roughly an hour of the release, but Alpine-based ones took around 12 to 13 hours. If you rebuilt an Alpine image on the afternoon of October 21, rebuild again.

  • node:20, node:18 and node:25 will stay vulnerable forever. Move to 22, 24 or 26.

Step 4: Hunt down pinned paths on servers

If you use nvm, fnm or a similar version manager, changing the default version only affects new shells. Nothing that's already running moves. A DevOps team that patched a live box on release day found old binaries hiding in several places:

  • pm2 entries with the old Node path baked in as the interpreter. Plain pm2 restart did not move them.

  • Cluster-mode pm2 apps, which only switch when the pm2 daemon itself is updated with pm2 update. Note that this restarts every app at once, so do it in a quiet window.

  • systemd units generated by pm2 startup with the old PATH hard-coded. Run pm2 unstartup then pm2 startup again.

  • Cron jobs and helper scripts pointing at the old install by absolute path.

Their key lesson: verify the binary each process is really running (on Linux, /proc/<pid>/exe), not its name. Tools like pm2 and Next.js rename their processes, so pgrep node can report zero while several old Node processes are happily running.

Step 5: Don't trust node -v on distro packages

This one catches people out. Some Linux distributions, Debian for example, build their nodejs package against the system Brotli library instead of Node's bundled copy. On those systems, the fix arrives through your distro's brotli package, not the Node version number. Similarly, FROM node:22-alpine (Node's own build) and apk add nodejs (Alpine's package) are different patch channels even on the same major version. Check your distribution's security advisories.

Step 6: Don't forget the edges

CI runners, local dev machines, single-executable apps, and Electron-style apps that bundle their own runtime all carry their own Node. Vercel's advisory explicitly lists external backends, self-hosted Next.js, custom container images, CI runners and local development as places to patch.

Node2Shell on AWS: Lambda, ECS, Beanstalk and EC2

On AWS, who patches Node.js depends entirely on how you deploy it. The quick rule: if AWS manages the runtime, AWS ships the fix; if you built the image or the server, you do.

AWS Lambda (managed Node.js runtimes)

For functions on the managed nodejs22.x or nodejs24.x runtimes, AWS rolls out patched runtime versions itself. What happens next depends on the function's runtime management setting:

  • Auto (the default): the function moves to the patched runtime automatically as AWS rolls it out.

  • Function update: the function picks up the patched runtime the next time you deploy it.

  • Manual: the function is pinned to a runtime version ARN and stays vulnerable until you change it.

[VERIFY: AWS bulletin — date the patched nodejs22.x / nodejs24.x runtime versions finished rolling out.]

To check what a function is really running, look for the INIT_START line in its CloudWatch logs, which names the exact runtime version, or log process.version from inside the handler. Functions still on nodejs20.x are on an end-of-life Node line that the Node.js project is not fixing, so move them to nodejs22.x or nodejs24.x.

The most likely door on Lambda is the fetch() path: functions that unfurl links, deliver webhooks or call URLs a customer configured. Prioritise those.

Lambda container images, ECS, Fargate, EKS and App Runner

If you ship a container image, AWS does not touch the Node inside it. Rebuild from a fresh base image and redeploy:

docker build --pull -t your-app:patched .
# push to ECR, then roll the service, for example on ECS:
aws ecs update-service --cluster your-cluster --service your-service --force-new-deployment

This includes Lambda functions deployed from public.ecr.aws/lambda/nodejs base images: they keep the Node they were built with until you rebuild and redeploy them. The digest-pinning and Alpine-lag traps from Step 3 apply here too.

Elastic Beanstalk

Beanstalk's Node.js platform versions bundle a specific Node build. If managed platform updates are enabled, the environment moves during its maintenance window; if not, update the platform version yourself from the environment's console page. [VERIFY: first Elastic Beanstalk Node.js platform version that includes 22.23.4 / 24.22.1.]

EC2 and other servers you run

Nothing on AWS's side patches these. Treat them exactly like any other server, using Steps 1, 4 and 5 above. Remember that Amazon Linux's nodejs packages come through the distro's own security advisories, not the Node version number. [VERIFY: Amazon Linux security advisory (ALAS) for this CVE.]

AWS WAF

If you can't patch an internet-facing API today, an AWS WAF rule that blocks requests whose Content-Encoding header contains br closes the inbound Express 5 path. As with any WAF, it cannot see Brotli responses your own code fetches.

Can't upgrade today? Stopgaps that actually help

Be clear-eyed about this: the Node.js advisory offers no workaround inside the runtime, and only the upgrade removes the Node.js Brotli vulnerability. But if a specific service genuinely can't move today, you can narrow the attack surface:

  • Express 5: turn off body decompression on your parsers until you upgrade. Note this also rejects gzip and deflate request bodies.

    app.use(express.json({ inflate: false }));
  • At your reverse proxy: refuse incoming requests whose Content-Encoding includes br (an HTTP 415 response is the usual choice).

  • HTTP clients: for axios, node-fetch and got, disable automatic decompression on calls to untrusted URLs. Built-in fetch() has no such switch.

  • Feature flags: temporarily disable features that fetch user-supplied URLs, such as link previews or "import from URL".

  • WAF rules: Vercel deployed rules on October 21 that block the inbound attack for every project on its platform, and Imperva added rules to its own WAF. Remember that a WAF only sees traffic coming in. It cannot protect a Brotli response your server fetches from somewhere else.

If you deploy on Vercel: projects on Node 22.x or 24.x get patched automatically, but projects still on 20.x must switch their Node version to 22.x or 24.x and redeploy.

The bigger lesson

This is the second "2Shell" bug in a single week. If you missed it, PHP2Shell hit PHP web servers just before this one. The Node.js Brotli vulnerability is a neat illustration of how modern supply-chain risk really works. The vulnerable code wasn't in Node's JavaScript and certainly wasn't in your app. It was a C library, written by another company, compiled into your runtime, and invoked silently by a convenience feature you probably never think about. The fix sat in the upstream repository for eight months before anyone labelled it a security issue.

Three habits would have made this week easier for most teams: keeping an inventory of where your runtimes actually run (not just what package.json says), staying on a supported LTS line instead of drifting past end-of-life, and treating any code path that decodes data from strangers as a security boundary. Subscribe to the low-volume nodejs-sec mailing list if you don't already, and you'll hear about the next one the day it lands.

Sources: Node.js October 2026 security releases · The Hacker News · The Hacker News: Node2Shell demo · Vercel security bulletin · DevOps Daily · WardenBit

Frequently asked questions

What is Node2Shell?

It’s the nickname for CVE-2026-111497, an out-of-bounds write in the Brotli decoder bundled with Node.js. Node.js says it can allow remote code execution.

Which Node.js versions fix it?

22.23.4, 24.22.1 and 26.12.1, all released on October 21, 2026. Versions 20.x and older will not be patched.

I don’t use Brotli. Am I safe?

Not necessarily. Node’s built-in fetch() automatically decodes Brotli responses, and so do axios, node-fetch and got by default. Express 5 also decodes Brotli request bodies by default.

Is it being exploited?

No in-the-wild attacks were reported as of October 22, 2026. However, researchers have demonstrated code execution on an official build, so you should patch now.

Is there a workaround?

Not inside Node.js. You can reduce exposure by setting inflate: false on Express 5 body parsers, blocking Content-Encoding: br requests at your proxy, and pausing features that fetch user-supplied URLs.

How do I check if my Node build is patched?

Run node -v. You can also run node -p "require('zlib').constants.BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH", which prints -17 on fixed builds.

Is there a public Node2Shell exploit on GitHub?

A first proof-of-concept was published on October 22, but it only reached code execution on a deliberately weakened test build. Imperva separately showed code execution on an official Node.js 24.22.0 build. Be careful with unofficial PoC repos: fake exploits for headline CVEs are a common way to spread malware. You can check whether you're patched without any exploit by running node -p "require('zlib').constants.BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH", which prints -17 on fixed builds.

Is AWS Lambda affected by Node2Shell?

Functions on the managed nodejs22.x and nodejs24.x runtimes are patched by AWS, automatically if runtime management is set to Auto. Functions pinned with Manual mode, functions deployed as container images, and functions still on nodejs20.x need action from you. ECS, Fargate, EKS and EC2 workloads must be rebuilt or upgraded by you.

Written by

· Senior Software Engineer

Deepak Kumar is a Senior Software Engineer based in New Delhi. He maintains production systems with millions of readers, where a missed patch is an incident rather than a headline. He started ByteMuni to publish clear, source-checked guides for working developers.