CVE-2026-119188: Forge: Denial of service in TLS record processing via a record interleaved with a fragmented handshake message
Summary
A connection created with forge.tls.createConnection() throws an uncaught TypeError from process() when it receives a record of another content type while waiting for the rest of a handshake message that was split across records. A peer can trigger this with 18 bytes before the handshake, without authentication, which terminates Node.js processes that use the documented socket wiring. Fixed in node-forge 1.4.1.
Details
In lib/tls.js, readRecord() handles a record that arrives while a handshake message split across records is incomplete. If the record has any other content type (changecipherspec, alert, applicationdata or heartbeat), forge correctly reports "Invalid fragmented record." through c.error() with a fatal unexpectedmessage alert. That fatal error closes and resets the connection, which sets c.record to null. readRecord() then continues with c.record.ready = true and throws TypeError: Cannot set properties of null (setting 'ready') out of process(). The code is unchanged since the first release.
RFC 5246 section 6.2.1 allows a handshake message to be fragmented across records, and RFC 8446 section 5.1 forbids interleaving handshake messages with other record types. Failing the connection with unexpectedmessage is the intended behavior; the bug is the crash after the error.
node-forge reports TLS protocol errors through the error callback, so applications do not normally wrap process() in try/catch. When process() is called from a Node.js socket data handler, as in the documented usage, the exception is uncaught and terminates the process.
The fix returns from readRecord() right after the fatal error. The connection is failed, the alert is sent and the error callback is called as before, and process() no longer throws.
Patches Fixed in node-forge 1.4.1. The connection still fails with a fatal unexpectedmessage alert and the error callback, but process() no longer throws.
Workarounds Wrap every call to connection.process() in try/catch. The connection has already been failed and closed when the exception is thrown, so the exception can be ignored or the socket destroyed.
Proof of concept
Input (server mode), two records, 18 bytes:
16 03 01 00 06 01 00 00 10 03 01 handshake record: ClientHello header announcing 16 bytes, 2 present 15 03 01 00 02 02 28 alert record
// c = forge.tls.createConnection({server: true, ...}) c.process(forge.util.hexToBytes('1603010006010000100301' + '15030100020228'));
On 1.4.0 the error callback receives "Invalid fragmented record.", then process() throws TypeError: Cannot set properties of null (setting 'ready'). A changecipherspec, applicationdata or heartbeat record in place of the alert has the same effect, and a client connection behaves the same with a fragment of a ServerHello. In a Node.js server that calls c.process(data.toString('binary')) from socket.on('data', ...), a client sending these 18 bytes terminates the server process. On 1.4.1 the connection fails with a fatal unexpectedmessage alert and process() returns normally.
Regression tests in tests/unit/tls.js, "fragmented handshake messages": each of the four other record types after a handshake fragment, in server and client mode, plus same-type continuation records that are still accepted.
Impact
Applications that use node-forge's TLS implementation: connections from forge.tls.createConnection(), including those created by forge.tls.wrapSocket() and the forge.http / forge.xhr TLS connections. Any peer that can connect to a forge TLS server, or any server a forge TLS client connects to, can send 18 bytes before the handshake, without authentication. In Node.js applications using the documented usage, the uncaught exception terminates the process, which drops every connection it serves, and the attacker can repeat it on every restart. There is no confidentiality or integrity impact.
Other sources
Forge (also called node-forge) is a native implementation of Transport Layer Security in JavaScript. Prior to 1.4.1, the affected implementation used forge.tls.createConnection(), fragmented handshake message, interleaved record to process the relevant input or security decision. node-forge continued processing after a TLS record of another content type interrupted a fragmented handshake message. A remote peer could trigger an uncaught TypeError in process(), causing the connection or hosting process to fail. This issue is fixed in version 1.4.1.
— NVD
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
npm/node-forgeto a version that resolves this vulnerability.Fixed in 1.4.1 - Upgrade
Upgrade
node-forgeto a version that resolves this vulnerability.Fixed in 1.4.1 - Compensating control
Wrap every call to connection.process() in try/catch when using node-forge TLS connections.
Event History
Frequently Asked Questions
Which deployments are exposed to this denial of service?
Node.js processes using node-forge TLS connections created with forge.tls.createConnection() and the documented socket wiring are exposed. A malformed peer can terminate the process rather than only closing its own TLS connection.
Does exploitation require authentication or a valid TLS session?
No. A peer can trigger the condition before the TLS handshake with 18 bytes, and no authentication is required.
What traffic pattern triggers the crash?
The peer must cause a handshake message to be fragmented across TLS records, then send a record with a different content type while Forge is waiting for the remaining handshake fragment. The interleaved record may be change_cipher_spec, alert, application_data, or heartbeat.
What version contains the fix?
The issue is fixed in node-forge 1.4.1. The affected code had been unchanged since the first release.