CVE-2026-48931
This Vulnerability has been fixed in the Never-Ending Support (NES) version offered by HeroDevs.
Overview
Node.js is a JavaScript runtime built on Chrome's V8 engine, widely used to build servers, command line tools, and back-end services. The built-in http.Agent class manages the pooling and reuse of TCP connections (keep-alive sockets) for outgoing HTTP client requests.
A time-of-check to time-of-use (TOCTOU) race condition (CVE-2026-48931) has been identified in Node.js, which allows a client to accept an HTTP response that was sent before the client actually issued the corresponding request. This can lead to HTTP response queue poisoning, where responses on a reused keep-alive connection become misaligned with the requests that were actually sent.
HTTP/1.1 has no response identifiers: a client matches a response to a request purely by ordering on the connection. When a socket is returned to the http.Agent free pool and reused, that positional contract is the only thing binding response N to request N. If an upstream writes an extra, unsolicited response onto an idle pooled socket, the next request issued on that connection will be matched against the wrong response, shifting every subsequent response by one and breaking the expected request/response sequencing.
This issue affects all currently supported Node.js release lines.
Details
Module Info
- Product: Node.js
- Upstream component: Node.js core http module (http.Agent connection pooling)
- Affected versions:
- Node.js 22.x through v22.22.3; v24.x through v24.16.0; v26.x through v26.3.0
- GitHub repository: https://github.com/nodejs/node
- Fixed in:
- OSS Node.js v22.23.0, v24.17.0, and v26.3.1.
- Node.js NES v12.22.19, v14.21.12, v16.20.13, v18.20.18, v20.20.3
- Custom NES: v20.20.10 and v22.22.10
Vulnerability Info
This Low-severity vulnerability is found in the core http module's connection-pooling logic, which ships with every Node.js installation.
Before the fix, when a socket was returned to the http.Agent free pool for reuse, Node.js detached the HTTP parser and left the socket paused, attaching only an error listener. Any unsolicited bytes written by the server while the socket sat idle in freeSockets were buffered silently and consumed as the response to the next request issued on that connection — with no validation that the response actually corresponded to a request the client had sent.
An attacker-controlled or compromised upstream server can exploit this by writing a syntactically valid, unsolicited HTTP/1.1 response onto a keep-alive socket after completing a legitimate response, while that socket is idle in the connection pool. When the client's http.Agent later reuses the socket for a new request, the pre-staged bytes are parsed as the response to that request, poisoning the response queue and causing responses to be permanently misattributed on that connection.
The fix introduces a data guard (freeSocketDataGuard) on idle pooled sockets: any unsolicited byte received while a socket sits in the free pool now destroys the socket instead of allowing it to be reused, closing the deterministic, idle-window version of the race.
Steps To Reproduce
- Create a Node.js HTTP client using the default http.Agent with keep-alive enabled, connecting to a server under attacker control.
- Have the server complete a normal response to request 1, then immediately write a second, unsolicited, well-formed HTTP response on the same socket before it is returned to the client's free socket pool.
- Issue request 2 from the client. Prior to the fix, http.Agent reuses the poisoned socket and the client parses the attacker's pre-staged bytes as the response to request 2, rather than the response the server actually sends for request 2.
Proof Of Concept
The researcher's technical writeup is documented here: CVE-2026-48931 Shouldn’t Have Been a CVE – Adventures in Nodeland
Mitigation
This is a property of HTTP/1.1 connection reuse rather than a defect unique to Node.js — every major HTTP/1.1 client (curl, Go net/http, urllib3, OkHttp, JDK HttpClient, Apache HttpClient, Jetty) faces the same structural race when a connection is reused against an untrusted peer. The Node.js guard reduces — but, as the maintainers themselves note, cannot fully eliminate — the exposure, since a check-then-reuse decision can never be made perfectly atomic against an attacker who controls the timing of writes on a full-duplex socket.
Users of affected Node.js versions should apply one or more of the following mitigations:
- Upgrade to Node.js v22.23.0, v24.17.0, v26.3.1, or later, which include the freeSocketDataGuard hardening.
- Avoid reusing pooled, positionally-ordered HTTP/1.1 connections against upstreams that are not fully trusted, especially where a connection pool is shared across different users or trust levels.
- Where possible, prefer HTTP/2 or HTTP/3 for connections to untrusted or semi-trusted origins; stream IDs decouple response identity from socket byte position and remove this class of issue entirely.
- Leverage a commercial support partner like HeroDevs for post-EOL security support.
Credits
- yushengchen (reporter)
- Matteo Collina (fix)