Tag: npm

  • GHSA-6wvx-3r32-xmcg: Forge: Prototype Pollution in forge.http cookie handling via a malicious Set-Cookie

    • Severity: MEDIUM
    • CVE: CVE-2026-118777
    • Ecosystems: npm
    • Packages: node-forge

    ### Summary
    The `forge.http` client stored cookies from `Set-Cookie` response headers in a plain object keyed by cookie name and path. A malicious server could use the cookie name `__proto__` to add properties to `Object.prototype`, use inherited names such as `constructor` to add properties to `Object`, and send an empty cookie value to delete `Object.prototype` properties. Fixed in node-forge 1.4.1.

    ### Details
    `client.setCookie()` in `lib/http.js` stored a cookie with `client.cookies[cookie.name][cookie.path] = cookie`, creating `client.cookies[cookie.name]` only when `!(cookie.name in client.cookies)`. Both values come from the server’s `Set-Cookie` header via `response.getCookies()`. Because `in` also sees inherited properties:

    – a cookie named `__proto__` skipped the initialization, `client.cookies[‘__proto__’]` returned `Object.prototype`, and the cookie path was written as a new `Object.prototype` property, visible on every object;
    – a cookie named `constructor` wrote onto the global `Object` function the same way;
    – a cookie with an empty value calls `client.removeCookie(name, path)`, which used the same `in` checks and then `delete client.cookies[name][path]`, so `Set-Cookie: __proto__=; path=toString` deleted `Object.prototype.toString`. This path skipped the cookie’s secure-flag and domain checks, so it also worked for `http://` clients.

    `forge.http` is not loaded by `require(‘node-forge’)`. It is included in the `forge.all` browser bundle and is used with a so…

    Source: GitHub Security Advisories · 2026-10-30

  • GHSA-525v-9gp9-w22h: Forge: TLS client crash when the server sends no certificate

    • Severity: MEDIUM
    • CVE: CVE-2026-118778
    • Ecosystems: npm
    • Packages: node-forge

    ### Summary
    The forge TLS client’s check for a missing server certificate read the wrong property and never ran. A malicious TLS server, or an on-path attacker who removes the server’s Certificate message, can make `connection.process()` throw a `TypeError`, which crashes applications that use the forge TLS client. Fixed in node-forge 1.4.1.

    ### Details
    After ServerHelloDone, `tls.handleServerHelloDone()` in `lib/tls.js` is meant to reject a server that sent no certificate with a fatal `insufficient_security` alert, after consulting the application’s `verify` callback. The check tested `c.serverCertificate`, which is never set (the certificate is stored as `c.session.serverCertificate`), so it never ran. The client state machine allows ServerHelloDone directly after ServerHello, so the handshake continued to `tls.createClientKeyExchange()`, which reads `c.session.serverCertificate.publicKey` and throws a `TypeError` on the null certificate. The exception propagates out of `connection.process()` without calling the `verify` or `error` callbacks.

    There is no authentication bypass: without a server certificate the client cannot build its RSA key exchange, so the handshake cannot complete.

    The fix reads `c.session.serverCertificate` and always fails the handshake in that case, still calling `verify` so the application can customize the alert. Failing even when `verify` returns `true` is needed because the key exchange requires the certificate.

    #### Patches
    Fixed in node-forge…

    Source: GitHub Security Advisories · 2026-10-30

  • GHSA-7wch-p9px-956x: Forge: Regular expression denial of service in PEM decoding (pem.decode)

    • Severity: HIGH
    • CVE: CVE-2026-118779
    • Ecosystems: npm
    • Packages: node-forge

    ### Summary
    `pem.decode()`, which every node-forge API that parses PEM input uses, split messages with a regular expression that takes polynomial time on input without a matching END line, and parsed encapsulated headers with regular expressions that take quadratic time on long header lines. A remote attacker can block the Node.js event loop for seconds to minutes with a crafted input of a few tens of kilobytes. Fixed in node-forge 1.4.1.

    ### Details
    `pem.decode()` in `lib/pem.js` found PEM messages with this regular expression:

    /\s*—–BEGIN ([A-Z0-9- ]+)—–\r?\n?([\x21-\x7e\s]+?(?:\r?\n\r?\n))?([:A-Za-z0-9+\/=\s]+?)—–END \1—–/g

    The optional headers group and the body group are adjacent, lazy, and share characters (whitespace and the base64 alphabet), and the match must end with an END line whose label equals the BEGIN label (`\1`). When no such END line follows, the engine tries every split of the text between the two groups, for every label split and at every BEGIN line in the input. The work grows polynomially with the input length (roughly cubic for repeated BEGIN/END lines with mismatched labels, quadratic for a BEGIN line followed by many newlines).

    The encapsulated header parsing in the same function had two smaller quadratic cases: trimming each header line with `/\s+$/`, and an unanchored header regex `([\x21-\x7e]+):…` that retries at every position of a long line without a colon. These are reachable when a message does match.

    All PEM entry points…

    Source: GitHub Security Advisories · 2026-10-30

  • GHSA-h6cr-wjv9-3cpp: Forge: Denial of Service via infinite loop in BigInteger.modInverse() with negative input (incomplete fix of CVE-2026-33891)

    • Severity: LOW
    • CVE: CVE-2026-118781
    • Ecosystems: npm
    • Packages: node-forge

    ### Summary

    `BigInteger.prototype.modInverse()` in node-forge’s bundled jsbn library enters an infinite loop when the value or the modulus is negative, for example when a negative public exponent is passed to `forge.pki.rsa.generateKeyPair()`. This is an incomplete fix of CVE-2026-33891 (GHSA-5m6q-g25r-mvwx), which handled only a zero value. Fixed in node-forge 1.4.1.

    ### Details

    `bnModInverse()` (`lib/jsbn.js`, exposed as `forge.jsbn.BigInteger.prototype.modInverse`) implements the binary extended Euclidean algorithm (Handbook of Applied Cryptography, Algorithm 14.61), which is defined for positive integers. With a negative operand the loop cannot terminate: `rShiftTo` is an arithmetic shift, so a negative value never moves towards zero, and the loop’s exit condition (`u` reaching zero) is never met. The calling thread spins at 100% CPU indefinitely; in Node.js this blocks the event loop. Memory does not grow.

    node-forge 1.4.0 fixed CVE-2026-33891 (GHSA-5m6q-g25r-mvwx) by returning zero early when `this.signum() == 0`, although its changelog describes the check as “

    Source: GitHub Security Advisories · 2026-10-30

  • GHSA-4487-j8r4-4g2f: Forge: verifyCertificateChain() removes trusted CA certificates with the same subject from the CA store

    • Severity: LOW
    • CVE: CVE-2026-118780
    • Ecosystems: npm
    • Packages: node-forge

    ### Summary

    When a CA store created by `pki.createCaStore()` holds two or more certificates with the same subject name, `pki.verifyCertificateChain()` permanently removes those certificates from the store while trying each one as the issuer. A peer can send any certificate that names that subject as its issuer to make all later verifications against those CA certificates fail. Fixed in node-forge 1.4.1.

    ### Details

    `pki.verifyCertificateChain()` (`lib/x509.js`) asks the CA store for the issuer of each certificate with `caStore.getIssuer()`. When the store holds several certificates with the same subject, `caStore.getIssuer()` returns the store’s own internal array of those certificates (`caStore.certs[hash]`, built by `addCertificate()`). `verifyCertificateChain()` used that array directly as its list of candidate parents and called `parents.shift()` on it while trying each candidate. Every candidate it tried, including the one that finally verified the certificate, was removed from the store.

    Once the array is empty, `getIssuer()` returns an empty list, no candidate is tried, and verification fails with “Certificate signature is invalid.” `hasCertificate()` and `listAllCertificates()` no longer see the removed certificates. The store stays in this state for as long as the object is used.

    The attacker’s certificate only needs an issuer name equal to the shared subject name, which is public. Its signature and key can be arbitrary: every candidate is tried and shifted out…

    Source: GitHub Security Advisories · 2026-10-30

  • GHSA-5cwg-6vw7-rj6p: Forge: AES-GCM authentication bypass via unvalidated tagLength option

    • Severity: MEDIUM
    • CVE: CVE-2026-119186
    • Ecosystems: npm
    • Packages: node-forge

    ### Summary

    The AES-GCM mode in forge.cipher does not validate the tagLength option passed to start(). With a tagLength of 0 or a value that converts to 0 (such as null, false, ” or ‘0’), the authentication tag is empty, and decipher.finish() returns true for any ciphertext and additional data when the supplied tag is empty or missing. Fixed in node-forge 1.4.1.

    ### Details

    `modes.gcm.prototype.start()` in `lib/cipherModes.js` stored the `tagLength` option without validation, and `lib/cipher.js` documented it as accepting “0-128”. After processing the data, `afterFinish()` truncates the 16-byte tag with `truncate(16 % (tagLength / 8))`. For a `tagLength` that converts to 0, this is `16 % 0 = NaN`, and truncating by `NaN` empties the tag. On decryption, the start-time check that the supplied `tag` has `tagLength / 8` bytes passes for an empty tag, and the final tag comparison compares two empty strings, so `decipher.finish()` returns `true` for any ciphertext and additional data.

    NIST SP 800-38D section 5.2.1.2 allows only tag lengths of 128, 120, 112, 104 and 96 bits, plus 64 and 32 bits for certain applications.

    The fix validates `tagLength` in `start()`, for both encryption and decryption. A missing, `null` or `undefined` `tagLength` uses the default of 128 bits. Any other value is converted with `Number()` and must be one of 128, 120, 112, 104, 96, 64 or 32; otherwise `start()` throws “Invalid tag length.”. Numeric strings such as `’128’` are still accepted. With a v…

    Source: GitHub Security Advisories · 2026-10-30

  • GHSA-jc66-7q78-782g: Forge: PKCS#12 MAC forgery and constant-key PBE via zero iteration count

    • Severity: HIGH
    • CVE: CVE-2026-118782
    • Ecosystems: npm
    • Packages: node-forge

    ### Summary

    forge.pkcs12.pkcs12FromAsn1() accepts a PKCS#12 MAC iteration count of zero (or less) from the input, and the PKCS#12 key derivation function then returns a constant key that does not depend on the password or salt. An attacker who does not know the password can modify a PKCS#12/PFX file and compute a MAC that verifies with any password; PKCS#12 PBE-encrypted private keys prepared the same way decrypt with any password. Fixed in node-forge 1.4.1.

    ### Details

    `p12.pkcs12FromAsn1()` in `lib/pkcs12.js` reads the MAC iteration count (`MacData.iterations`) from the input and passes it to the PKCS#12 key derivation function `pki.pbe.generatePkcs12Key()` in `lib/pbe.js` (also exposed as `forge.pkcs12.generateKey()`) without checking that it is at least 1. The key derivation function (RFC 7292 Appendix B.2) runs its hash loop `iter` times. With an iteration count of 0 (or an empty or negative INTEGER), no hashing is done and the function returns the PKCS#12 diversifier ID byte repeated, for example `0x03` repeated 20 times for an HMAC-SHA1 MAC key. The result does not depend on the password or the salt, so anyone can compute a valid MAC over arbitrary PFX contents.

    The same key derivation function is used by `pki.pbe.getCipherForPKCS12PBE()` for the PKCS#12 password-based encryption schemes `pbeWithSHAAnd3-KeyTripleDES-CBC` and `pbewithSHAAnd40BitRC2-CBC`, whose `pkcs-12PbeParams` iteration count is also read from the input. With an iteration count of 0 or less, the e…

    Source: GitHub Security Advisories · 2026-10-30

  • GHSA-8hrf-8g6f-7xwp: Forge: Denial of service in TLS record processing via a record interleaved with a fragmented handshake message

    • Severity: HIGH
    • CVE: CVE-2026-119188
    • Ecosystems: npm
    • Packages: node-forge

    ### 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…

    Source: GitHub Security Advisories · 2026-10-30

  • GHSA-hqq3-7jhf-c5jx: Forge: Denial of service in TLS record processing via call stack exhaustion

    • Severity: HIGH
    • CVE: CVE-2026-119187
    • Ecosystems: npm
    • Packages: node-forge

    ### Summary

    Connections 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.

    ### Details

    In `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`.

    `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…

    Source: GitHub Security Advisories · 2026-10-30