{"id":"CVE-2026-119187","aliases":["GHSA-hqq3-7jhf-c5jx"],"title":"Forge: Denial of service in TLS record processing via call stack exhaustion","titleSource":"GitHub_M","descriptionSource":"OSV","declaredSeverity":"HIGH","description":"### Summary\n\nConnections created with `forge.tls.createConnection()` processed received TLS records recursively, one stack level per record. A peer can send one chunk of thousands of small records, such as heartbeat records, which are accepted before the handshake, and `process()` throws `RangeError: Maximum call stack size exceeded`, 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`, `c.process()` reads one record and passes it to `_update()`, which calls the handler for the record type from `ctTable`. Every handler (`tls.handleHeartbeat`, `tls.handleApplicationData`, `tls.handleHandshake` for fragmented messages, `tls.handleHelloRequest` and the other handshake handlers) ends by calling `c.process()` again to continue with the next buffered record, or the next message in the same record. Each record therefore adds `process` → `_update` → handler to the call stack. In Node.js 24, about 2,300 eight-byte records (under 20 KB) in one chunk are enough for `process()` to throw `RangeError: Maximum call stack size exceeded`.\n\n`ctTable` maps heartbeat records to `tls.handleHeartbeat` in every client and server state, so heartbeat records are processed before the handshake without any extension negotiation. The attack needs no authentication and no valid handshake messages. Both server connections (attacked by a client) and client connections (attacked by the server they connect to) are affected. Other record types recurse the same way: a handshake message fragmented over many records, many HelloRequest messages in one record, or many application data records after the handshake.\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. If the exception is caught, the connection is left partially processed and should be discarded.\n\nNo specification is violated; this is an implementation flaw (uncontrolled recursion).\n\nThe fix makes `c.process()` iterative. The record-reading code moves into `_processRecord()`, and `c.process()` runs it in a loop. A handler's call to `c.process()` without new data, made while records are being processed, now only flags that the loop should continue. Calls with new data are processed right away as before, so callback order and return values are unchanged.\n\n### Patches\nFixed in node-forge 1.4.1. Records are now processed in a loop, so the stack depth no longer depends on the number of records.\n\n### Workarounds\nWrap every call to `connection.process()` in `try`/`catch`, and on an exception close the connection (`connection.close()`) and destroy the underlying socket. This prevents the process crash but not the loss of the attacked connection.\n\n### Credits\nReported jointly by Ziyue Wang, Chenchen, Strick Sheng, Liyi Zhou, and Maurice Ng.\n\n### Proof of concept\n\nInput: one chunk of N identical heartbeat records, each 8 bytes:\n\n  18 03 01 00 03 01 00 00\n  (content type 24 = heartbeat, TLS 1.0, length 3, heartbeat_request with a 0-byte payload)\n\n  // c = forge.tls.createConnection({server: true, ...})\n  var rec = forge.util.hexToBytes('1803010003010000');\n  c.process(new Array(5001).join(rec)); // 5,000 records, 40,000 bytes\n\nOn 1.4.0 (Node.js 24), `process()` throws `RangeError: Maximum call stack size exceeded` after about 2,300 records, before any handshake. The same happens for a client connection. In a Node.js server that calls `c.process(data.toString('binary'))` from `socket.on('data', ...)`, a client sending these 40,000 bytes terminates the server process. On 1.4.1 all records are processed and the server keeps running.\n\nRegression tests in tests/unit/tls.js, \"record processing\": 50,000 heartbeat records in one call in server and client mode, a handshake message fragmented over 50,000 records, many HelloRequest messages per record, and checks that byte-by-byte input and exceptions from callbacks behave as before.\n\n### Impact\n\nApplications 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, which call `process()` on received data. Any peer that can connect to a forge TLS server, or any server a forge TLS client connects to, can send a small chunk of records (under 20 KB) without authentication. In Node.js applications using the documented usage, the uncaught exception terminates the process, which drops every connection it serves; repeated chunks keep it down. In browsers the impact is limited to the attacked connection. There is no confidentiality or integrity impact.","severity":"high","score":7.5,"published":"2026-10-29T19:19:36.824Z","modified":"2026-10-30T23:15:07.576Z","withdrawn":false,"knownExploited":false,"affected":[{"ecosystem":"npm","name":"node-forge","sources":[{"name":"OSV · GHSA-hqq3-7jhf-c5jx","url":"https://osv.dev/vulnerability/GHSA-hqq3-7jhf-c5jx"}],"ranges":[{"events":[{"introduced":"0"},{"fixed":"1.4.1"}],"type":"SEMVER"}],"versions":[],"fixed":["1.4.1"]}],"sourceAffected":[],"references":["https://github.com/digitalbazaar/forge/security/advisories/GHSA-hqq3-7jhf-c5jx","https://github.com/digitalbazaar/forge/commit/817e2ce0d0f6d8a25bc9c2670615c38a9ffdc51c","https://github.com/digitalbazaar/forge/releases/tag/v1.4.1","https://nvd.nist.gov/vuln/detail/CVE-2026-119187","https://github.com/digitalbazaar/forge"],"sources":[{"name":"CVE","id":"CVE-2026-119187","modified":"2026-10-29T19:19:36.824Z","url":"https://www.cve.org/CVERecord?id=CVE-2026-119187"},{"name":"OSV","id":"GHSA-hqq3-7jhf-c5jx","modified":"2026-10-30T23:15:07.576305226Z","url":"https://osv.dev/vulnerability/GHSA-hqq3-7jhf-c5jx"}],"assessments":[{"source":"GitHub_M","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","score":7.5},{"source":"OSV","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","score":7.5}],"cve":{"records":[{"id":"CVE-2026-119187","source":"GitHub_M","state":"PUBLISHED","reasons":[],"replacements":[],"disputed":false,"unsupportedWhenAssigned":false,"reserved":"2026-10-29T16:04:48.727Z","published":"2026-10-29T19:19:36.824Z","modified":"2026-10-29T19:19:36.824Z"}],"publishers":[{"name":"GitHub_M","role":"CNA","organizationId":"a0819718-46f1-4df5-94e2-005712e83aaa","modified":"2026-10-29T19:19:36.824Z"}],"references":[{"url":"https://github.com/digitalbazaar/forge/security/advisories/GHSA-hqq3-7jhf-c5jx","tags":["x_refsource_CONFIRM"],"source":"GitHub_M"},{"url":"https://github.com/digitalbazaar/forge/commit/817e2ce0d0f6d8a25bc9c2670615c38a9ffdc51c","tags":["x_refsource_MISC"],"source":"GitHub_M"},{"url":"https://github.com/digitalbazaar/forge/releases/tag/v1.4.1","tags":["x_refsource_MISC"],"source":"GitHub_M"}],"weaknesses":[{"id":"CWE-674","description":"CWE-674: Uncontrolled Recursion","source":"GitHub_M"}],"applicability":[],"prioritization":[]},"products":[{"vendor":"digitalbazaar","name":"forge","platforms":[],"versions":[{"version":"< 1.4.1","status":"affected"}],"cpes":[],"source":"GitHub_M","url":"https://www.cve.org/CVERecord?id=CVE-2026-119187"}],"remediation":[],"observedAt":"2026-10-30T23:32:06.632Z","kev":{"status":"not-listed","checkedAt":"2026-10-31T07:50:32.196Z"},"coverage":[{"source":"NVD","status":"no-result","checkedAt":"2026-10-29T19:55:30.874Z","attemptedAt":"2026-10-29T19:55:30.874Z"},{"source":"GHSA","status":"no-result","checkedAt":"2026-10-29T19:55:31.142Z","attemptedAt":"2026-10-29T19:55:31.142Z"},{"source":"EUVD","status":"refresh-failed","attemptedAt":"2026-10-29T19:55:30.876Z"},{"source":"EPSS","status":"no-result","checkedAt":"2026-10-29T19:55:31.142Z","attemptedAt":"2026-10-29T19:55:31.142Z"}],"evidence":[],"aiSummary":{"text":"The Forge library's TLS record processing is vulnerable to a denial-of-service attack via uncontrolled recursion, affecting versions prior to 1.4.1. An attacker can send a small chunk of records without authentication, causing the Node.js process to terminate.","model":"@cf/meta/llama-3.3-70b-instruct-fp8-fast","generatedAt":"2026-10-31T00:01:43.118Z","sources":[{"name":"CVE","url":"https://www.cve.org/CVERecord?id=CVE-2026-119187"},{"name":"OSV","url":"https://osv.dev/vulnerability/GHSA-hqq3-7jhf-c5jx"}]}}