Sep 16, 2026·8 min read·51 visits
A socket leak and logic comparison bug in node-opcua client keepalives triggers an infinite, high-frequency reconnection storm. The improper socket teardown leaves connections in FIN-WAIT-2 indefinitely, exhausting system file descriptors and causing client-side crashes.
CVE-2026-68904 is a high-severity Denial of Service (DoS) vulnerability in the node-opcua library. It arises from a logical flaw in the keepalive session manager combined with incorrect socket termination at the TCP transport layer. When server-side anomalies occur, affected clients fall into an infinite, high-frequency reconnection loop. Due to the use of graceful teardown (socket.end) instead of immediate termination (socket.destroy) during negotiation failures, sockets remain open in the FIN-WAIT-2 state. This accumulates system file descriptors and memory, eventually crashing the client process.
The node-opcua library is an open-source, full-stack implementation of the OPC Unified Architecture (OPC UA) standard, written in TypeScript and executed within the Node.js runtime environment. OPC UA is a machine-to-machine communication protocol utilized heavily in industrial automation, supervisory control and data acquisition (SCADA) systems, and smart manufacturing ecosystems. In these contexts, node-opcua clients serve as the critical bridge between control networks and enterprise applications, requesting real-time telemetry from programmable logic controllers (PLCs) and industrial servers.
To maintain persistent and reliable communication sessions across potentially unstable network environments, the library implements a background session manager. This manager periodically polls the remote server to verify the active state of the session—a process designated as the keepalive transaction. The attack surface is exposed via this keepalive and the underlying TCP transport layer, where client-side resource management mistakes can have significant operational consequences.
CVE-2026-68904 represents a high-severity Denial of Service (DoS) vulnerability categorized under CWE-400 (Uncontrolled Resource Consumption). The flaw lies in the convergence of a logical misclassification of application-level faults as network disconnects, an object-identity comparison error, and improper transport socket disposal. This configuration triggers a rapid reconnection cycle where sockets are left dangling, leading to complete client process failure due to file descriptor and memory exhaustion.
The technical root cause of CVE-2026-68904 is divided into two distinct logical errors working in tandem: one at the application layer and one at the transport layer. The application-layer issue begins in the ClientSessionKeepAliveManager which monitors the integrity of active sessions. During a standard keepalive check, the client reads the status of a specific node on the server (typically the server status variable). If an error occurs during this transaction, the client evaluates whether the session is still valid or has terminated.
In vulnerable versions, the keepalive manager evaluates the returned error status against expected failure states using strict reference equality (===) on complex StatusCodes objects. Because of Node.js object instantiating patterns, the status code returned in the error response might be a separate object instance with the same value, causing the identity comparison to fail. Consequently, when the server returns expected errors indicating the session is invalid, the check fails to identify them, falling back to a generic network-failure routine that forcefully tears down the channel.
This behavior is compounded by a second logic flaw: the inability to distinguish between transport-level failures and valid application-level failures. For example, if a server's system clock drifts, it will successfully return a valid OPC UA ServiceFault containing the status code BadInvalidTimestamp. Although this fault represents a correct cryptographic/protocol response indicating the session is intact but experiencing clock skew, the client's keepalive manager misinterprets it as a severe network outage. This misinterpretation triggers an immediate transport teardown and an aggressive reconnection sequence.
The transport layer exacerbates these errors during the socket teardown process. When the client initiates a reconnection, it attempts to negotiate a new OPC UA transport session via a HEL/ACK (Hello/Acknowledge) handshake. If this handshake fails, the _on_ACK_response handler initiates socket closure using Node.js's socket.end(). In Node.js, socket.end() initiates a half-close sequence, transmitting a TCP FIN packet and waiting for the peer to acknowledge and transmit its own FIN packet. If the remote peer fails to respond (due to load, network conditions, or malicious design), the client socket remains trapped in the FIN-WAIT-2 state indefinitely, leaking both memory and system file descriptors.
To understand the mechanical changes applied to address these flaws, we analyze the specific code differentials implemented across the affected modules. In the transport-layer module (packages/node-opcua-transport/source/client_tcp_transport.ts), the patch forces immediate, ungraceful closure of failing sockets instead of attempting a polite half-close. By replacing socket.end() with socket.destroy(), the system forcefully tears down the socket state machine and releases operating system resources.
// Packages/node-opcua-transport/source/client_tcp_transport.ts
// Before the patch:
if (this._socket) {
this._socket.end();
}
// After the patch:
if (this._socket) {
const s = this._socket;
this._socket = null; // Prevent re-entrancy bugs
s.destroy(); // Force immediate connection termination
}At the application layer, the strict reference equality issue was corrected by replacing direct identity comparisons with structural equality checks using the .equals() method. This ensures that even if the returned StatusCodes represent separate memory allocations, they are evaluated correctly based on their underlying status value. Additionally, a new interface ServiceFaultAnnotatedError is introduced to allow the client to extract the precise serviceResult status code from server response headers, allowing the client to differentiate between transient transport drops and valid application faults.
// Packages/node-opcua-client/source/client_session_keepalive_manager.ts
// Strict object reference check fix:
const serviceFaultResponse = (err as ServiceFaultAnnotatedError).response;
if (serviceFaultResponse) {
const sc = serviceFaultResponse.responseHeader?.serviceResult;
// Before: sc === StatusCodes.BadSessionIdInvalid || sc === StatusCodes.BadSessionClosed
if (sc?.equals(StatusCodes.BadSessionIdInvalid) || sc?.equals(StatusCodes.BadSessionClosed)) {
this.emit("failure");
terminateConnection(session._client);
resolve(0);
} else {
this.consecutiveFailures++;
this.emit("keepalive_failure");
// Resolve with backoff algorithm
resolve(Math.min(this.checkInterval * 2 ** this.consecutiveFailures, maxBackoffInterval));
}
}Finally, the keepalive manager was refactored to introduce an exponential backoff mechanism. Consecutive failures increment a consecutiveFailures counter, which mathematically increases the retry delay up to a hard cap of 60 seconds (maxBackoffInterval). This stops the client from generating an immediate, high-frequency reconnection storm in the event of persistent server faults or clock drift.
Exploitation of CVE-2026-68904 does not require the injection of shellcode or memory manipulation payloads. Instead, the vulnerability is triggered through external environmental manipulation or protocol abuse that forces the client into the vulnerable reconnection cycle. A prime methodology involves manipulating network-level variables to induce a clock skew on either the client or server host. If the client clock drifts relative to the server, subsequent keepalive requests trigger a BadInvalidTimestamp ServiceFault, initiating the infinite loop.
Alternatively, an attacker positioned as a man-in-the-middle or controlling an unstable destination gateway can systematically trigger the socket leak. By allowing the initial TCP connection but refusing to complete or cleanly terminate the OPC UA HEL/ACK handshake, the attacker forces the client to invoke its handshake error handler. Because the client closed these connections via socket.end(), and the attacker intentionally refuses to transmit the corresponding FIN packet, the client's host operating system accumulates sockets in the FIN-WAIT-2 state.
The rapid rate of the reconnection attempts (lacking any backoff) causes the client to exhaust its allocated file descriptors within minutes. Once the limit is reached, the Node.js runtime is unable to open new file descriptors for standard operations, leading to unhandled exceptions and process termination. This represents a highly reliable, low-complexity Denial of Service vector against critical industrial monitoring clients.
The impact of CVE-2026-68904 is classified primarily as a high-severity Denial of Service (DoS) affecting the availability of industrial automation data flows. In SCADA environments, client processes are responsible for logging telemetry, monitoring safety parameters, and bridging industrial controllers to databases. If the client process terminates or becomes unresponsive, operators lose visibility into physical processes, which can lead to delayed incident response or operational disruptions.
The CVSS v3.1 base score is evaluated at 7.0 (High), with a vector of CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:H. The attack complexity is rated as high because triggering the fault requires specific, non-default conditions such as clock drift, transport failures, or controlled TCP behavior from the server. However, the impact on availability is complete, as the client process will inevitably crash due to host-level resource exhaustion.
Because Node.js processes typically execute within containerized environments (e.g., Docker or Kubernetes), a local resource exhaustion bug can have cascading effects. A single container leaking file descriptors and memory can saturate the host operating system's socket table or exceed container-group limits. This can cause adjacent containers or the orchestration node itself to experience degradation, magnifying the overall operational impact.
The primary remediation pathway is upgrading the node-opcua dependency to version 2.170.0 or later. This version contains the comprehensive set of patches that address the strict equality failure, implement the .equals() comparison, introduce the exponential backoff, and force ungraceful TCP socket destruction (socket.destroy()) on handshake failure. Projects consuming this library should update their package.json lockfiles and rebuild their deployment artifacts immediately.
In environments where upgrading the library is not immediately feasible, system-level workarounds can mitigate the risk of file descriptor exhaustion. System administrators can harden the host operating system's TCP timeout parameters. Specifically, reducing the tcp_fin_timeout parameter forces the kernel to reclaim sockets stuck in the FIN-WAIT-2 state much faster than the default 60-second window, mitigating the rate of the resource leak.
# Temporarily reduce FIN-WAIT-2 timeout to 15 seconds
sysctl -w net.ipv4.tcp_fin_timeout=15Additionally, network-level validation should be implemented to ensure tight NTP synchronization between the OPC UA client host and server hosts. Keeping clock drift to a minimum reduces the probability of triggering BadInvalidTimestamp errors. Finally, monitoring tools should track the quantity of active TCP connections in the FIN_WAIT_2 state to identify ongoing exploit attempts or environment instability before a process crash occurs.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
node-opcua node-opcua | >= 2.0.0, < 2.170.0 | 2.170.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-400: Uncontrolled Resource Consumption |
| Attack Vector | Network |
| CVSS v3.1 Score | 7.0 (High) |
| EPSS Score | Not Available |
| Impact | Availability (High) - Client Process Crash |
| Exploit Status | PoC / Functional Test Available |
| KEV Status | Not Listed |
The product does not properly control the allocation and maintenance of a limited resource, enabling an actor to influence the amount of resources consumed, eventually leading to exhaustion.
CVE-2026-118777 is a prototype pollution vulnerability (CWE-1321) in the Forge JavaScript cryptography and networking library (distributed on npm as node-forge) prior to version 1.4.1. The flaw resides in the forge.http client subsystem, where incoming HTTP cookie names and paths parsed from Set-Cookie response headers are stored directly into standard JavaScript objects without key validation or property isolation. By returning crafted Set-Cookie headers containing keys such as __proto__ or constructor, a malicious HTTP server can inject arbitrary properties into Object.prototype, attach attributes to the global Object constructor, or delete built-in prototype methods to cause an application-wide denial of service.
CVE-2026-118775 (GHSA-4642-5mcj-2rqw) is an integer overflow and validation flaw in the ASN.1 serialization module of Forge (node-forge) prior to version 1.4.1. The asn1.oidToDer() function fails to validate the range of the first two arcs of an Object Identifier (OID) and truncates the packed initial subidentifier to a single 8-bit integer. Applications that perform policy checks on untrusted OID strings before encoding them into Distinguished Encoding Rules (DER) format can be tricked into emitting DER bytes that correspond to a completely different, restricted OID.
CVE-2026-119194 is an algorithmic complexity vulnerability (CWE-407) in the pyasn1 library for Python, affecting versions 0.3.3 through 0.6.4. The flaw resides in the schemaless decoding routine ConstructedPayloadDecoderBase._decodeComponentsSchemaless used across BER, CER, and DER codecs. By sending an ASN.1 container populated with child components whose tag identifiers are separated by multiples of CPython's integer hash modulus (sys.hash_info.modulus), an unauthenticated attacker can force worst-case hash collisions in an unbounded Python set, degrading decoding performance from O(N) to O(N^2) and causing a Denial of Service (DoS).
CVE-2026-119195 is an algorithmic complexity vulnerability (CWE-407) in the pyasn1 library for Python, affecting all versions prior to 0.6.5. When decoding constructed strings or indefinite-length ANY types across BER, CER, and certain DER code paths, the payload decoders repeatedly concatenate incoming fragments onto an immutable bytes object using the += operator. Because Python bytes objects cannot be resized in-place, each fragment iteration allocates a new buffer and copies all previously accumulated bytes, resulting in quadratic O(N^2) time and memory copy complexity. An unauthenticated remote attacker can supply a highly fragmented ASN.1 stream to exhaust CPU cycles and stall application worker threads.
An algorithmic complexity vulnerability (CWE-407) in pyasn1 versions prior to 0.6.5 allows unauthenticated attackers to cause a Denial of Service (DoS) via crafted ASN.1 payloads. The flaw resides in the binary-encoded ASN.1 REAL mantissa deserialization routine within RealPayloadDecoder.valueDecoder. By performing iterative byte-string slicing and repeated bitwise shifts on a growing arbitrary-precision integer, the decoder exhibits quadratic time and memory allocation complexity relative to the mantissa length. Supplying a BER-, CER-, or DER-encoded payload with a large REAL mantissa causes excessive CPU consumption and blocks application worker processes.
A vulnerability classified under CWE-407 (Inefficient Algorithmic Complexity) exists in the pyasn1 library prior to version 0.6.5. When parsing constructed ASN.1 BIT STRING payloads under BER, CER, or schema-driven DER encoding rules, the BitStringPayloadDecoder repeatedly shifts and copies the entire accumulated bit value for each incoming fragment. Processing inputs with a large number of small constructed fragments induces quadratic CPU consumption, allowing unauthenticated remote attackers to cause a denial of service via resource exhaustion during the initial decoding phase.