MediumGHSA-wr5m-5jph-8m96CVE-2026-125116

Mongoose: Discriminator key values matching inherited properties (e.g. __t: 'constructor') bypass schema casting and can poison collections

Published 10 Nov 2026 · Updated 10 Nov 2026

Is your project on an affected mongoose?

Private repo? Upload its lockfile instead, or connect GitHub to check every repo.

Affected packages

Affected
<6.13.12
Fixed in
6.13.12
Update to the fixed version for your release line, for example:npm install mongoose@6.13.12
Affected
>=7.0.0 <7.8.13
Fixed in
7.8.13
Update to the fixed version for your release line, for example:npm install mongoose@7.8.13
Affected
>=8.0.0 <8.24.7
Fixed in
8.24.7
Update to the fixed version for your release line, for example:npm install mongoose@8.24.7
Affected
>=9.0.0 <9.11.4
Fixed in
9.11.4
Update to the fixed version for your release line, for example:npm install mongoose@9.11.4

Details

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 global Object function. Mongoose then calls new 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 TypeError or a non-document object returned to application code. Ways in include Model.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 to bulkWrite() insertOne with skipValidation: true, the raw object is stored without casting or strict filtering. Fields the schema would normally strip are persisted, such as an isAdmin that isn't in the schema. Fields with wrong types are also persisted.
  • Persistent denial of service for queries. A stored document with such a discriminator key value makes hydration throw. Every query that returns that document fails, for all users, until the document is deleted directly in the database. Besides the bulkWrite() path above, the document can get into the collection through another application, the raw MongoDB driver, or any other writer that does not go through Mongoose casting.

This issue doesn't let an attacker modify Object.prototype, built-in functions, schemas or other shared in-memory state. The schema bypass requires the application to opt in to skipValidation with untrusted input.

Is GHSA-wr5m-5jph-8m96 in any of your client repos?

Checking one package by hand is fine. Checking it across forty client projects is not. NPMscan reads the lockfiles in every GitHub repo you pick, finds each project with an affected mongoose, and gives you the command that fixes it.

More advisories for mongoose

  • HighCVE-2026-125117Mongoose walks into inherited properties when hydrating, setting, and merging, allowing overwrites of built-in functions (denial of service) and shared schema state · 10 Nov 2026
  • MediumCVE-2026-125118Mongoose: Prototype pollution via Schema.prototype.virtual() · 10 Nov 2026
  • HighCVE-2026-125119Mongoose: Connection-level sanitizeFilter not applied · 10 Nov 2026
  • CriticalCVE-2026-125120Mongoose: sanitizeFilter not applied on countDocuments · 10 Nov 2026
  • MediumCVE-2026-73562Mongoose: Prototype pollution in mongoose update casting via __proto__-prefixed dotted path (Schema._getSchema/path getter) · 24 Jul 2026
  • HighCVE-2026-42334Mongoose's Improper Sanitization of $nor in sanitizeFilter May Allow NoSQL Injection · 5 May 2026
All 14 advisories for mongoose

References

Advisory data from OSV.dev (opens OSV.dev in a new tab), refreshed daily.