Oct 31, 2026·10 min read·3 visits
Forge (node-forge) versions prior to 1.4.1 are vulnerable to prototype pollution and arbitrary prototype property deletion in the forge.http client when processing crafted Set-Cookie HTTP response headers from untrusted servers. Upgrading to version 1.4.1 resolves the issue.
CVE-2026-118777 is a prototype pollution vulnerability (CWE-1321) in the Forge JavaScript cryptography and networking library (distributed on npm as node-forge) prior to version 1.4.1. The flaw resides in the forge.http client subsystem, where incoming HTTP cookie names and paths parsed from Set-Cookie response headers are stored directly into standard JavaScript objects without key validation or property isolation. By returning crafted Set-Cookie headers containing keys such as __proto__ or constructor, a malicious HTTP server can inject arbitrary properties into Object.prototype, attach attributes to the global Object constructor, or delete built-in prototype methods to cause an application-wide denial of service.
Forge (distributed via npm as node-forge) is an open-source native JavaScript implementation of Transport Layer Security (TLS) and cryptographic utilities maintained by Digital Bazaar. Beyond cryptographic primitives such as X.509 certificate handling, ASN.1 parsing, and cipher suites, the library includes an optional HTTP client implementation (forge.http) and an XMLHttpRequest abstraction (forge.xhr) designed to operate over Forge TLS sockets. These networking components manage client-side HTTP state, including automated parsing and storage of HTTP cookies received from remote endpoints.
In versions prior to 1.4.1, the cookie management mechanism inside lib/http.js exhibits an Improperly Controlled Modification of Object Prototype Attributes vulnerability, classified under CWE-1321 (Prototype Pollution). When an application uses forge.http or forge.xhr to communicate with a remote server, the client parses incoming Set-Cookie HTTP response headers and stores the resulting cookie objects inside a two-level dictionary structure indexed by cookie name and cookie path. Because this dictionary is initialized as a standard JavaScript object literal ({}), it inherits properties and methods from Object.prototype.
The vulnerability is triggered when a remote HTTP server returns crafted Set-Cookie headers containing special property identifiers such as __proto__ or constructor. Because the cookie storage methods verify key existence using the JavaScript in operator rather than checking strictly for own properties, references to inherited prototype attributes evaluate to true. Subsequent write or delete operations against the cookie store traverse the prototype chain and directly mutate Object.prototype or the global Object constructor.
> [!NOTE]
> Default Node.js imports via require('node-forge') do not load the vulnerable forge.http subsystem automatically. The exposure is limited to applications that explicitly require lib/http, invoke forge.http or forge.xhr, or include the full browser bundle (forge.all).
The root cause of CVE-2026-118777 lies in the combination of unisolated object initialization and unsafe property existence checks within http.createClient in lib/http.js. When a new HTTP client instance is constructed, its internal cookie jar is initialized via client.cookies = {}. In JavaScript, an object created via literal notation has its internal [[Prototype]] slot linked directly to Object.prototype, exposing inherited properties such as __proto__, constructor, toString, valueOf, and toLocaleString on every key lookup.
When the HTTP client receives a response containing a Set-Cookie header, it extracts the cookie attributes into an object and passes it to client.setCookie(cookie). To determine whether a namespace for cookie.name already exists in the cookie jar, the vulnerable implementation executes if(!(cookie.name in client.cookies)). The in operator inspects both the target object's own properties and every object along its prototype chain. If cookie.name matches any property defined on Object.prototype, the in operator returns true, and the initialization assignment client.cookies[cookie.name] = {} is bypassed.
Following this check, the function executes the nested assignment client.cookies[cookie.name][cookie.path] = cookie. When cookie.name is set to "__proto__", the first bracket access client.cookies["__proto__"] resolves via the getter on Object.prototype to Object.prototype itself. The second bracket assignment [cookie.path] = cookie then writes the attacker-controlled cookie object directly onto Object.prototype under the key specified by the cookie's path attribute. Similarly, if cookie.name is "constructor", client.cookies["constructor"] resolves to Object.prototype.constructor (the global Object constructor function), attaching the property specified by cookie.path directly onto Object.
The exact same prototype traversal flaw exists in client.getCookie(name, path) and client.removeCookie(name, path). When a server sends a Set-Cookie header with an empty value (which instructs the client to expire and remove the cookie), removeCookie executes delete client.cookies[name][path] after validating name in client.cookies and path in paths. By supplying name as "__proto__" and path as a built-in method name such as "toLocaleString", the delete operator removes the method directly from Object.prototype.
Prior to version 1.4.1, the cookie management functions setCookie, getCookie, and removeCookie inside lib/http.js relied exclusively on the in operator to guard dictionary accesses. The vulnerable implementation is shown below:
// Vulnerable code in lib/http.js prior to commit 797c3f816a22748a96fceebb34eb3a6716853991
client.setCookie = function(cookie) {
// ... validation omitted ...
// BUG: 'in' operator checks Object.prototype chain
if(!(cookie.name in client.cookies)) {
client.cookies[cookie.name] = {};
}
// BUG: Resolves to Object.prototype[cookie.path] = cookie when cookie.name is '__proto__'
client.cookies[cookie.name][cookie.path] = cookie;
// ...
};
client.getCookie = function(name, path) {
var rval = null;
if(name in client.cookies) {
var paths = client.cookies[name];
if(path) {
if(path in paths) {
rval = paths[path];
}
}
// ...
}
return rval;
};
client.removeCookie = function(name, path) {
var rval = false;
if(name in client.cookies) {
if(path) {
var paths = client.cookies[name];
// BUG: Evaluates to true for built-in methods on Object.prototype
if(path in paths) {
rval = true;
// BUG: Deletes built-in methods off Object.prototype
delete client.cookies[name][path];
}
}
}
return rval;
};In commit 797c3f816a22748a96fceebb34eb3a6716853991, the maintainers addressed the issue through two distinct defensive layers in lib/http.js. First, a helper function _hasOwn was introduced to perform safe own-property checks via Object.prototype.hasOwnProperty.call(obj, key). Calling hasOwnProperty directly off Object.prototype via .call() ensures that the check cannot be bypassed even if an object defines its own shadowing hasOwnProperty attribute.
Second, client.setCookie was updated to explicitly reject any cookie where cookie.name or cookie.path equals '__proto__'. Why are both checks necessary? If cookie.name is 'constructor', _hasOwn(client.cookies, 'constructor') returns false, causing client.cookies['constructor'] = {} to create an own property that safely shadows Object.prototype.constructor. However, in JavaScript environments, assigning obj['__proto__'] = {} on a standard object literal invokes the Object.prototype.__proto__ setter and mutates the object's prototype link rather than defining an own property named '__proto__'. Therefore, explicitly rejecting '__proto__' while using _hasOwn for all other keys prevents both prototype reassignment and inherited property traversal.
// Patched implementation in lib/http.js (commit 797c3f816a22748a96fceebb34eb3a6716853991)
var _hasOwn = function(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
};
// Inside http.createClient:
// FIX 1: Explicit blocklist for '__proto__' key and path
if(cookie.name === '__proto__' || cookie.path === '__proto__') {
return false;
}
// FIX 2: Check only own properties on client.cookies
if(!_hasOwn(client.cookies, cookie.name)) {
client.cookies[cookie.name] = {};
}
client.cookies[cookie.name][cookie.path] = cookie;
client.getCookie = function(name, path) {
var rval = null;
// FIX 3: Restrict lookups in getCookie to own properties
if(_hasOwn(client.cookies, name)) {
var paths = client.cookies[name];
if(path) {
if(_hasOwn(paths, path)) {
rval = paths[path];
}
} else {
// ...
}
}
return rval;
};
client.removeCookie = function(name, path) {
var rval = false;
// FIX 4: Restrict deletion in removeCookie to own properties
if(_hasOwn(client.cookies, name)) {
if(path) {
var paths = client.cookies[name];
if(_hasOwn(paths, path)) {
rval = true;
delete client.cookies[name][path];
// ...
}
}
}
return rval;
};An assessment of the patch confirms that the fix is complete for this code path. By guarding setCookie against '__proto__' on both dimensions (name and path) and enforcing _hasOwn checks across setCookie, getCookie, and removeCookie, cookie attributes can no longer resolve to inherited properties on Object.prototype or Object.
Exploiting CVE-2026-118777 requires an application using forge.http or forge.xhr to process an HTTP response from an attacker-controlled server or over an unencrypted HTTP connection subject to Man-in-the-Middle (MitM) interception. No authentication or client-side user interaction is required once the client initiates the HTTP request and reads the response headers.
The repository's security regression test (tests/security/ghsa-6wvx-3r32-xmcg.js) demonstrates three distinct exploitation primitives via crafted Set-Cookie headers:
Polluting Object.prototype: The server responds with a cookie named __proto__, a non-empty value, and a path parameter corresponding to the target property name:
HTTP/1.1 200 OK
Set-Cookie: __proto__=evil; path=polluted
When the vulnerable client processes this header, Object.prototype.polluted is assigned the parsed cookie structure { name: '__proto__', value: 'evil', path: 'polluted', ... }. Consequently, any object {} in the JavaScript runtime that checks obj.polluted will evaluate to a truthy object reference.
Polluting the Object Constructor: The server sets the cookie name to constructor:
HTTP/1.1 200 OK
Set-Cookie: constructor=evil; path=polluted
Because client.cookies['constructor'] resolves to the global Object function, the assignment attaches polluted directly onto Object (Object.polluted).
Deleting Object.prototype Methods (Denial of Service): The server returns an empty cookie value for __proto__ (or sets an expired timestamp) along with a path matching an existing prototype method:
HTTP/1.1 200 OK
Set-Cookie: __proto__=; path=toLocaleString
When forge.http parses an empty or expired cookie from a response, it invokes client.removeCookie('__proto__', 'toLocaleString'). In vulnerable versions, this executes delete Object.prototype.toLocaleString (or delete Object.prototype.toString). Subsequent code anywhere in the Node.js process or browser tab that implicitly or explicitly calls .toLocaleString() or .toString() on an object will throw an unhandled TypeError: obj.toLocaleString is not a function, crashing the process or terminating script execution.
CVE-2026-118777 is rated Medium severity with a CVSS v3.1 base score of 4.8 (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:L). The Attack Complexity is rated High (AC:H) because the vulnerable component is an outbound HTTP client rather than a listening server; successful exploitation requires the client application to connect to a malicious endpoint or traverse an untrusted network link where HTTP headers can be injected.
From an availability perspective (A:L), the most direct impact is reliable application-wide denial of service. By deleting foundational methods such as toString, valueOf, or toLocaleString from Object.prototype, a malicious server can cause immediate runtime exceptions in unrelated application logic, logging frameworks, or serialization routines sharing the same V8 isolate or browser execution context.
From an integrity perspective (I:L), the injected property value is constrained to a Forge cookie object structure rather than an arbitrary primitive string or number. However, because a JavaScript object always evaluates to true in boolean contexts, any downstream code or third-party dependency that performs unguarded truthiness checks on configuration options (e.g., if (options.polluted)) can have its control flow altered. According to FIRST EPSS metrics, the vulnerability currently carries a low exploitation probability score of 0.00255 (17.16th percentile) and is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog.
To remediate CVE-2026-118777, upgrade node-forge (or forge) to version 1.4.1 or later, which includes commit 797c3f816a22748a96fceebb34eb3a6716853991. Update the dependency specification in package.json and reinstall packages:
npm install node-forge@^1.4.1
# or via Yarn
yarn add node-forge@^1.4.1If an immediate dependency upgrade cannot be deployed, development teams should verify whether the vulnerable submodule is actually loaded by their application:
require('node-forge') in Node.js do not load lib/http.js by default and are not affected, provided forge.http and forge.xhr are never referenced.forge.all.js or forge.all.min.js bundle the HTTP/XHR client automatically. Switching to modular imports (such as requiring node-forge/lib/forge and only the needed cryptographic primitives like node-forge/lib/pki or node-forge/lib/aes) eliminates the vulnerable code path.forge.http must be used prior to patching, invoking Object.freeze(Object.prototype) and Object.freeze(Object) at application startup prevents prototype mutation and property deletion.forge.http traverse an egress proxy or API gateway, inspect incoming Set-Cookie response headers and strip headers containing __proto__ or constructor prior to client delivery.CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
node-forge Digital Bazaar | < 1.4.1 | 1.4.1 |
| Attribute | Detail |
|---|---|
| Vulnerability Type | Prototype Pollution (CWE-1321) |
| Affected Component | forge.http / lib/http.js (node-forge < 1.4.1) |
| Attack Vector | Network (Malicious HTTP Set-Cookie Response Header) |
| CVSS v3.1 Score | 4.8 Medium (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:L) |
| EPSS Score | 0.26% (17.16th Percentile) |
| Impact | Object.prototype Mutation & Denial of Service via Method Deletion |
| Exploit Status | PoC (Regression Tests in Repository) |
| CISA KEV Status | Not Listed |
The application initializes or modifies object prototype properties through an application boundary without sufficient validation, allowing external input to mutate standard prototype attributes across the JavaScript runtime environment.
CVE-2026-118775 (GHSA-4642-5mcj-2rqw) is an integer overflow and validation flaw in the ASN.1 serialization module of Forge (node-forge) prior to version 1.4.1. The asn1.oidToDer() function fails to validate the range of the first two arcs of an Object Identifier (OID) and truncates the packed initial subidentifier to a single 8-bit integer. Applications that perform policy checks on untrusted OID strings before encoding them into Distinguished Encoding Rules (DER) format can be tricked into emitting DER bytes that correspond to a completely different, restricted OID.
CVE-2026-119194 is an algorithmic complexity vulnerability (CWE-407) in the pyasn1 library for Python, affecting versions 0.3.3 through 0.6.4. The flaw resides in the schemaless decoding routine ConstructedPayloadDecoderBase._decodeComponentsSchemaless used across BER, CER, and DER codecs. By sending an ASN.1 container populated with child components whose tag identifiers are separated by multiples of CPython's integer hash modulus (sys.hash_info.modulus), an unauthenticated attacker can force worst-case hash collisions in an unbounded Python set, degrading decoding performance from O(N) to O(N^2) and causing a Denial of Service (DoS).
CVE-2026-119195 is an algorithmic complexity vulnerability (CWE-407) in the pyasn1 library for Python, affecting all versions prior to 0.6.5. When decoding constructed strings or indefinite-length ANY types across BER, CER, and certain DER code paths, the payload decoders repeatedly concatenate incoming fragments onto an immutable bytes object using the += operator. Because Python bytes objects cannot be resized in-place, each fragment iteration allocates a new buffer and copies all previously accumulated bytes, resulting in quadratic O(N^2) time and memory copy complexity. An unauthenticated remote attacker can supply a highly fragmented ASN.1 stream to exhaust CPU cycles and stall application worker threads.
An algorithmic complexity vulnerability (CWE-407) in pyasn1 versions prior to 0.6.5 allows unauthenticated attackers to cause a Denial of Service (DoS) via crafted ASN.1 payloads. The flaw resides in the binary-encoded ASN.1 REAL mantissa deserialization routine within RealPayloadDecoder.valueDecoder. By performing iterative byte-string slicing and repeated bitwise shifts on a growing arbitrary-precision integer, the decoder exhibits quadratic time and memory allocation complexity relative to the mantissa length. Supplying a BER-, CER-, or DER-encoded payload with a large REAL mantissa causes excessive CPU consumption and blocks application worker processes.
A vulnerability classified under CWE-407 (Inefficient Algorithmic Complexity) exists in the pyasn1 library prior to version 0.6.5. When parsing constructed ASN.1 BIT STRING payloads under BER, CER, or schema-driven DER encoding rules, the BitStringPayloadDecoder repeatedly shifts and copies the entire accumulated bit value for each incoming fragment. Processing inputs with a large number of small constructed fragments induces quadratic CPU consumption, allowing unauthenticated remote attackers to cause a denial of service via resource exhaustion during the initial decoding phase.
CVE-2026-119209 is an uncontrolled resource consumption vulnerability in the pypdf Python library prior to version 6.21.3. The flaw exists within the PDF 1.5 cross-reference (XRef) stream parsing routines in pypdf/_reader.py. Due to the absence of a document-wide entry limit across chained cross-reference streams and improper validation of negative entry counts in the /Index array, an attacker can craft a compact PDF file that triggers excessive memory allocation, resulting in a Denial of Service (DoS) via MemoryError or process termination.