Where
-Infinity
0
Severity
7.4
AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N

Forge (also called node-forge) is a native implementation of Transport Layer Security in JavaScript. Prior to 1.4.3, pki.verifyCertificateChain() in lib/x509.js does not decode or enforce the X.509 nameConstraints extension while validating certificate paths. A name-constrained certificate authority with a non-critical extension can therefore issue a leaf certificate with a subjectAltName dNSName or subject commonName outside a permitted subtree or inside an excluded subtree, and node-forge accepts the chain. Exploitation requires a name-constrained CA that chains to a trusted root and either its private key or the ability to obtain an out-of-scope certificate, allowing impersonation of names outside the delegated namespace, including against forge.tls clients. Chains without nameConstraints are not affected, and affected versions safely reject critical nameConstraints as unsupported rather than accepting them. This issue is fixed in version 1.4.3.

First published (updated )
Severity
3.7
AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N

Summary ed25519.verify() does not reject public keys of small order, such as the identity point. Under such a key, a signature with S = 0 and a small-order R verifies for many messages, or for every message with the identity key, without any private key. Fixed in node-forge 1.4.3.

Details cryptosignopen() in lib/ed25519.js decodes the public key A with unpackneg(), which only checks that the point is on the curve, and then checks R == [S]B - [k]A with k = SHA-512(R || A || M). Nothing rejects public keys of small order: the identity point, the other 7 points of order 2, 4 or 8, and their non-canonical encodings with y >= p.

With S = 0, the check becomes R == k. For a small-order A, k is one of at most 8 small-order points, so an attacker can make a valid signature by trying the small-order values of R. For the identity key (01 00 … 00), R = identity and S = 0 verifies for every message. For the other small-order keys, a given message can be forged with high probability. No private key is involved. The S < L check added for CVE-2026-33895 (GHSA-q67f-28xg-22rw) doesn't apply, because S = 0 is canonical.

Keys produced by Ed25519 key generation (ed25519.generateKeyPair() or any RFC 8032 implementation) are never small-order, so signatures under them cannot be forged this way. Applications are affected when they verify signatures against a public key an attacker can choose (for example a key included with the signed data, a self-registered key, or a key in an attacker-controlled document) and rely on the signature being bound to the message: proof of possession, attribution, non-repudiation or deduplication.

RFC 8032 doesn't require rejecting small-order public keys, and implementations differ. libsodium rejects them, and Node.js crypto.verify() (OpenSSL) rejects these signatures.

The fix rejects a public key A when [8]A is the identity point. This covers all small-order points and their non-canonical encodings.

Patches Fixed in node-forge 1.4.3. ed25519.verify() returns false for small-order public keys.

Workarounds Before calling ed25519.verify() with a public key from untrusted input, reject keys whose 32-byte encoding is one of the small-order points (the identity 01 00 … 00 and the other encodings that libsodium blocks), or only verify against keys you generated or have otherwise validated.

Resources - RFC 8032, Section 5.1.7 (Ed25519 verification): https://www.rfc-editor.org/rfc/rfc8032#section-5.1.7 - GHSA-q67f-28xg-22rw / CVE-2026-33895 (a different Ed25519 verification issue, fixed in 1.4.0): https://github.com/digitalbazaar/forge/security/advisories/GHSA-q67f-28xg-22rw

Severity CVSS 3.1: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N (3.7, Low) CVSS 4.0 (informational): CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N (6.3) CVSS 4.0 rates this Moderate because its integrity weighting is higher; the severity rating follows CVSS 3.1.

Credits Reported by @analisaperlengkapan.

Proof of concept Identity public key A = 01 00 … 00 (32 bytes) and signature R = 01 00 … 00, S = 00 … 00 (64 bytes):

forge.ed25519.verify({message: 'admin=true', encoding: 'utf8', signature: SIG, publicKey: IDENTITY}); // <= 1.4.2: true for any message; node:crypto.verify(): false // 1.4.3: false

Other small-order public keys (orders 2, 4 and 8, and non-canonical encodings) give forgeries for many messages with S = 0 and a small-order R.

Regression tests are in tests/unit/ed25519.js, and the reporter's PoC is in tests/pocs/ghsa-6889-vf6x-f3g5.js.

Impact Applications that verify Ed25519 signatures with node-forge against a public key that an attacker can choose are affected. An attacker can provide a small-order public key and then produce signatures that verify for many or all messages without a private key, defeating checks that rely on a signature being bound to its message under that key (proof of possession, attribution, non-repudiation, deduplication). Signatures under keys from honest key generation cannot be forged, and applications that only verify against their own or otherwise trusted keys are not affected.

1 / 2
Source: GitHub
First published (updated )
Severity
5.9
Null Pointer Dereference
AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H

Summary A forge.tls client throws an uncaught TypeError from connection.process() when it receives a ServerHello while it has no session: after a closenotify alert or a call to close(), or before handshake() is called. A malicious server or an on-path attacker can use this to terminate a Node.js process. Fixed in node-forge 1.4.3.

Details tls.handleHandshake() in lib/tls.js updates the handshake digests with c.session.md5.update(bytes) before it calls the message handler. A client creates its session in handshake(), and c.reset() sets it to null when the connection is closed. Closing doesn't set the fail flag, and the client goes back to expecting a ServerHello, so a Handshake record received afterwards still reaches handleHandshake(), which throws TypeError: Cannot read properties of null (reading 'md5').

This happens: - after the server sends a closenotify alert (at any alert level); - after the application calls close(); - before the application calls handshake().

The exception propagates out of connection.process(). Applications that pass socket data to process() from a data event handler without catching exceptions, as in the README example, terminate. After a reset the connection uses no encryption, so an on-path attacker can inject the records even when the real server is honest.

Servers are not affected: a server creates its session when it receives a ClientHello.

The fix handles these records like other unexpected records on a closed client: they are ignored.

Patches Fixed in node-forge 1.4.3.

Workarounds Catch exceptions thrown by connection.process() and close the socket, or stop passing socket data to process() once the closed callback has been called and until handshake() is called again.

Resources - RFC 5246, section 7.2.1 (Closure Alerts): https://www.rfc-editor.org/rfc/rfc5246#section-7.2.1 - Related: GHSA-525v-9gp9-w22h (another TLS client TypeError, fixed in 1.4.1): https://github.com/digitalbazaar/forge/security/advisories/GHSA-525v-9gp9-w22h

Severity CVSS 3.1: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H (5.9, Moderate) CVSS 4.0 (informational): CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N (8.2) CVSS 4.0 has no counterpart to 3.1's AC:H beyond AT:P and scores this higher; the severity rating follows CVSS 3.1.

Credits Reported by @manus-use.

Proof of concept var c = forge.tls.createConnection({server: false, ...}); c.handshake(); // warning closenotify alert, then an empty ServerHello, in separate // process() calls c.process(forge.util.hexToBytes('15030100020100')); c.process(forge.util.hexToBytes('160301000402000000')); // <= 1.4.2: TypeError: Cannot read properties of null (reading 'md5') // 1.4.3: the record is ignored

The reporter's PoC is in tests/pocs/ghsa-j8rw-jp99-435v.js, and regression tests are in tests/unit/tls.js.

Impact Applications that use forge.tls as a client and pass data from a network socket to connection.process() are affected. The server they connect to, or an on-path attacker, can make process() throw; if the application doesn't catch it, the Node.js process terminates, dropping all its connections. Servers built on forge.tls are not affected. There is no confidentiality or integrity impact.

1 / 2
Source: GitHub
First published (updated )
Severity
5.9
Input Validation
AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H

Summary A forge.tls client accepts any compression method in the ServerHello, although it only offers no compression. An unknown method makes connection.process() throw an uncaught Error when the client handles ServerHelloDone, before the server is authenticated. A malicious server or an on-path attacker can use this to terminate a Node.js process. Fixed in node-forge 1.4.3.

Details The client's ClientHello offers only the null compression method. tls.parseHelloMessage() in lib/tls.js stores the ServerHello's compressionmethod for the client without checking it (the code has a TODO: handle compression methods). When the client handles ServerHelloDone it calls tls.createConnectionState(), which throws Error('Unsupported compression algorithm.') for any method other than null (0) and deflate (1). The exception propagates out of connection.process().

The ServerHello isn't authenticated until the server's Finished message, so an on-path attacker can change the compression byte of a genuine ServerHello; the certificate chain still verifies. Applications that pass socket data to process() without catching exceptions terminate. This includes forge.tls.wrapSocket, which calls process() from its socket data handler, and forge.http, which uses it. A deflate (1) method, also never offered, makes the handshake fail later with an error instead of a crash.

Servers are not affected: a server always selects no compression.

The fix fails the handshake with an illegalparameter alert, through the connection's error callback, when the ServerHello selects any compression method other than null.

Patches Fixed in node-forge 1.4.3.

Workarounds Catch exceptions thrown by connection.process() and close the socket.

Resources - RFC 5246, section 7.4.1.3 (Server Hello): https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.3 - Related: GHSA-525v-9gp9-w22h (another TLS client crash during the handshake, fixed in 1.4.1): https://github.com/digitalbazaar/forge/security/advisories/GHSA-525v-9gp9-w22h

Severity CVSS 3.1: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H (5.9, Moderate) CVSS 4.0 (informational): CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N (8.2) CVSS 4.0 has no counterpart to 3.1's AC:H beyond AT:P and scores this higher; the severity rating follows CVSS 3.1.

Credits Reported by @alienkeric.

Proof of concept A server (or on-path attacker) sends a ServerHello with compressionmethod 0xff, followed by a valid Certificate and ServerHelloDone:

client.handshake(); client.process(serverHello + certificate + serverHelloDone); // <= 1.4.2: throws Error: Unsupported compression algorithm. // 1.4.3: error callback with "Unsupported compression method."

The reporter's PoC (a CA-issued server certificate and the default verify callback) is in tests/pocs/ghsa-w4xr-hqv8-ch44.js, and regression tests are in tests/unit/tls.js.

Impact Applications that use forge.tls as a client (directly, through forge.tls.wrapSocket, or through forge.http) and pass data from a network socket to connection.process() are affected. The server they connect to, or an on-path attacker, can make process() throw before the server is authenticated; if the application doesn't catch it, the Node.js process terminates, dropping all its connections. Servers built on forge.tls are not affected. There is no confidentiality or integrity impact.

1 / 2
Source: GitHub
First published (updated )
Severity
7.5
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

Summary asn1.fromDer() decodes the contents of BIT STRINGs as ASN.1 by default. That decode ignored the maxDepth recursion limit (an incomplete fix of CVE-2025-66031) and also decoded nested BIT STRINGs, keeping a full copy of the decoded contents at each level, so small crafted DER or PEM inputs (certificates, keys, PKCS structures) use excessive CPU time and memory and can abort the Node.js process. Fixed in node-forge 1.4.1.

Details asn1.fromDer() attempts by default to decode the contents of primitive BIT STRINGs as ASN.1 (decodeBitStrings, default true). Two flaws in that decode in lib/asn1.js allow resource exhaustion:

1. fromDer() started the BIT STRING decode with a new options object, {strict: true, decodeBitStrings: true}, that did not include maxDepth. Inside that decode the depth check compared against undefined and never triggered, so the recursion limit added for CVE-2025-66031 (GHSA-554w-wpv2-vw27) was not enforced for anything below a decoded BIT STRING. Both the global asn1.maxDepth (default 256) and the per-call maxDepth option were ignored there. Errors in this speculative decode, including stack overflows, are caught and the contents kept as raw bytes, so deep nesting is parsed instead of rejected. This is an incomplete fix of CVE-2025-66031. 2. BIT STRINGs nested inside decoded BIT STRING contents were decoded as well, and asn1.create() gives each decoded BIT STRING a deep copy of its decoded contents (obj.original, used by toDer() to detect changes). Memory use therefore grows quadratically with the nesting depth, even within the default depth limit.

No specification is violated; the parser lacks a resource limit.

The fix passes maxDepth to the BIT STRING decode, and decodes BIT STRING contents with decodeBitStrings: false, so only one level of BIT STRING contents is decoded. Contents that would exceed the limit, and BIT STRINGs inside decoded BIT STRING contents, are kept as undecoded BIT STRING values; toDer() writes their original bytes. Common structures such as SubjectPublicKeyInfo only use one level. Code that relied on nested decoding can decode the inner value explicitly with asn1.fromDer().

Patches Fixed in node-forge 1.4.1. The maxDepth limit now applies to ASN.1 decoded from BIT STRING contents, and only one level of BIT STRING contents is decoded.

Workarounds Code that calls forge.asn1.fromDer() directly and does not need encapsulated BIT STRING contents decoded can pass {decodeBitStrings: false}. This changes the parsed structure. The higher-level PKI, PKCS and TLS APIs do not expose this option and rely on the decoded form. For those, the only remedy is upgrading.

References - CVE-2025-66031 / https://github.com/digitalbazaar/forge/security/advisories/GHSA-554w-wpv2-vw27 (ASN.1 Unbounded Recursion, fixed in 1.3.2) - https://cwe.mitre.org/data/definitions/674.html (CWE-674: Uncontrolled Recursion) - https://cwe.mitre.org/data/definitions/400.html (CWE-400: Uncontrolled Resource Consumption)

Severity CVSS 3.1: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H (7.5, High) CVSS 4.0 (informational): CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N (8.7) Both versions rate this High. The severity rating follows CVSS 3.1.

Credits Reported by Ethan Kim (cremit.io), Youngjoon Kim (@acorn421) and Brian Lee (@hibrian827) of Georgia Tech SSLab, @arpitjain099, and Mike Read.

Proof of concept Input shape: n nested primitive BIT STRINGs (03 <len> 00 <inner>) around a NULL. For example:

var asn1 = forge.asn1; var obj = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, ''); for(var i = 0; i < 2000; ++i) { obj = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false, [obj]); } var der = asn1.toDer(obj).getBytes(); // 9,886 bytes asn1.fromDer(der, {maxDepth: 10});

On 1.4.0 (Node.js 24) this parses all 2,000 levels despite maxDepth: 10, taking about 0.5-1 s and 600 MB. One SEQUENCE holding 20 such chains (about 198 KB) aborts a process with the default heap with "FATAL ERROR: JavaScript heap out of memory", which the application cannot catch. Within the depth limit, 80 chains of 254 levels (92 KB) still use about 0.8 GB. The same inputs wrapped as PEM reach pki.publicKeyFromPem() and pki.certificateFromPem() (a 20 KB certificate PEM used 1.3 GB before being rejected). On 1.4.1 these inputs parse in a few milliseconds and about 55-65 MB.

Regression tests in tests/unit/asn1.js: "should limit depth in decoded BIT STRINGs (default)", "should limit depth in decoded BIT STRINGs (maxDepth)", "should only decode one level of BIT STRINGs" and "should not decode BIT STRINGs in many BIT STRINGs".

Impact A remote, unauthenticated attacker can supply a small DER or PEM structure with nested BIT STRINGs to any API that parses with asn1.fromDer() using default options. This includes pki.certificateFromPem(), pki.publicKeyFromPem(), the PKCS#7, PKCS#8 and PKCS#12 loaders, and server certificates received by forge's TLS client. Parsing such input consumes excessive CPU time and memory before the structure is rejected. On Node.js 24 a 10 KB input used about 600 MB and a 25 KB input about 1.4 GB. Inputs of about 25 KB abort a process limited to a 512 MB heap, and about 200 KB abort a process with the default heap, with a fatal out-of-memory error that the application cannot catch. Inputs that stay within the depth limit still reach about 0.8 GB at 92 KB and 1.8 GB at 231 KB. The result is denial of service. There is no confidentiality or integrity impact.

1 / 2
Source: GitHub
First published (updated )
Severity
5.9
AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N

Summary forge.pki.verifyCertificateChain() enforces the pathLenConstraint of a CA certificate's basicConstraints extension only when that certificate also has a keyUsage extension. For a CA certificate with a pathLenConstraint and no keyUsage, chains with more intermediate CA certificates below it than the constraint allows are accepted. Fixed in node-forge 1.4.1.

Details In pki.verifyCertificateChain() (lib/x509.js), the path length check for a CA certificate was guarded by keyUsageExt !== null && 'pathLenConstraint' in bcExt. The keyUsage test was originally a proxy for "a basicConstraints extension exists". Since node-forge 1.4.0 (GHSA-2328-f5f3-gj25), basicConstraints is required for every certificate that reaches this check, so the only remaining effect of the guard is to skip pathLenConstraint enforcement for CA certificates without keyUsage.

RFC 5280 permits CA certificates without keyUsage, and path length processing in section 6.1.4 (l)-(m) does not depend on keyUsage; section 4.2.1.9 defines pathLenConstraint as part of basicConstraints only. For example, with a trusted root, an intermediate CA with pathLenConstraint=0 and no keyUsage, a sub-CA issued by that intermediate, and a leaf issued by the sub-CA, node-forge accepts the chain. It correctly rejects the same chain when the intermediate has keyUsage.

The fix checks pathLenConstraint whenever the basicConstraints extension is present, regardless of keyUsage (based on PR #1142).

Patches Fixed in node-forge 1.4.1. pathLenConstraint is now enforced for CA certificates without a keyUsage extension.

Workarounds None in node-forge itself. Applications that pass an options.verify callback to pki.verifyCertificateChain() (or verify to forge.tls.createConnection()) can reject chains in which a CA certificate at depth d has basicConstraints.pathLenConstraint < d - 1. Re-issuing constrained CA certificates with a keyUsage extension that includes keyCertSign also restores enforcement.

References - RFC 5280, sections 4.2.1.9 and 6.1.4: https://www.rfc-editor.org/rfc/rfc5280#section-6.1.4 - Related: GHSA-2328-f5f3-gj25 / CVE-2026-33896 (basicConstraints bypass, fixed in 1.4.0) - https://github.com/digitalbazaar/forge/pull/1142

Severity CVSS 3.1: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N (5.9, Moderate) CVSS 4.0 (informational): CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N (8.2) CVSS 4.0 weights the attack preconditions differently and scores this higher; the severity rating follows CVSS 3.1.

Credits @offset, @eddieran, also reported independently by Zion Boggan (@zionboggan) and @EchoSkorJjj.

Proof of concept Build the chain root <- int <- subCA <- leaf, where int has basicConstraints cA=true, pathLenConstraint=0 and no keyUsage, subCA is a CA certificate issued by int, and leaf is issued by subCA:

js var caStore = forge.pki.createCaStore([root]); forge.pki.verifyCertificateChain(caStore, [leaf, subCA, int]); // <= 1.4.0: true (pathLenConstraint=0 ignored) // 1.4.1: throws "Certificate basicConstraints pathLenConstraint violated."

The same chain is rejected on <= 1.4.0 when int also has a keyUsage extension with keyCertSign. The fix adds regression tests in tests/unit/x509.js and the reporter's PoC as tests/pocs/ghsa-h46v-rmh6-fq4f.js.

Impact Applications that verify certificate chains with forge.pki.verifyCertificateChain(), including TLS peer certificate verification in forge.tls, are affected when a CA they trust (directly or through a chain) has a pathLenConstraint and no keyUsage extension.

Exploitation requires such a path-length-constrained CA certificate that chains to a root the application trusts. The attacker must either hold that CA's private key or obtain a CA certificate from it. That CA can then create subordinate CAs, and certificates issued by those subordinate CAs are accepted by pki.verifyCertificateChain(). Public web PKI CA certificates normally include keyUsage. Private PKIs whose CA certificates were created without keyUsage are most likely to be affected.

1 / 2
Source: GitHub
First published (updated )
Severity
3.7
Input Validation
AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N

Summary decipher.finish() in CBC and ECB mode accepted malformed PKCS#7 padding: only the last byte was checked, against a limit of four blocks, so mismatched padding bytes, a zero count and oversized counts were accepted and up to four blocks of plaintext could be silently removed. Fixed in node-forge 1.4.1.

Details modes.cbc.prototype.unpad() and modes.ecb.prototype.unpad() in lib/cipherModes.js read the last decrypted byte as the PKCS#7 padding count, rejected it only if it was larger than blockSize << 2 (64 for AES, 32 for DES), and then removed that many bytes. They did not reject a count of 0 or a count larger than one block, and did not check that every padding byte equals the count, as PKCS#7 (RFC 5652 section 6.3) requires. On the Wycheproof AES-CBC-PKCS5 test vectors, 69 of 144 invalid vectors decrypted successfully.

CBC and ECB without a MAC do not provide integrity: an attacker who can modify ciphertext can already make the final block decrypt to any padding. The effect of this bug is that malformed ciphertext is accepted as valid and that oversized counts silently truncate up to four blocks of plaintext. The fix does not protect against padding oracle attacks; applications that decrypt untrusted CBC data need to authenticate it (for example with an HMAC) or use AES-GCM.

The fix requires a count from 1 to the block size and checks every padding byte. Data with other padding schemes that only use the last byte as the count (ANSI X.923, ISO 10126, XML Encryption) now fails to decrypt with default unpadding; set decipher.mode.unpad = false before finish() and remove such padding in application code.

Patches Fixed in node-forge 1.4.1. CBC and ECB decryption requires valid PKCS#7 padding.

Workarounds Authenticate CBC/ECB ciphertext before decrypting it, or use AES-GCM. To check padding on 1.4.0, set decipher.mode.unpad = false and validate the padding in application code.

References - https://www.rfc-editor.org/rfc/rfc5652#section-6.3 (PKCS#7 content-encryption padding) - https://github.com/C2SP/wycheproof/blob/master/testvectorsv1/aescbcpkcs5test.json - https://cwe.mitre.org/data/definitions/354.html (CWE-354: Improper Validation of Integrity Check Value)

Severity CVSS 3.1: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N (3.7, Low) CVSS 4.0 (informational): CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N (6.3) CVSS 4.0 rates this Moderate because its integrity weighting is higher; the severity rating follows CVSS 3.1.

Credits Reported by Oren Yomtov (@orenyomtov, Fireblocks), @zebbern, and Brian Lee (@hibrian827, Georgia Tech SSLab, Team Atlanta), who also suggested the fix.

Proof of concept var forge = require('node-forge'); // Wycheproof aescbcpkcs5test tcId 26: zero padding instead of PKCS#7 var d = forge.cipher.createDecipher('AES-CBC', forge.util.hexToBytes('db4f3e5e3795cc09a073fa6a81e5a6bc')); d.start({iv: forge.util.hexToBytes('23468aa734f5f0f19827316ff168e94f')}); d.update(forge.util.createBuffer( forge.util.hexToBytes('aa62606a287476777b92d8e4c4e53028'))); console.log(d.finish()); // 1.4.0: true; 1.4.1: false

Directly, unpad() on 1.4.0 also accepts a final block ending in 03 02 01 04 (removing 4 bytes) and a count of 0x20 on 16-byte blocks (removing 32 bytes).

Regression tests are in tests/unit/aes.js ("PKCS#7 padding checks").

Impact Applications that decrypt AES or DES data in CBC or ECB mode and use the result of decipher.finish() to decide whether the data is valid could accept malformed ciphertext, with up to four blocks of trailing plaintext removed. This also affects PKCS#7 EncryptedData/EnvelopedData decryption, PKCS#12 and encrypted private keys. Because CBC and ECB without a MAC are malleable, the additional capability given to an attacker is small. There is no confidentiality or availability impact.

1 / 2
Source: GitHub
First published (updated )
Severity
3.7
AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N

Summary

When verifying a certificate, node-forge chose the RSA padding scheme (PKCS#1 v1.5 or RSASSA-PSS) and PSS parameters from the issuer certificate's own signature algorithm instead of the certificate being verified. A certificate labeled as signed with RSASSA-PSS could pass verifyCertificateChain() with a PKCS#1 v1.5 signature. Fixed in node-forge 1.4.1.

Details

certificate.verify(child) in lib/x509.js hashes the child's TBSCertificate with the digest of the child's signature algorithm, then calls verifySignature() with the issuer certificate. verifySignature() selected the padding scheme from that certificate's signatureOid and signatureParameters, so the child was verified with the issuer's own scheme rather than the one the child declares.

As a result, a child certificate whose signature algorithm says RSASSA-PSS was accepted with a PKCS#1 v1.5 signature when the issuer itself was signed with PKCS#1 v1.5. The outer signatureAlgorithm field of a certificate is not covered by the signature, so an existing PKCS#1 v1.5 (SHA-256) certificate can be relabeled as RSASSA-PSS and still pass verification. The signature itself must still be made with the issuer's private key over the certificate's contents; no certificate can be forged.

The same cause made correctly signed chains fail: a PKCS#1 v1.5 certificate issued by an RSASSA-PSS-signed certificate, or the reverse, was rejected.

The fix verifies with the child certificate's signature scheme and parameters and the issuer's public key.

Patches Fixed in node-forge 1.4.1. Certificate signatures are verified with the scheme of the certificate's own signature algorithm.

Workarounds Applications that make decisions based on a certificate's signature algorithm can verify the signature themselves with that algorithm's scheme and the issuer's public key.

References - https://www.rfc-editor.org/rfc/rfc4055#section-3 (RSASSA-PSS signature algorithm in certificates) - https://www.rfc-editor.org/rfc/rfc5280#section-4.1.1.2 (Certificate signatureAlgorithm) - https://cwe.mitre.org/data/definitions/347.html (CWE-347: Improper Verification of Cryptographic Signature)

Severity CVSS 3.1: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N (3.7, Low) CVSS 4.0 (informational): CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N (6.3) CVSS 4.0 rates this Moderate because its integrity weighting is higher; the severity rating follows CVSS 3.1.

Credits Reported by Brian Lee (@hibrian827, Georgia Tech SSLab, Team Atlanta), who also suggested the fix.

Proof of concept

Create a CA certificate signed with sha256WithRSAEncryption, and a leaf certificate whose signature algorithm fields (outer and in the TBSCertificate) are RSASSA-PSS with SHA-256/MGF1-SHA-256/salt 32, but sign the leaf's TBSCertificate with the CA key using PKCS#1 v1.5 (caKey.sign(sha256)).

On 1.4.0, forge.pki.verifyCertificateChain(forge.pki.createCaStore([ca]), [leaf]) returns true, although verifying the leaf's signature with RSASSA-PSS fails. On 1.4.1 it throws "Certificate signature is invalid.".

No private key is needed for the same result: take any certificate signed by the CA with sha256WithRSAEncryption and replace only its outer signatureAlgorithm (outside the signed TBSCertificate) with the RSASSA-PSS identifier above. On 1.4.0 it still verifies and reports signatureOid as RSASSA-PSS; on 1.4.1 it is rejected.

Regression tests are in tests/unit/x509.js ("signature scheme of the child certificate").

Impact

Applications that rely on a certificate's declared signature algorithm, for example a policy that only accepts RSASSA-PSS-signed certificates, can be given a certificate labeled RSASSA-PSS whose signature was verified as PKCS#1 v1.5. The certificate must still have been signed by the trusted issuer's key, so the identity it certifies cannot be forged. There is no confidentiality or availability impact.

1 / 2
Source: GitHub
First published (updated )
Severity
7

node-forge through 1.4.0 fails to validate element count in nested DigestAlgorithm sequences during RSA PKCS#1 v1.5 signature verification. Attackers can embed garbage bytes inside the DigestAlgorithm sequence to forge valid signatures for arbitrary messages using low-exponent RSA keys. This is an incomplete fix for CVE-2026-33894.

First published (updated )
Severity
8.7
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N

node-forge through 1.4.0 fails to validate element count in nested DigestAlgorithm sequences during RSA PKCS#1 v1.5 signature verification. Attackers can embed garbage bytes inside the DigestAlgorithm sequence to forge valid signatures for arbitrary messages using low-exponent RSA keys. This is an incomplete fix for CVE-2026-33894.

1 / 2
Source: NVD
First published (updated )
Severity
7

Forge (also called node-forge) is a native implementation of Transport Layer Security in JavaScript. Prior to version 1.4.0, RSASSA PKCS#1 v1.5 signature verification accepts forged signatures for low public exponent keys (e=3). Attackers can forge signatures by stuffing “garbage” bytes within the ASN structure in order to construct a signature that passes verification, enabling Bleichenbacher style forgery. This issue is similar to CVE-2022-24771, but adds bytes in an addition field within the ASN structure, rather than outside of it. Additionally, forge does not validate that signatures include a minimum of 8 bytes of padding as defined by the specification, providing attackers additional space to construct Bleichenbacher forgeries. Version 1.4.0 patches the issue.

First published (updated )
Severity
7

Forge (also called node-forge) is a native implementation of Transport Layer Security in JavaScript. Prior to version 1.4.0, Ed25519 signature verification accepts forged non-canonical signatures where the scalar S is not reduced modulo the group order (S >= L). A valid signature and its S + L variant both verify in forge, while Node.js crypto.verify (OpenSSL-backed) rejects the S + L variant, as defined by the specification. This class of signature malleability has been exploited in practice to bypass authentication and authorization logic (see CVE-2026-25793, CVE-2022-35961). Applications relying on signature uniqueness (i.e., dedup by signature bytes, replay tracking, signed-object canonicalization checks) may be bypassed. Version 1.4.0 patches the issue.

First published (updated )
Severity
7

Forge (also called node-forge) is a native implementation of Transport Layer Security in JavaScript. Prior to version 1.4.0, pki.verifyCertificateChain() does not enforce RFC 5280 basicConstraints requirements when an intermediate certificate lacks both the basicConstraints and keyUsage extensions. This allows any leaf certificate (without these extensions) to act as a CA and sign other certificates, which node-forge will accept as valid. Version 1.4.0 patches the issue.

First published (updated )
Severity
7

Forge (also called node-forge) is a native implementation of Transport Layer Security in JavaScript. Prior to version 1.4.0, a Denial of Service (DoS) vulnerability exists in the node-forge library due to an infinite loop in the BigInteger.modInverse() function (inherited from the bundled jsbn library). When modInverse() is called with a zero value as input, the internal Extended Euclidean Algorithm enters an unreachable exit condition, causing the process to hang indefinitely and consume 100% CPU. Version 1.4.0 patches the issue.

First published (updated )
Severity
4

Forge (also called node-forge) is a native implementation of Transport Layer Security in JavaScript. An Uncontrolled Recursion vulnerability in node-forge versions 1.3.1 and below enables remote, unauthenticated attackers to craft deep ASN.1 structures that trigger unbounded recursive parsing. This leads to a Denial-of-Service (DoS) via stack exhaustion when parsing untrusted DER inputs. This issue has been patched in version 1.3.2.

First published (updated )
Severity
7

An interpretation-conflict (CWE-436) vulnerability in node-forge versions 1.3.1 and earlier enables unauthenticated attackers to craft ASN.1 structures to desynchronize schema validations, yielding a semantic divergence that may bypass downstream cryptographic verifications and security decisions.

First published (updated )

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203