GHSA-5X38-2VFF-X55P
Vulnerability from github – Published: 2026-10-30 23:01 – Updated: 2026-10-30 23:01Summary
node-forge parses certificate extensions with the deprecated OIDs 2.5.29.10 and 2.5.29.13 as basicConstraints, and forge.pki.verifyCertificateChain() used the first extension with that name. A certificate with a forged deprecated-OID extension (cA TRUE) before its genuine basicConstraints extension (cA FALSE) is accepted as a CA certificate, and the same technique can hide a pathLenConstraint. Fixed in node-forge 1.4.1.
Details
lib/oids.js maps the deprecated certificate extension OIDs 2.5.29.10 and 2.5.29.13 to the name basicConstraints, the same name as the RFC 5280 extension 2.5.29.19. certificateExtensionFromAsn1() in lib/x509.js decodes extensions by name, so these extensions are decoded into the same cA and pathLenConstraint fields. cert.getExtension() returns the first extension with a matching name, and pki.verifyCertificateChain() looked up basicConstraints by name.
As a result, a certificate that contains a 2.5.29.10 (or 2.5.29.13) extension with cA TRUE before its genuine basicConstraints extension with cA FALSE is accepted as a CA certificate. The same technique can hide a CA certificate's pathLenConstraint. A deprecated-OID extension on its own also satisfies the check added for GHSA-2328-f5f3-gj25 (CVE-2026-33896) that basicConstraints is present. RFC 5280 section 4.2.1.9 defines basicConstraints only as id-ce 19 (2.5.29.19), and the path validation checks in section 6.1.4 apply to that extension.
The fix makes chain verification look up basicConstraints by OID (cert.getExtension({id: '2.5.29.19'})). This covers both the cA and the pathLenConstraint checks. Certificate parsing, the OID table and cert.getExtension('basicConstraints') are unchanged.
Patches
Fixed in node-forge 1.4.1. Chain verification now uses only the basicConstraints extension with OID 2.5.29.19.
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 non-leaf certificate has no 2.5.29.19 extension with cA TRUE, for example with cert.getExtension({id: '2.5.29.19'}). CA operators can avoid copying unrecognized extensions from certificate requests.
Credits
WorkOS Security Team· security@workos.com (@nickcollisson-workos)
Proof of concept
Have a trusted CA issue an end-entity certificate whose extensions are, in this order: a 2.5.29.10 (or 2.5.29.13) extension with the value of a basicConstraints cA TRUE (DER 30 03 01 01 FF), then the CA's own 2.5.29.19 basicConstraints with cA FALSE, and no keyUsage. For example, openssl x509 -req -copy_extensions copy with an extensions file that sets basicConstraints = CA:FALSE copies the extension from the CSR and produces this order. Use that certificate's key to sign a leaf certificate for any name.
On node-forge <= 1.4.0, forge.pki.verifyCertificateChain(caStore, [leaf, eeCert]) accepts the chain, and eeCert.getExtension('basicConstraints') returns the 2.5.29.10 extension with cA true. The same certificate without the deprecated-OID extension is rejected ("Certificate basicConstraints indicates the certificate is not a CA."). A certificate with only the deprecated-OID extension is also accepted.
The fix adds regression tests in tests/unit/x509.js and the reporter's PoC as tests/pocs/ghsa-5x38-2vff-x55p.js.
Impact
Applications that verify certificate chains with forge.pki.verifyCertificateChain(), including TLS peer certificate verification in forge.tls, are affected.
Exploitation requires a CA trusted by the application to issue a certificate that contains the attacker-supplied deprecated-OID extension, typically a CA that copies extensions from certificate signing requests. For example, openssl x509 -req -copy_extensions copy (or copyall) with an extensions file that sets basicConstraints = CA:FALSE and no keyUsage produces such a certificate. If the issuing CA adds a keyUsage extension without keyCertSign, node-forge rejects the chain.
The holder of such a certificate can then issue certificates for any name that pki.verifyCertificateChain() accepts. In forge's own TLS stack this allows impersonating servers (or clients, where client certificates are verified) and intercepting traffic.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 1.4.0"
},
"package": {
"ecosystem": "npm",
"name": "node-forge"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.4.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-119190"
],
"database_specific": {
"cwe_ids": [
"CWE-295",
"CWE-436"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-30T23:01:46Z",
"nvd_published_at": "2026-10-29T20:17:30Z",
"severity": "HIGH"
},
"details": "### Summary\nnode-forge parses certificate extensions with the deprecated OIDs `2.5.29.10` and `2.5.29.13` as `basicConstraints`, and `forge.pki.verifyCertificateChain()` used the first extension with that name. A certificate with a forged deprecated-OID extension (`cA` TRUE) before its genuine `basicConstraints` extension (`cA` FALSE) is accepted as a CA certificate, and the same technique can hide a `pathLenConstraint`. Fixed in node-forge 1.4.1.\n\n### Details\n`lib/oids.js` maps the deprecated certificate extension OIDs `2.5.29.10` and `2.5.29.13` to the name `basicConstraints`, the same name as the RFC 5280 extension `2.5.29.19`. `certificateExtensionFromAsn1()` in `lib/x509.js` decodes extensions by name, so these extensions are decoded into the same `cA` and `pathLenConstraint` fields. `cert.getExtension()` returns the first extension with a matching name, and `pki.verifyCertificateChain()` looked up `basicConstraints` by name.\n\nAs a result, a certificate that contains a `2.5.29.10` (or `2.5.29.13`) extension with `cA` TRUE before its genuine `basicConstraints` extension with `cA` FALSE is accepted as a CA certificate. The same technique can hide a CA certificate\u0027s `pathLenConstraint`. A deprecated-OID extension on its own also satisfies the check added for GHSA-2328-f5f3-gj25 (CVE-2026-33896) that `basicConstraints` is present. RFC 5280 section 4.2.1.9 defines basicConstraints only as `id-ce 19` (`2.5.29.19`), and the path validation checks in section 6.1.4 apply to that extension.\n\nThe fix makes chain verification look up basicConstraints by OID (`cert.getExtension({id: \u00272.5.29.19\u0027})`). This covers both the `cA` and the `pathLenConstraint` checks. Certificate parsing, the OID table and `cert.getExtension(\u0027basicConstraints\u0027)` are unchanged.\n\n#### Patches\nFixed in node-forge 1.4.1. Chain verification now uses only the `basicConstraints` extension with OID `2.5.29.19`.\n\n#### Workarounds\nNone 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 non-leaf certificate has no `2.5.29.19` extension with `cA` TRUE, for example with `cert.getExtension({id: \u00272.5.29.19\u0027})`. CA operators can avoid copying unrecognized extensions from certificate requests.\n\n#### Credits\n\nWorkOS Security Team\u00b7 [security@workos.com](mailto:security@workos.com) (@nickcollisson-workos)\n\n### Proof of concept\nHave a trusted CA issue an end-entity certificate whose extensions are, in this order: a `2.5.29.10` (or `2.5.29.13`) extension with the value of a basicConstraints `cA` TRUE (DER `30 03 01 01 FF`), then the CA\u0027s own `2.5.29.19` basicConstraints with `cA` FALSE, and no `keyUsage`. For example, `openssl x509 -req -copy_extensions copy` with an extensions file that sets `basicConstraints = CA:FALSE` copies the extension from the CSR and produces this order. Use that certificate\u0027s key to sign a leaf certificate for any name.\n\nOn node-forge \u003c= 1.4.0, `forge.pki.verifyCertificateChain(caStore, [leaf, eeCert])` accepts the chain, and `eeCert.getExtension(\u0027basicConstraints\u0027)` returns the `2.5.29.10` extension with `cA` true. The same certificate without the deprecated-OID extension is rejected (\"Certificate basicConstraints indicates the certificate is not a CA.\"). A certificate with only the deprecated-OID extension is also accepted.\n\nThe fix adds regression tests in `tests/unit/x509.js` and the reporter\u0027s PoC as `tests/pocs/ghsa-5x38-2vff-x55p.js`.\n\n### Impact\nApplications that verify certificate chains with `forge.pki.verifyCertificateChain()`, including TLS peer certificate verification in `forge.tls`, are affected.\n\nExploitation requires a CA trusted by the application to issue a certificate that contains the attacker-supplied deprecated-OID extension, typically a CA that copies extensions from certificate signing requests. For example, `openssl x509 -req -copy_extensions copy` (or `copyall`) with an extensions file that sets `basicConstraints = CA:FALSE` and no `keyUsage` produces such a certificate. If the issuing CA adds a `keyUsage` extension without `keyCertSign`, node-forge rejects the chain.\n\nThe holder of such a certificate can then issue certificates for any name that `pki.verifyCertificateChain()` accepts. In forge\u0027s own TLS stack this allows impersonating servers (or clients, where client certificates are verified) and intercepting traffic.",
"id": "GHSA-5x38-2vff-x55p",
"modified": "2026-10-30T23:01:46Z",
"published": "2026-10-30T23:01:46Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/digitalbazaar/forge/security/advisories/GHSA-5x38-2vff-x55p"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-119190"
},
{
"type": "WEB",
"url": "https://github.com/digitalbazaar/forge/commit/58c06bc251856b409c30ecb35a93f5f6c9bbc84a"
},
{
"type": "PACKAGE",
"url": "https://github.com/digitalbazaar/forge"
},
{
"type": "WEB",
"url": "https://github.com/digitalbazaar/forge/releases/tag/v1.4.1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "Forge: X.509 basicConstraints bypass in verifyCertificateChain() via deprecated extension OIDs 2.5.29.10 and 2.5.29.13"
}
Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline
Sightings and recorded dates, grouped by day · UTC
Loading timeline…
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.