Skip to main content
CVE-2026-125117VulnerabilityPublished

Mongoose walks into inherited properties when hydrating, setting, and merging, allowing overwrites of built-in

Affects mongooseSource GCVE Database

0
Compare
Authoritative record:CVE-2026-125117· High 8.2

Not known to be exploited · CVSS 8.2 High : upgrade to 6.13.12 soon.

Data as of 10 Nov 2026, 23:24 UTC

CVSS 3.1 base score
8.2High
How severe the flaw is on the 0–10 CVSS scale.

Thin evidence

One scorer or none
CVSS scores by source
CNA (GitHub_M)CVSS 3.18.2
EPSS
0.5% chance of exploitation in 30 days – higher than 48% of CVEsExploit Prediction Scoring System: the likelihood attackers will use this flaw soon.
Patch
Available – fixed in 6.13.12, 7.8.13, 8.24.7 +1
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:H

Exploitation evidence

  1. Disclosed: evidence found.

    CVE published 9 Nov 2026

  2. Detectable: no evidence on record.

    No public nuclei template indexed

  3. Weaponized: no evidence on record.

    No public exploit code indexed in Metasploit or Exploit-DB

  4. Exploited in the wild: no evidence on record.

    Not in CISA KEV and no source reports exploitation

  5. Observed by OffSeq Mirage: no evidence on record.

    No attack traffic recorded by OffSeq Mirage sensors

A missing step is not evidence of safety. Sources: cve.org (opens in a new tab), the Metasploit, Exploit-DB and nuclei-templates index as of 10 Nov 2026.

What to do

Upgrade to a fixed release listed under Affected software.

Affected software

ProductAffectedFixed in
npmmongooseAffected
  • <6.13.12
Fixed in
  • 6.13.12
npmmongooseAffected
  • >=7.0.0 <7.8.13
Fixed in
  • 7.8.13
npmmongooseAffected
  • >=8.0.0 <8.24.7
Fixed in
  • 8.24.7
npmmongooseAffected
  • >=9.0.0 <9.11.4
Fixed in
  • 9.11.4

Check your own hosts for these packages with threat-finder (opens in a new tab), our free open-source scanner.

Description

### Summary 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: 1. Loading a document from the database (`init()`, also used by `Model.hydrate()`) 2. Setting a dotted path on a document (`$__set`, used by `new Model(obj)`, `doc.set()` and `doc.$set()`) 3. Merging update documents (`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. - **Denial of service.** For example, `{ "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. - **Changing shared schema state.** Through a subdocument with `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. ### Details #### 1. Loading documents: `init()` ```js 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: - through a model with `strict: false`, for example `new Model(req.body).save()`; - through another application or the raw MongoDB driver; - through any update that writes arbitrary keys. #### 2. Setting dotted paths: `$__set` When 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: - **A `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. - **Any root path on a schema with `strict: false`:** for example `doc.set('toString.call', 1)`. - **A subdocument with `strict: false`**, when the application passes a user-controlled path to `doc.set(path, value)`: for example `doc.set('profile.$__schema.paths.email.validators', [])`. #### 3. Merging updates: `utils.mergeClone` ```js if (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. - **Update calls chained onto a query that already has an update.** For example `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. ### Proof of concept ```js 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 ``` ```js // 1. Loading documents: a stored field named after an Object.prototype method const Loose =

References

Community reviews

Loading reviews…