GHSA-r2pf-9cw4-5j65: High severity npm/node-opcua vulnerability
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.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
npm/node-opcuato a version that resolves this vulnerability.Fixed in 2.170.0 - Upgrade
Upgrade
npm/node-opcua-clientto a version that resolves this vulnerability.Fixed in 2.170.0 - Upgrade
Upgrade
npm/node-opcua-transportto a version that resolves this vulnerability.Fixed in 2.170.0 - Upgrade
Upgrade
node-opcuato a version that resolves this vulnerability.Fixed in 2.169.0 - Configuration
Disable automatic session keepalives to avoid the reconnection cycles triggered by ClientSessionKeepAliveManager._ping_server() when it receives BadInvalidTimestamp due to clock skew (default keepSessionAlive: true is implicated).
node-opcua client keepSessionAlive = false - Configuration
If you must use keepalives, reduce keepAliveInterval from the implicated default value (3000 ms) to decrease the rate of reconnection-triggered leaked sockets during rapid failure cycles.
node-opcua client keepAliveInterval = 3000 - Configuration
Correct server time on the OPC UA server/PLC so the client's RequestHeader.timestamp does not fall outside the server's tolerance window (clock skew of ~50 minutes ahead of client was described as triggering BadInvalidTimestamp).
OPC UA Server / PLC time configuration clock skew relative to client = remove > tolerance skew - Compensating control
Monitor TCP socket accumulation to detect the issue early: run 'ss -antp | grep FIN-WAIT-2 | wc -l' and alert when FIN-WAIT-2 sockets grow continuously (approx. one per keepalive interval).
- Operational
If the container/process has crashed due to the resource exhaustion (OOM kill / file descriptor exhaustion), restart it and ensure the clock-skew/keepalive mitigation is applied before resuming.
Event History
Frequently Asked Questions
Is a default client configuration affected?
Yes. The issue is triggered when keepSessionAlive is enabled, which is the default configuration, if the OPC UA server clock is skewed relative to the client.
What conditions are needed for the resource exhaustion to occur?
The reported scenario involves automatic reconnection after a failed HEL/ACK handshake while connecting to an OPC UA server whose clock is skewed. The tested environment used a server approximately 50 minutes ahead of the client.
How can I identify a potentially affected client?
Affected clients can accumulate TCP sockets in the FIN-WAIT-2 state during reconnection attempts. This can cause increasing memory use followed by an OOM kill of the container or process.