MONGOOSE · CVE-2026-125117 · Package
FIX → 9.11.4 · 8.24.7 · 7.8.13 · 6.13.12
Mongoose copies data into inherited object properties such as toString when loading, setting and merging documents, so a crafted key can overwrite Object.prototype.toString.call. An attacker who controls input can crash every query in the process until restart, or change schema validators shared by all documents.
first public Nov 2, 2026 · 2 sources
Could this affect your app?
Free first check from your website. Connect your repo for the full answer.
Applications that use Mongoose and pass untrusted input into document construction, set, findOneAndReplace or update merging, or that store documents with attacker-chosen field names. Applications using lean queries only, or that accept no untrusted input into Mongoose, are much less exposed. unknown whether the sources list other affected paths beyond these.
Not affected if lean-queries-only, managed-hosting-not-relevant, no-untrusted-input
Who cares express, mongodb, mongoose, node.js
npm ls mongoose, and see whether it is below 6.13.12, 7.8.13, or for its major line.Three Mongoose code paths copy data into objects by property name without checking whether the property is the object's own or inherited from its prototype:
init(), also used by Model.hydrate())$__set, used by new Model(obj), doc.set() and doc.$set())utils.mergeClone, used by findOneAndReplace(), chained update calls and Query.prototype.merge())When a key names an inherited property, such as toString or valueOf, or a document internal such as $__schema, Mongoose writes into the shared object behind that property. The data comes from the attacker.
Check it with your coding agent
It reads this page and tells you if your code is affected.
Are you affected?
Ask from here. InfraStack checks the affected versions against your stack and answers in your inbox.
InfraStack checks the affected versions against your stack and answers in Slack, with the evidence and the fix.
8.24.79.11.4new Model(req.body), findOneAndReplace(, and doc.set( called with user-supplied paths or values.Mixed paths and for subdocuments with strict: false.Upgrade to 9.11.4 · 8.24.7 · 7.8.13 · 6.13.12
mongoose 6.13.12 for the 6.x linemongoose 7.8.13 for the 7.x linemongoose 8.24.7 for the 8.x linemongoose 9.11.4 for the 9.x line{ "toString": { "call": 1 } } overwrites Object.prototype.toString.call. Mongoose and most other libraries rely on Object.prototype.toString.call(). After the overwrite, every later query in the process throws TypeError: _toString.call is not a function until the process restarts. This includes queries on unrelated models.strict: false, a path such as profile.$__schema.paths.email.validators reaches the subdocument's schema, which every document shares. Setting it to [] removes the required validator for every document in the process.Mongoose already blocked __proto__, constructor and prototype, so these bugs cannot add properties to Object.prototype itself. They can still overwrite methods and properties of shared built-in functions and of Mongoose's internal objects.
init()if (!schemaType && utils.isPOJO(value)) {
// assume nested object
if (!doc[i]) { doc[i] = {}; ... }
init(self, value, doc[i], opts, path + '.');
}
For a stored field named toString, doc[i] is the inherited Object.prototype.toString. It is truthy, so init() recurses into that function and copies the stored subfields onto it.
This runs for every document Mongoose loads: find(), findOne(), cursors, populated documents and Model.hydrate(). It runs whatever the strict setting, and it applies to top-level and nested paths alike. lean() queries are not affected.
A malicious document can get into the collection in several ways:
strict: false, for example new Model(req.body).save();$__setWhen it walks a dotted path, $__set read obj[parts[i]] and descended into whatever it found. That included inherited functions, such as Object.prototype.toString on a plain object or Document.prototype.toObject on a subdocument. On subdocuments it also included non-function internals that are prototype getters, such as $__schema, schema, $__middleware and $__schemaTypeOptions. Those point at objects that every document of the model shares.
Attacker-controlled input reaches this path through:
Mixed path on a default, strict schema: new Model({ "mixed.toString.call": 1 }), or doc.set({ "mixed.toString.call": 1 }). Nothing needs to be saved.strict: false: for example doc.set('toString.call', 1).strict: false, when the application passes a user-controlled path to doc.set(path, value): for example doc.set('profile.$__schema.paths.email.validators', []).utils.mergeCloneif (typeof to[key] === 'undefined') {
to[key] = clone(fromObj[key]);
} else if (isObject(val)) {
mergeClone(to[key], val); // to[key] may be an inherited built-in
}
to[key] was read without an own-property check, so a key such as toString recursed into Object.prototype.toString. This affects:
Model.findOneAndReplace(filter, replacement). It always merges the replacement into the query's update, so a single call with an untrusted replacement is enough.Model.updateOne(filter, { $set: {...} }).updateOne({}, untrustedUpdate), the same pattern with findOneAndUpdate(), or middleware that calls this.updateOne(...).Query.prototype.merge() with a source query whose update came from untrusted input.const mongoose = require('mongoose');
const { Schema } = mongoose;
// 2. Setting dotted paths: a Mixed path on a default strict schema, nothing saved
const Item = mongoose.model('Item', new Schema({ name: String, meta: {} }));
new Item(JSON.parse('{"meta.toString.call": 1}')); // e.g. new Item(req.body)
console.log(typeof Object.prototype.toString.call); // 'number'
// From here on, every query in the process fails:
// TypeError: _toString.call is not a function
// 1. Loading documents: a stored field named after an Object.prototype method
const Loose = mongoose.model('Loose', new Schema({ name: String }, { strict: false }));
await new Loose(JSON.parse('{"name":"evil","toString":{"call":1}}')).save();
await Loose.findOne({ name: 'evil' }); // any later read of this document
await OtherModel.findOne(); // TypeError: _toString.call is not a function
// 3. Merging updates: a single findOneAndReplace() call with an untrusted replacement
const Doc = mongoose.model('Doc', new Schema({ name: String }));
Doc.findOneAndReplace({}, JSON.parse('{"toString":{"call":1}}'));
console.log(typeof Object.prototype.toString.call); // 'number'
// 2. Setting dotted paths: changing the shared subdocument schema
const User = mongoose.model('User', new Schema({
profile: new Schema({ email: { type: String, required: true } }, { strict: false })
}));
// e.g. JSON-patch style endpoint: doc.set(req.body.path, req.body.value)
new User({ profile: { email: 'a@b.c' } }).set('profile.$__schema.paths.email.validators', []);
await new User({ profile: {} }).validate(); // resolves: `required` removed for every document
Denial of service (paths 1, 2 and 3). One request overwrites Object.prototype.toString.call. After that, every query in the process fails, and so does much other code, until the process restarts. Ways in:
new Model(req.body) on any schema with a Mixed path;findOneAndReplace() with an untrusted replacement;{ "toString": { "call": 1 } }.In the stored-document case, the attacker does not need any write path through Mongoose at the moment of the attack. Any later read of that document triggers the overwrite. If the document is read on startup or by a scheduled job, the application can be kept down.
Changes to shared state (path 2, strict: false subdocuments). An attacker who controls the path passed to doc.set(path, value) can change a subdocument schema that every document shares. For example, they can remove validators or change schema options for every later document in the process.
Properties on built-in functions. More generally, an attacker can attach properties they choose to built-in functions such as Object.prototype.toString, Object.prototype.valueOf and Function.prototype.call. Any other code that reads properties of those functions sees the attacker's values.
This page as .md · .json · CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:H
GitHub Advisory Database, CC-BY 4.0 · NVD data is provided by NIST and is public domain. Posts quoted belong to their authors.