CVE-2026-125116
Mongoose: Discriminator key values matching inherited properties (e.g. __t: 'constructor') bypass schema casting and can poison collections
Mongoose is a MongoDB object modeling tool designed to work in an asynchronous environment. Prior to 6.13.12, 7.8.13, 8.24.7, and 9.11.4, Mongoose uses attacker-controlled discriminator key values, including the default __t field, to index plain-object registries such as Model.discriminators and schema.discriminators without requiring an own property. Values such as constructor, toString, or hasOwnProperty can therefore resolve to inherited built-in functions instead of a registered discriminator, causing type confusion in model construction, document creation, hydration, query and update casting, embedded discriminators, and populate operations. In the bulkWrite() insertOne path with skipValidation enabled, a constructor value can cause a raw input object to be stored without schema casting or strict filtering, and a stored poisoned document can make every query that hydrates it fail until the document is removed. The weakness reads inherited properties but does not modify Object.prototype and is not prototype pollution. This issue is fixed in versions 6.13.12, 7.8.13, 8.24.7, and 9.11.4.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- High
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- None
- Integrity
- Low
- Availability
- Low
Exploit likelihood
What each source reports
CVE List Score used 4.8
Mongoose: Discriminator key values matching inherited properties (e.g. __t: 'constructor') bypass schema casting and can poison collections
Mongoose is a MongoDB object modeling tool designed to work in an asynchronous environment. Prior to 6.13.12, 7.8.13, 8.24.7, and 9.11.4, Mongoose uses attacker-controlled discriminator key values, including the default __t field, to index plain-object registries such as Model.discriminators and schema.discriminators without requiring an own property. Values such as constructor, toString, or hasOwnProperty can therefore resolve to inherited built-in functions instead of a registered discriminator, causing type confusion in model construction, document creation, hydration, query and update casting, embedded discriminators, and populate operations. In the bulkWrite() insertOne path with skipValidation enabled, a constructor value can cause a raw input object to be stored without schema casting or strict filtering, and a stored poisoned document can make every query that hydrates it fail until the document is removed. The weakness reads inherited properties but does not modify Object.prototype and is not prototype pollution. This issue is fixed in versions 6.13.12, 7.8.13, 8.24.7, and 9.11.4.
- Record
CVE-2026-125116- CVSS 3.1
- 4.8 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:L - Weakness
- CWE-843
- Updated
- 2026-11-09
EUVDEUVD-2026-120497 4.8
Mongoose: Discriminator key values matching inherited properties (e.g. __t: 'constructor') bypass schema casting and can poison collections
- Record
EUVD-2026-120497- CVSS 3.1
- 4.8 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:L - Updated
- 2026-11-10
GitHub AdvisoriesGHSA-wr5m-5jph-8m96 4.8
Mongoose: Discriminator key values matching inherited properties (e.g. __t: 'constructor') bypass schema casting and can poison collections
Summary
Mongoose chooses which discriminator model or schema to use by looking up the discriminator key value (__t by default) in a plain object of registered discriminators, for example Model.discriminators[doc.__t]. That value comes from user documents and query filters. Because the lookup did not check own properties, a value that names a property inherited from Object.prototype resolves to that inherited value instead of a discriminator. Examples are 'constructor', 'toString' and 'hasOwnProperty'.
__t: 'constructor'resolves to the globalObjectfunction. Mongoose then callsnew Object(doc), which returns the raw user object where a Mongoose document is expected.__t: 'toString'and similar names resolve to built-in functions that throw when used as a constructor or schema.
The most serious effect: through bulkWrite() insertOne with skipValidation: true, the raw user object is stored as-is, without schema casting or strict filtering. After that, every query that returns the stored document throws, until the document is removed from the database.
Details
The registries that hold discriminators are plain objects: Model.discriminators, schema.discriminators, and the embedded discriminator registries on document arrays and single nested subdocuments. Several code paths indexed them directly with a value taken from the document or filter:
| Code path | Input |
|---|---|
| Model constructor (new Model(doc)) | document |
| Model.create(), insertMany(), bulkWrite(), bulkSave() | documents |
| Hydrating query results (find(), findOne(), etc.) and Model.hydrate() | stored documents |
| Casting query filters, including flat paths such as 'events.__t' and $elemMatch | query filter |
| Casting updates and array filters | update filter, arrayFilters |
| Document array push() / create(), document array and single nested casting | subdocuments |
| Populate on embedded discriminators | stored subdocuments |
getDiscriminatorByValue() already iterated the registry's own keys safely. But most callers tried the direct lookup first and only fell back to it afterwards, so a truthy inherited value took precedence.
The lookups only read inherited properties. They do not write to Object.prototype or any other shared object, and they do not change in-memory state that affects other requests. This is NOT a prototype pollution vector.
Proof of concept
const mongoose = require('mongoose');
const Base = mongoose.model('Base', new mongoose.Schema({ name: String, age: Number }));
Base.discriminator('Child', new mongoose.Schema({ extra: String }));
// Type confusion: the "document" is the raw input object
const doc = new Base({ name: 'x', __t: 'constructor' });
console.log(doc instanceof mongoose.Document); // false
// Schema bypass and persistent poison document via bulkWrite + skipValidation
await Base.bulkWrite([{
insertOne: { document: { name: 'x', age: 'not a number', notInSchema: true, __t: 'constructor' } }
}], { skipValidation: true });
// Stored verbatim: { age: 'not a number', notInSchema: true, __t: 'constructor', ... }
await Base.find(); // throws: Cannot read properties of undefined (reading 'eachPath')
Impact
- Crashing the request. An attacker who controls the discriminator key of a document or filter can make the request fail. In most code paths that sent the value, this means a
TypeErroror a non-document object returned to application code. Ways in includeModel.create(req.body),new Model(req.body),Model.find(req.query)-style filters, and pushing user input onto embedded discriminator arrays. The effect is limited to the request carrying the value. - Schema bypass on
bulkWrite({ skipValidation: true }). When an application passes untrusted documents tobulkWrite()insertOnewithskipValidation: true, the raw object is stored without casting orstrictfiltering. Fields the schema would…
- Record
GHSA-wr5m-5jph-8m96- CVSS 3.1
- 4.8 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:L - Weakness
- CWE-843
- Updated
- 2026-11-10
NVD 4.8
Mongoose is a MongoDB object modeling tool designed to work in an asynchronous environment. Prior to 6.13.12, 7.8.13, 8.24.7, and 9.11.4, Mongoose uses attacker-controlled discriminator key values, including the default __t field, to index plain-object registries such as Model.discriminators and schema.discriminators without requiring an own property. Values such as constructor, toString, or hasOwnProperty can therefore resolve to inherited built-in functions instead of a registered discriminator, causing type confusion in model construction, document creation, hydration, query and update casting, embedded discriminators, and populate operations. In the bulkWrite() insertOne path with skipValidation enabled, a constructor value can cause a raw input object to be stored without schema casting or strict filtering, and a stored poisoned document can make every query that hydrates it fail until the document is removed. The weakness reads inherited properties but does not modify Object.prototype and is not prototype pollution. This issue is fixed in versions 6.13.12, 7.8.13, 8.24.7, and 9.11.4.
- Record
CVE-2026-125116- CVSS 3.1
- 4.8 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:L - Weakness
- CWE-843
- Updated
- 2026-11-10
OSV 4.8
Mongoose: Discriminator key values matching inherited properties (e.g. __t: 'constructor') bypass schema casting and can poison collections
Mongoose is a MongoDB object modeling tool designed to work in an asynchronous environment. Prior to 6.13.12, 7.8.13, 8.24.7, and 9.11.4, Mongoose uses attacker-controlled discriminator key values, including the default __t field, to index plain-object registries such as Model.discriminators and schema.discriminators without requiring an own property. Values such as constructor, toString, or hasOwnProperty can therefore resolve to inherited built-in functions instead of a registered discriminator, causing type confusion in model construction, document creation, hydration, query and update casting, embedded discriminators, and populate operations. In the bulkWrite() insertOne path with skipValidation enabled, a constructor value can cause a raw input object to be stored without schema casting or strict filtering, and a stored poisoned document can make every query that hydrates it fail until the document is removed. The weakness reads inherited properties but does not modify Object.prototype and is not prototype pollution. This issue is fixed in versions 6.13.12, 7.8.13, 8.24.7, and 9.11.4.
- Record
CVE-2026-125116- CVSS 3.1
- 4.8 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:L - Weakness
- CWE-843
- Updated
- 2026-11-11
OSVGHSA-wr5m-5jph-8m96 4.8
Mongoose: Discriminator key values matching inherited properties (e.g. __t: 'constructor') bypass schema casting and can poison collections
Summary
Mongoose chooses which discriminator model or schema to use by looking up the discriminator key value (__t by default) in a plain object of registered discriminators, for example Model.discriminators[doc.__t]. That value comes from user documents and query filters. Because the lookup did not check own properties, a value that names a property inherited from Object.prototype resolves to that inherited value instead of a discriminator. Examples are 'constructor', 'toString' and 'hasOwnProperty'.
__t: 'constructor'resolves to the globalObjectfunction. Mongoose then callsnew Object(doc), which returns the raw user object where a Mongoose document is expected.__t: 'toString'and similar names resolve to built-in functions that throw when used as a constructor or schema.
The most serious effect: through bulkWrite() insertOne with skipValidation: true, the raw user object is stored as-is, without schema casting or strict filtering. After that, every query that returns the stored document throws, until the document is removed from the database.
Details
The registries that hold discriminators are plain objects: Model.discriminators, schema.discriminators, and the embedded discriminator registries on document arrays and single nested subdocuments. Several code paths indexed them directly with a value taken from the document or filter:
| Code path | Input |
|---|---|
| Model constructor (new Model(doc)) | document |
| Model.create(), insertMany(), bulkWrite(), bulkSave() | documents |
| Hydrating query results (find(), findOne(), etc.) and Model.hydrate() | stored documents |
| Casting query filters, including flat paths such as 'events.__t' and $elemMatch | query filter |
| Casting updates and array filters | update filter, arrayFilters |
| Document array push() / create(), document array and single nested casting | subdocuments |
| Populate on embedded discriminators | stored subdocuments |
getDiscriminatorByValue() already iterated the registry's own keys safely. But most callers tried the direct lookup first and only fell back to it afterwards, so a truthy inherited value took precedence.
The lookups only read inherited properties. They do not write to Object.prototype or any other shared object, and they do not change in-memory state that affects other requests. This is NOT a prototype pollution vector.
Proof of concept
const mongoose = require('mongoose');
const Base = mongoose.model('Base', new mongoose.Schema({ name: String, age: Number }));
Base.discriminator('Child', new mongoose.Schema({ extra: String }));
// Type confusion: the "document" is the raw input object
const doc = new Base({ name: 'x', __t: 'constructor' });
console.log(doc instanceof mongoose.Document); // false
// Schema bypass and persistent poison document via bulkWrite + skipValidation
await Base.bulkWrite([{
insertOne: { document: { name: 'x', age: 'not a number', notInSchema: true, __t: 'constructor' } }
}], { skipValidation: true });
// Stored verbatim: { age: 'not a number', notInSchema: true, __t: 'constructor', ... }
await Base.find(); // throws: Cannot read properties of undefined (reading 'eachPath')
Impact
- Crashing the request. An attacker who controls the discriminator key of a document or filter can make the request fail. In most code paths that sent the value, this means a
TypeErroror a non-document object returned to application code. Ways in includeModel.create(req.body),new Model(req.body),Model.find(req.query)-style filters, and pushing user input onto embedded discriminator arrays. The effect is limited to the request carrying the value. - Schema bypass on
bulkWrite({ skipValidation: true }). When an application passes untrusted documents tobulkWrite()insertOnewithskipValidation: true, the raw object is stored without casting orstrictfiltering. Fields the schema would…
- Record
GHSA-wr5m-5jph-8m96- CVSS 3.1
- 4.8 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:L - Weakness
- CWE-843
- Updated
- 2026-11-10
Affected products
Affected packages
History
| Recorded | Changed | Sources |
|---|---|---|
| published | EUVD | |
| affected, references | OSV | |
| affected, facts, references | GitHub Advisories | |
| aliases, references | NVD, OSV | |
| cwes, references, severity, title | CVE List | |
| first indexed | EUVD |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-125116 is a medium vulnerability (CVSS 4.8) in Automattic Mongoose, published November 9, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.34%, the 27th percentile. Fixed in mongoose 6.13.12.
References
github.com/Automattic/mongoose/security/advisories/GHSA-wr5m-5jph-8m96
github.com/Automattic/mongoose/commit/0ecd8040d9b446f36535d79938c130c800f91766
github.com/Automattic/mongoose/commit/18afab75bea861f625b7c5b54635c84567ec566c
github.com/Automattic/mongoose/commit/390453c8ce1b36b6cfd1e63432a910da1f3d81d5
github.com/Automattic/mongoose/commit/8cee51beead554df1f3d05645edb439c69eb2a9b
github.com/Automattic/mongoose/commit/b26c43554233134ea978c5852babe13be858b2cf
Show 9 moreShow less
github.com/Automattic/mongoose/commit/b68f90734b50cd22c89a669c8ca1469d7234b296
github.com/Automattic/mongoose/releases/tag/6.13.12
github.com/Automattic/mongoose/releases/tag/7.8.13
github.com/Automattic/mongoose/releases/tag/8.24.7
github.com/Automattic/mongoose/releases/tag/9.11.4
nvd.nist.gov/vuln/detail/CVE-2026-125116
github.com/advisories/GHSA-wr5m-5jph-8m96
github.com/CVEProject/cvelistV5/tree/main/cves/2026/125xxx/CVE-2026-125116.json
github.com/Automattic/mongoose
Left out here: the rest of 2 source descriptions. The JSON record has them.