CVE-2026-68904: node-opcua: TCP Socket Leak (FIN-WAIT-2) via keepalive reconnection cycle - Resource Exhaustion

Published Sep 16, 2026
·
Updated

node-opcua is an OPC UA implementation for TypeScript and Node.js. From 2.0.0 until 2.170.0, node-opcua clients using the default keepSessionAlive setting can enter a repeated reconnection cycle when an OPC UA server's clock skew causes BadInvalidTimestamp responses. ClientSessionKeepAliveManager.pingserver treated the server-originated ServiceFault as a network outage and forced a transport reconnect, while ClientTCPtransport.onACKresponse used socket.end() after failed HEL/ACK negotiation and could leave the connection in FIN-WAIT-2 when the peer did not close. Repetition at the keepAliveInterval accumulates file descriptors and memory until the client process or container can be terminated by resource exhaustion. This issue is fixed in version 2.170.0.

Other sources

SUMMARY ------- A combination of bugs in node-opcua causes unlimited TCP socket accumulation (FIN-WAIT-2 state) during automatic reconnection, leading to memory exhaustion and eventual container/process crash (OOM kill). The issue is triggered by the default configuration (keepSessionAlive: true) when the OPC UA server has clock skew relative to the client.

Affected version: Tested on 2.169.0 (latest as of April 2026).

ENVIRONMENT ----------- - Node.js: v24.11.0 - node-opcua: 2.169.0 - OS: Linux (containerized via Podman, slirp4netns networking) - OPC UA Server: Industrial PLC (opc.tcp endpoint), clock skew of ~50 minutes ahead of client - Client config: keepSessionAlive: true (default), keepAliveInterval: 3000, securityMode: None, securityPolicy: None

ROOT CAUSE ANALYSIS -------------------

Bug #1 - ClientTCPtransport.onACKresponse() uses socket.end() instead of socket.destroy()

File: node-opcua-transport/src/clienttcptransport.ts, onACKresponse() method

When the HEL/ACK handshake fails during a reconnection attempt, the error handler calls socket.end():

if (err || !data) { externalCallback(err || new Error("no data")); if (this.socket) { this.socket.end(); // <- sends TCP FIN, leaves socket in FIN-WAIT-2 } }

socket.end() sends a TCP FIN and waits for the peer to close its side. If the peer doesn't respond (common with PLCs), the socket remains in FIN-WAIT-2 state indefinitely, leaking file descriptors and memory. During rapid reconnection cycles (triggered by Bug #2 below), every failed HEL/ACK creates a new leaked socket.

---

Bug #2 - ClientSessionKeepAliveManager.pingserver() treats BadInvalidTimestamp as network outage

File: node-opcua-client/src/clientsessionkeepalivemanager.ts, pingserver() method

The keepalive manager reads Server.ServerStatus.CurrentTime on each ping cycle. If the server responds with BadInvalidTimestamp (because the client's RequestHeader.timestamp falls outside the server's tolerance window due to clock skew), the manager treats this as a fatal network error:

// Any error -> emit("failure") -> terminateConnection() -> forceConnectionBreak()

This triggers a full transport-level reconnection on every keepalive cycle (every keepAliveInterval ms). Combined with Bug #1, each reconnection attempt leaks one TCP socket in FIN-WAIT-2. Impact amplification: With keepAliveInterval: 3000 (3 seconds), the client leaks ~20 sockets/minute, ~1200/hour, exhausting resources in hours.

REPRODUCTION STEPS ------------------ 1. Set up an OPC UA server with a clock skewed more than the server's timestamp tolerance ahead of the client. 2. Connect using node-opcua with default settings (keepSessionAlive: true). 3. Monitor TCP sockets: ss -antp | grep FIN-WAIT-2 | wc -l 4. Observe FIN-WAIT-2 count growing continuously (approximately one per keepalive interval). 5. Eventually the process runs out of file descriptors or memory and crashes.

SUGGESTED FIXES ---------------

For Bug #1 (onACKresponse):

// Replace socket.end() with socket.destroy() if (this.socket) { this.socket.destroy(); }

For Bug #2 (pingserver): Distinguish between transport-level errors (actual network outage) and application-level OPC UA status codes like BadInvalidTimestamp. The latter indicates the server is reachable and the session is alive - only the timestamp validation failed. The keepalive should not trigger reconnection.

REPORTER -------- Marco Velluso @Velluso velluso.marco64@gmail.com Requesting CVE assignment and credit as reporter upon fix publication.

GitHub

Affected Software

3 affected componentsFixes available
npm/node-opcua>=2.0.0<2.170.0
2.170.0
npm/node-opcua-client>=2.0.0<2.170.0
2.170.0
npm/node-opcua-transport>=2.0.0<2.170.0
2.170.0

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade npm/node-opcua to a version that resolves this vulnerability.

    Fixed in 2.170.0
  2. Upgrade

    Upgrade npm/node-opcua-client to a version that resolves this vulnerability.

    Fixed in 2.170.0
  3. Upgrade

    Upgrade npm/node-opcua-transport to a version that resolves this vulnerability.

    Fixed in 2.170.0
  4. Upgrade

    Upgrade node-opcua to a version that resolves this vulnerability.

    Fixed in 2.170.0
  5. Configuration

    Disable default keepalive reconnection by setting keepSessionAlive: false (material indicates the issue is triggered by the default configuration keepSessionAlive: true).

    node-opcua client configuration keepSessionAlive = false
  6. Configuration

    If keepSessionAlive cannot be disabled, reduce keepAliveInterval from 3000 (3 seconds) to limit the rate of reconnection cycles and the resulting TCP socket accumulation described in the material.

    node-opcua client configuration keepAliveInterval = 3000
  7. Compensating control

    Set up/verify correct time synchronization for the involved OPC UA server and the client to prevent BadInvalidTimestamp caused by server clock skew more than the server's timestamp tolerance window (PLC scenarios described as ~50 minutes ahead of client).

  8. Operational

    Monitor TCP sockets during affected periods: run `ss -antp | grep FIN-WAIT-2 | wc -l` and confirm the FIN-WAIT-2 count stops growing continuously.

Event History

Sep 16, 2026
CVE Published
via MITRE·04:18 PM
Data Sourced
via MITRE·04:18 PM
DescriptionSeverityWeakness
Advisory Published
via GitHub·04:19 PM
Data Sourced
via GitHub·04:19 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

Is a non-default client configuration required for this issue to occur?

No. The reported trigger uses keepSessionAlive: true, which is identified as the default configuration. The tested client also used a 3000 ms keepAliveInterval with no OPC UA security mode or security policy.

2

What server condition is associated with the failing reconnection attempts?

The issue was observed when the OPC UA server clock was skewed relative to the client. In the tested environment, the industrial PLC clock was approximately 50 minutes ahead of the client.

3

How can I tell whether a client may already be affected?

Look for an increasing number of TCP sockets in the FIN-WAIT-2 state during automatic reconnection. The reported impact is rising memory consumption followed by a container or process crash caused by an OOM kill.

4

Which components should be included in triage and inventory?

The affected software list includes npm/node-opcua, npm/node-opcua-client, and npm/node-opcua-transport. The issue was tested with node-opcua 2.169.0 on Node.js v24.11.0 in a Linux container environment.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203