GHSA-5cwg-6vw7-rj6p CVE-2026-119186
Forge: AES-GCM authentication bypass via unvalidated tagLength option
- Severity
- 5.9 / 10
- Medium · CVSS 3.1
- Exploit chance (EPSS)
- 0.21%
- Chance of exploitation in the next 30 days. Higher than 10.69% of scored vulnerabilities.
- Known exploited
- No
- Not in CISA's catalog
- Weakness
- CWE-354 Improper Validation of Integrity Check Value
- CWE-1284 Improper Validation of Specified Quantity in Input
- Fix
1.4.1- Upgrade node-forge to this version or later
Description
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 validated tagLength, an empty tag no longer matches the expected tag length and is rejected.
Patches
Fixed in node-forge 1.4.1. cipher.start() now throws an error unless tagLength is one of the tag lengths allowed by NIST SP 800-38D (128, 120, 112, 104, 96, 64 or 32 bits); a null or undefined tagLength means the default of 128 bits.
Workarounds
Do not derive tagLength from received data. Pass a fixed tagLength (or omit it to use the default of 128 bits), and check that the received tag has the expected length before calling start(), for example if(tag.length !== 16) { throw new Error('Invalid tag.'); } before decipher.start({iv: iv, tag: tag});.
Proof of concept
Start an AES-GCM decipher with tagLength: 0 and an empty tag, as an application does when it derives the tag length from a received empty tag, and feed it arbitrary bytes that were never encrypted under the key:
var decipher = forge.cipher.createDecipher('AES-GCM', key);
decipher.start({iv: iv, tag: '', tagLength: 0});
decipher.update(forge.util.createBuffer('any attacker-chosen bytes'));
decipher.finish();
// <= 1.4.0: true
// 1.4.1: start() throws "Invalid tag length."
tagLength values null, false and '0' (with an empty tag), and tagLength: 0 with no tag option, give the same result on <= 1.4.0. The fix adds regression tests in tests/unit/aes.js.
Impact
Applications that decrypt AES-GCM data with forge.cipher.createDecipher() ('AES-GCM') and pass a zero-like tagLength are affected. Typical cases are a tag length derived from the received tag, or an unset configuration value:
var decipher = forge.cipher.createDecipher('AES-GCM', key);
decipher.start({iv: iv, tag: receivedTag, tagLength: receivedTag.length * 8});
decipher.update(forge.util.createBuffer(receivedCiphertext));
if(decipher.finish()) { /* attacker-supplied data accepted */ }
An attacker who can send a ciphertext with an empty tag to such an application can make it accept attacker-chosen ciphertext as authentic. Because GCM encryption is a stream cipher, an attacker who knows (part of) a captured message's plaintext can also change that plaintext to chosen values. The default (no tagLength option, 128 bits) and any fixed valid tagLength are not affected.
Affected versions
npm
| Package | Vulnerable | Fixed in |
|---|---|---|
| node-forge | <= 1.4.0 |
No fix yet |
| node-forge | < 1.4.1 |
1.4.1 |
How to fix
Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.
node-forge npm → 1.4.1
- npm
npm install node-forge@1.4.1- yarn
yarn add node-forge@1.4.1- pnpm
pnpm add node-forge@1.4.1
Severity breakdown
CVSS 3.1 5.9
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N
- Attack vector
- Network
- Attack complexity
- High
- Privileges required
- None
- User interaction
- None
- Scope
- Unchanged
- Confidentiality
- None
- Integrity
- High
- Availability
- None
Weakness
CWE-354: Improper Validation of Integrity Check Value
The product does not validate or incorrectly validates the integrity check values or "checksums" of a message. This may prevent it from detecting if the data has been modified or corrupted in transmission.
How to prevent it
- Implementation: Ensure that the checksums present in messages are properly checked in accordance with the protocol specification before they are parsed and used.
CWE-1284: Improper Validation of Specified Quantity in Input
The product receives input that is expected to specify a quantity (such as size or length), but it does not validate or incorrectly validates that the quantity has the required properties.
How to prevent it
- Implementation: Assume all input is malicious. Use an "accept known good" input validation strategy, i.e., use a list of acceptable inputs that strictly conform to specifications. Reject any input that does not strictly conform to specifications, or transform it into something that does. When performing input validation, consider all potentially relevant properties, including length, type of input, the full range of acceptable values, missing or extra inputs, syntax, consistency across related fields, and conformance to business rules. As an example of business rule logic, "boat" may be syntactically valid because it only contains alphanumeric characters, but it is not valid if the input is only expected to contain colors such as "red" or "blue." Do not rely exclusively on looking for malicious or malformed inputs. This is likely to miss at least one undesirable input, especially if the code's environment changes. This can give attackers enough room to bypass the intended validation. However, denylists can be useful for detecting potential attacks or determining which inputs are so malformed that they should be rejected outright.
References
Advisories
Fixes and patches
Packages
Other links
Timeline
- Published
- Last updated by the source
- Latest EPSS score
Related advisories
| Advisory | Severity | Why it's related |
|---|---|---|
| GHSA-4642-5mcj-2rqw Forge: ASN.1 OID first arc truncation in asn1.oidToDer | Low | Also affects node-forge |
| GHSA-6wvx-3r32-xmcg Forge: Prototype Pollution in forge.http cookie handling via a malicious Set-Cookie | Medium | Also affects node-forge |
| GHSA-525v-9gp9-w22h Forge: TLS client crash when the server sends no certificate | Medium | Also affects node-forge |
| GHSA-7wch-p9px-956x Forge: Regular expression denial of service in PEM decoding (pem.decode) | High | Also affects node-forge |
| GHSA-h6cr-wjv9-3cpp Forge: Denial of Service via infinite loop in BigInteger.modInverse() with negative input (incomplete fix of CVE-2026-33891) | Low | Also affects node-forge |
| GHSA-4487-j8r4-4g2f Forge: verifyCertificateChain() removes trusted CA certificates with the same subject from the CA store | Low | Also affects node-forge |
| GHSA-jc66-7q78-782g Forge: PKCS#12 MAC forgery and constant-key PBE via zero iteration count | High | Also affects node-forge |
| GHSA-8hrf-8g6f-7xwp Forge: Denial of service in TLS record processing via a record interleaved with a fragmented handshake message | High | Also affects node-forge |
MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.
Check your repos with MergeHarbor