GHSA-8HRF-8G6F-7XWP

Vulnerability from github – Published: 2026-10-30 23:02 – Updated: 2026-10-30 23:02
VLAI
Summary
Forge: Denial of service in TLS record processing via a record interleaved with a fragmented handshake message
Details

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 (change_cipher_spec, alert, application_data or heartbeat), forge correctly reports "Invalid fragmented record." through c.error() with a fatal unexpected_message 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 unexpected_message 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 unexpected_message 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 change_cipher_spec, application_data 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 unexpected_message 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.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 1.4.0"
      },
      "package": {
        "ecosystem": "npm",
        "name": "node-forge"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.4.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-119188"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-248",
      "CWE-476"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-10-30T23:02:00Z",
    "nvd_published_at": "2026-10-29T20:17:29Z",
    "severity": "HIGH"
  },
  "details": "### Summary\n\nA 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.\n\n### Details\n\nIn `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 (change_cipher_spec, alert, application_data or heartbeat), forge correctly reports \"Invalid fragmented record.\" through `c.error()` with a fatal `unexpected_message` 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 \u0027ready\u0027)` out of `process()`. The code is unchanged since the first release.\n\nRFC 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 `unexpected_message` is the intended behavior; the bug is the crash after the error.\n\nnode-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.\n\nThe 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.\n\n### Patches\nFixed in node-forge 1.4.1. The connection still fails with a fatal `unexpected_message` alert and the `error` callback, but `process()` no longer throws.\n\n### Workarounds\nWrap 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.\n\n### Proof of concept\n\nInput (server mode), two records, 18 bytes:\n\n  16 03 01 00 06 01 00 00 10 03 01   handshake record: ClientHello header announcing 16 bytes, 2 present\n  15 03 01 00 02 02 28               alert record\n\n  // c = forge.tls.createConnection({server: true, ...})\n  c.process(forge.util.hexToBytes(\u00271603010006010000100301\u0027 + \u002715030100020228\u0027));\n\nOn 1.4.0 the `error` callback receives \"Invalid fragmented record.\", then `process()` throws `TypeError: Cannot set properties of null (setting \u0027ready\u0027)`. A change_cipher_spec, application_data 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(\u0027binary\u0027))` from `socket.on(\u0027data\u0027, ...)`, a client sending these 18 bytes terminates the server process. On 1.4.1 the connection fails with a fatal `unexpected_message` alert and `process()` returns normally.\n\nRegression 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.\n\n### Impact\n\nApplications that use node-forge\u0027s 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.",
  "id": "GHSA-8hrf-8g6f-7xwp",
  "modified": "2026-10-30T23:02:00Z",
  "published": "2026-10-30T23:02:00Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/digitalbazaar/forge/security/advisories/GHSA-8hrf-8g6f-7xwp"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-119188"
    },
    {
      "type": "WEB",
      "url": "https://github.com/digitalbazaar/forge/commit/700d977d39f9f8f896c55b0763eebb940a2c087e"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/digitalbazaar/forge"
    },
    {
      "type": "WEB",
      "url": "https://github.com/digitalbazaar/forge/releases/tag/v1.4.1"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Forge: Denial of service in TLS record processing via a record interleaved with a fragmented handshake message"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline

Sightings and recorded dates, grouped by day · UTC

JSON
Events included in the timeline

Loading timeline…


    Loading…

    Loading…

    Loading…

    Related by attack behaviour

    Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.