Node.js Vulnerability: Node2Shell Patch and How to Check Your Node.js Version (WordPress)

Node2Shell is the nickname of CVE-2026-111497, a Node.js vulnerability whose patch was released on October 21, 2026; its official title describes an out-of-bounds write flaw in Google's Brotli decoder. Because decompressing crafted Brotli data can lead to remote code execution, the first thing to do is check the Node.js, Bun and libbrotli versions on your server and upgrade them to the patched versions.
On an Nginx + Apache server running WordPress 7.1.3, I checked which packages use the Brotli decoder and the path by which WordPress remote requests pass through libbrotli.
1️⃣ The Node2Shell Node.js vulnerability: how the flaw works and what it affects
The name Node2Shell is a nickname given by the research team at security company Imperva in a research post on October 22. The title of the nodejs.org advisory is [Brotli decoder out-of-bounds write permits remote code execution]. The flaw is in the Google Brotli decoder source bundled with Node.js (c/dec/decode.c), so changing your application code won't stop it.
According to the NVD record, the Google Brotli 1.0.3 to 1.2.0 decoder writes past the end of a buffer when it decompresses a crafted stream, corrupting the adjacent decoder state memory. Through this flaw, a remote attacker without a login can read out server memory, bring the service down and execute arbitrary code.
There are two severity ratings. The nodejs.org advisory rates it High, and Google, which issued the CVE, scored it 9.2 (Critical) on CVSS 4.0. As of October 23, NVD has not finished its own analysis, so there is no NVD score yet. The weakness types are an unchecked return value (CWE-252) and an out-of-bounds write (CWE-787).
According to the nodejs.org security advisory, all supported Node.js release lines (22.x, 24.x and 26.x) are affected, and the patched versions are 22.23.4, 24.22.1 and 26.12.1. The same advisory states that end-of-life lines are always considered affected whenever a security advisory is published.
Any program that uses the same decoder is affected, even outside Node.js. On October 21, Google released three versions of the Brotli library that fix the same CVE: 1.0.10, 1.1.1 and 1.2.1. Bun listed Brotli among the security fixes in its 1.4.4 announcement on October 22, and an issue in its repository states that this version includes the fix that updated brotli.
| destination | Affected versions | Patched version | Source |
|---|---|---|---|
| Node.js | All supported lines 22.x · 24.x · 26.x · end-of-life lines | 22.23.4 · 24.22.1 · 26.12.1 | nodejs.org security advisory (2026-10-21) |
| Google Brotli library | 1.0.3 ~ 1.2.0 | 1.0.10 · 1.1.1 · 1.2.1 | NVD record · google/brotli releases (2026-10-21) |
| Bun | Brotli in the security fix list of the 1.4.4 announcement | 1.4.4 | Bun announcement (2026-10-22) |
| Distribution libbrotli package | Packages built from Brotli 1.0.3 to 1.2.0 | The version given in the distribution's security advisory (none yet as of 2026-10-23) | Each distribution's security tracker page |
Node.js itself does not decompress the Brotli data it receives on its own. According to the security advisory from hosting company Vercel, the exposed spots are where an app or library decompresses compressed data from outside. Examples are when the built-in fetch() receives a br response and when Express 5 or body-parser 2 decompresses a br request body; the default HTTP server, which only receives data, does not decompress it.
As of October 23, no actual exploitation has been confirmed. This CVE is not in CISA's Known Exploited Vulnerabilities catalog (KEV, October 21 version). The exploitation status that CISA assigns in the CVE record changed to [proof of concept (PoC) public] on October 23.
Where the decoder lives determines what you need to do. Official Node.js binaries bundle the decoder inside the executable, while some builds, such as Debian's nodejs package, use the system libbrotli. Linux distributions install the same source as the libbrotli package, which programs such as curl share.
2️⃣ WordPress and PHP sites are affected too: the path where remote requests decompress responses with libbrotli
Even a WordPress server without Node.js has the same decoder installed as the libbrotli package. As of October 23, WordPress, curl and PHP have made no official statement about this CVE, and it is not in curl's official vulnerability list either. So this section only confirms the structure, using the core code, the curl documentation and measurements on a test server.
On the Nginx + Apache server I tested, five packages required the decoder library (libbrotlidec), and the one connected to WordPress is libcurl.
rpm -q --whatrequires 'libbrotlidec.so.1()(64bit)' | sed 's/-[0-9].*//'freetype
brotli-devel
libjxl
libcurl
libsouplibcurl requires libbrotli to decompress compressed HTTP responses. According to the official curl documentation, libcurl built with the brotli library automatically decompresses responses whose Content-Encoding is br. PHP's curl extension calls that libcurl directly.
ldd /usr/lib64/libcurl.so.4 | grep brotli libbrotlidec.so.1 => /lib64/libbrotlidec.so.1 (0x00007f32803ed000)
libbrotlicommon.so.1 => /lib64/libbrotlicommon.so.1 (0x00007f327f858000)php -i | grep BROTLIBROTLI => YesThe libcurl version WordPress uses appears in the [cURL version] row when you expand the [Server] section on the [Info] tab of [Tools › Site Health] in the admin screen. That row doesn't show whether brotli is supported, so check it with server commands as above.
The [cURL version] row in the screenshot shows the libcurl version along with the SSL library it was built with. According to the curl documentation, br is supported from 7.57.0, so if this value is lower, libcurl can't decompress br responses. If it is 7.57.0 or higher, check with server commands whether brotli was included in the build.
When WordPress core's Requests library sends a request to an external URL, it sets the curl handle's [CURLOPT_ENCODING] value to an empty string. According to the curl documentation, an empty string means accepting every compression method that libcurl supports. So if the other server sends a br-compressed response, the WordPress server's libbrotli decompresses it.
grep -n CURLOPT_ENCODING wp-includes/Requests/src/Transport/Curl.php110: curl_setopt($this->handle, CURLOPT_ENCODING, '');
212: curl_setopt($this->handle, CURLOPT_ENCODING, 'none');Line 110 is the default for every request, and line 212 runs only when a request that failed to decompress is sent again without compression. There is no way to hit it directly from outside; a server that WordPress fetches responses from, such as for core and plugin update checks, oEmbed or RSS feeds, would have to be taken over first.
The flow starts with br-compressed data from outside. The flaw is triggered at the step where Node.js's fetch(), Express 5, Bun's fetch() or the libbrotli called by WordPress's libcurl decompresses it. The result leads to code execution on the server. The patched versions block this flow, so what you need to do is replace the decoder with a patched version.
Apache's mod_brotli links only the encoder library (libbrotlienc) that compresses responses, so a setup that enables compression with this module is not on the decoder flaw's path.
ldd /usr/lib64/httpd/modules/mod_brotli.so | grep brotli libbrotlienc.so.1 => /lib64/libbrotlienc.so.1 (0x00007f34b4b79000)
libbrotlicommon.so.1 => /lib64/libbrotlicommon.so.1 (0x00007f34b4a79000)3️⃣ Check your Node.js version: whether the server has Node.js, Bun and libbrotli, and which versions
Start by taking stock of what is installed on the server. For Node.js, check the version with [node -v]; if the value is below 22.23.4, 24.22.1 or 26.12.1 for its line, it is affected.
| Your environment | Command to check | What gets fixed |
|---|---|---|
| nodejs.org installer · nvm · official Docker image | [node -v] | Node.js itself (22.23.4 · 24.22.1 · 26.12.1) |
| Distribution nodejs · libbrotli line in ldd | [ldd $(command -v node)] | libbrotli package |
| Distribution nodejs · ldd result 0 | [ldd $(command -v node)] | nodejs package (after the distribution's security advisory) |
| Shared hosting Node.js Selector (CloudLinux alt-nodejs) | Provider announcement · package release number | Package deployed by the provider + app restart |
| Bun | [bun --version] | Bun 1.4.4 or later |
| No Node.js (WordPress only) | Check libcurl with [ldd] | libbrotli package + php-fpm restart · temporary mitigation until then |
| Managed hosting | Provider announcement · support inquiry | Packages the provider upgrades |
On a PC (Windows or macOS), reinstall with the patched-version installer from nodejs.org; with nvm, check the version with [nvm ls] and then reinstall by line number, as in [nvm install 24]. The 22, 24 and 26 tags of the official Docker node image were rebuilt with the patched versions on October 22, so pull the image again, rebuild and deploy. The Vercel advisory also recommends upgrading self-built container images, CI environments and local development environments to the patched versions.
On a server where you installed Node.js from a dnf module stream, [node -v] shows a version from that stream's line; even if the line matches, upgrade if the last digits fall short of the patched version.
node -vv24.xx.xUse [ldd] to check whether Node.js uses the system libbrotli or its bundled decoder. If the output has a libbrotlidec line, the build uses the shared library; if it is 0, as below, the decoder is bundled, and only upgrading Node.js itself fixes it.
ldd $(command -v node) | grep -c brotli0For Bun, check the version with [bun --version] and upgrade if it is lower than 1.4.4.
On RPM-based systems, check the libbrotli package version with [rpm -q libbrotli]. Package version numbers are assigned by the distribution, so don't compare them directly with Google's release numbers; compare them with the version the distribution's security advisory issues for this CVE number.
rpm -q libbrotlilibbrotli-1.0.x-x.el9_x.x86_64Check whether the distribution has published an advisory with [dnf updateinfo info --cve CVE-2026-111497]. If the output is empty, that repository has no advisory for this CVE yet.
dnf updateinfo info --cve CVE-2026-111497A package that only backports the fix while keeping the same version number leaves the CVE number in its changelog. So check with [rpm -q --changelog libbrotli | grep CVE-2026-111497]; if a line appears, the package is patched.
rpm -q --changelog libbrotli | grep CVE-2026-111497As of October 23, the libbrotli packages for Debian, Ubuntu, Red Hat and AlmaLinux do not yet have a patched version for this CVE. Alpine has a patched version only for its nodejs package; the source is each distribution's security tracker page. So for now, the order is to watch for the distribution's security advisory and upgrade once it is out.
On Debian and Ubuntu, the package name is libbrotli1, and I checked the same items on a Debian 12 test server at a managed hosting provider. On this server too, libcurl linked libbrotlidec and PHP's curl extension supported brotli. View the changelog with [zcat /usr/share/doc/libbrotli1/changelog.Debian.gz | grep CVE-2026-111497].
dpkg -l libbrotli1 | tail -1ii libbrotli1:amd64 1.1.0-2+0~20240305.8+debian12~1.gbp62df71 amd64 library implementing brotli encoder and decoder (shared libraries)ldd /usr/lib/x86_64-linux-gnu/libcurl.so.4 | grep brotli libbrotlidec.so.1 => /lib/x86_64-linux-gnu/libbrotlidec.so.1 (0x00007fa2baac6000)
libbrotlicommon.so.1 => /lib/x86_64-linux-gnu/libbrotlicommon.so.1 (0x00007fa2b9cb9000)4️⃣ Update Node.js: upgrade to the patched version and check again
On managed and shared hosting, the provider upgrades the server packages, so confirm through the provider's announcements or support whether Node.js and libbrotli have been patched. If you run the server yourself, upgrade Node.js to the patched version in the nodejs.org advisory, and Bun to 1.4.4 or later with [bun upgrade].
Node.js installed from a distribution module stream is upgraded with a command like [dnf upgrade nodejs] once the patched version reaches that repository. On a server where you downloaded and installed Node.js directly from nodejs.org, replace it with the patched version from the same place. End-of-life lines get no patched version, so move to a supported line: 22, 24 or 26.
The Node.js Selector on shared hosting (CloudLinux alt-nodejs) carries its own copy of brotli, so upgrading the system libbrotli doesn't fix it. According to the CloudLinux announcement, the new binary runs only after the package deployed by the provider is in place and the app is restarted. The same announcement says that for end-of-life versions the [node --version] value stays the same, so you check the package release number instead.
For the libbrotli package, once the distribution's security advisory is out, upgrade the package and restart the processes that have the library loaded in memory so they switch to the new library. Shared libraries are read when a process starts, so on a WordPress server the php-fpm restart is part of the same step.
After upgrading, check that [node -v] shows the patched version or later and that [rpm -q libbrotli] shows the version in the distribution's advisory. Check whether the CVE number made it into the changelog with [rpm -q --changelog libbrotli | grep CVE-2026-111497]. If you restarted php-fpm, also check the service status and the site's response.
systemctl is-active php-fpmactivecurl -s -o /dev/null -w '%{http_code}\n' 'https://example.com/?r=1'2005️⃣ Temporary mitigation before patching: turning off br decompression in WordPress remote requests
Red Hat's CVE page recommends configuring apps not to accept, or not to automatically decompress, br compression from untrusted sources.
Even before patched packages are out, you can block the WordPress-side path now. WordPress provides the [http_api_curl] hook, which passes the curl handle for every remote request. Setting [CURLOPT_ENCODING] to null there turns off libcurl's automatic decompression.
Placing the file below in the [wp-content/mu-plugins/] folder applies it to every remote request; the file name is just an example. To undo it, delete this file.
<?php
// no-brotli-decode.php — WordPress remote in requests libcurl Auto decompression Off(libbrotli patch version released until)
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
add_action( 'http_api_curl', function ( $handle ) {
curl_setopt( $handle, CURLOPT_ENCODING, null );
}, 99 );It is easy to think that narrowing [CURLOPT_ENCODING] to [gzip, deflate] leaves br out. That value only removes br from the request's Accept-Encoding header, so if the other server answers with br, libcurl decompresses what it receives anyway. On the test site, fetching httpbin.org/brotli with that same value returned a response with Content-Encoding br that was decompressed and readable as is.
According to the curl documentation, null is the only value that turns off automatic decompression; with null, libcurl doesn't send the Accept-Encoding header and doesn't decompress the body it receives either. gzip and deflate responses are decompressed by WordPress's Requests library itself and stay readable; only br responses remain undecompressed.
This hook is what WordPress attached to the [curl.before_send] point, which Requests calls just before running curl. It is called on line 174 of Curl.php and curl runs on line 205, so the empty-string value that core set on line 110 is overwritten in between.
Check whether it applies with the test file below. httpbin.org is a test address that returns the request headers it received and also returns br- and gzip-compressed responses, so you can see at once what WordPress sends and what it decompresses.
<?php
// br-check.php — WordPress remote request which compression sends whether it decompresses Check(httpbin.org test Address)
$h = json_decode( wp_remote_retrieve_body( wp_remote_get( 'https://httpbin.org/headers' ) ), true );
echo 'Accept-Encoding: ', isset( $h['headers']['Accept-Encoding'] ) ? $h['headers']['Accept-Encoding'] : '(none)', PHP_EOL;
foreach ( array( 'brotli', 'gzip' ) as $p ) {
$r = wp_remote_get( 'https://httpbin.org/' . $p );
$readable = strpos( wp_remote_retrieve_body( $r ), ': true' ) !== false;
echo $p, ': Content-Encoding=', wp_remote_retrieve_header( $r, 'content-encoding' ), ' / ', strlen( wp_remote_retrieve_body( $r ) ), ' bytes / ', ( $readable ? 'readable' : 'not decoded' ), PHP_EOL;
}Before adding the file, Accept-Encoding includes br, and the br response is decompressed and readable.
wp eval-file br-check.phpAccept-Encoding: deflate, gzip, br
brotli: Content-Encoding=br / 316 bytes / readable
gzip: Content-Encoding=gzip / 317 bytes / readableAfter adding the file, the Accept-Encoding header is gone, the br response stays undecompressed, and the gzip response is still readable.
wp eval-file br-check.phpAccept-Encoding: (none)
brotli: Content-Encoding=br / 199 bytes / not decoded
gzip: Content-Encoding=gzip / 272 bytes / readableOnce you have upgraded to the distribution's patched libbrotli package and restarted php-fpm, delete this file to go back to the original setup.
🔢 FAQ & Recommended Content
Security
OpenSSH Vulnerability CVE-2024-6387
How I temporarily blocked the OpenSSH flaw, then applied the patch and verified it.
Security
WP Security: wp-admin Security
Settings that protect the admin screen by changing the login URL, limiting login attempts and blocking XML-RPC.
Server
Alma Linux Update: 9.4 → 9.8
The steps I took to upgrade AlmaLinux 9.4 to 9.8 and what to check.
ℹ️ Affiliate Disclosure
This site's content contains affiliate links. When a visitor buys a product or service through one of them, the site receives a commission from the seller. The amount the buyer pays(it goes down during event discounts ↓)does not go up. Posted prices, discounts, and stock reflect the time of writing and may differ, so confirm with the seller before buying. Products are chosen and reviewed by our own standards, and commissions do not affect the order or content of reviews.