On this page
If a website or app stores its users in MongoDB through Mongoose, a stranger could ask it trick questions that the developers thought were blocked, and learn things from the answers: whether an email has an account, or what a password reset token starts with, one guess at a time. Mongoose has a setting called sanitizeFilter that is meant to stop exactly this kind of input. In older versions it was silently skipped for countDocuments() queries. The bug is CVE-2026-125120, and it is fixed in Mongoose 9.9.2, 8.24.3, 7.8.12 and 6.13.11.
The fixes are three months old. All four releases went to npm on Aug 10, 2026, with a one-line changelog entry. The advisory only went public on Nov 10, which is when npm audit and Dependabot started flagging it. If your lockfile has moved since August, you may already be done. Mongoose gets about 8.9 million npm downloads a week, so plenty of lockfiles have not.
How to fix
Upgrade to the patched release on your major line, or anything newer:
# Mongoose 9
npm install mongoose@^9.9.2
# Mongoose 8
npm install mongoose@^8.24.3
# Mongoose 7
npm install mongoose@^7.8.12
# Mongoose 6
npm install mongoose@^6.13.11
As of Nov 11 the latest releases are 9.12.1, 8.24.8, 7.8.13 and 6.13.12, so any current install on a supported line is past the fix. Mongoose 5 has no patched release listed in the advisory.
Check whether this applies to you
Two searches answer it. First, see which version you actually run, including copies pulled in by other packages:
npm ls mongoose
Second, see whether your code relies on the setting at all:
grep -rn "sanitizeFilter" --include=*.js --include=*.ts src/
If that search finds nothing, this CVE does not change your risk, for a reason covered below. If it finds mongoose.set('sanitizeFilter', true), a connection option, or setOptions({ sanitizeFilter: true }), look for countDocuments( and .cursor( calls that take a filter built from request data. Those are the calls that were unprotected.
Who is not affected
- Apps that never turned
sanitizeFilteron. The Mongoose source documents it asfalseby default. Without it, no query in your app was getting this protection in the first place, so the bug takes nothing away. That is not good news on its own: it means every query that takes raw request objects needs its own input checks. - Apps that only pass plain strings and numbers into filters. The attack needs the app to put a whole object from the request, like
{ "$ne": null }, into the query. If your code casts input with something likeString(req.body.email)or validates it with a schema library before querying, there is nothing to inject. - Anyone on a patched or newer release. See the version list above.
What this is not
This is not a way to run code on your server, and it does not hand anyone your database by itself. It is a filter bypass on one query method, and that method returns a number, not records. The advisory scores it 9.8, which describes the worst case for injection in general more than what a count query gives an attacker. GitHub's advisory and CISA's list show no reports of exploitation.
What goes wrong in practice
sanitizeFilter works by wrapping any object whose keys start with $ in $eq, so { pwd: { $ne: null } } becomes a search for a password literally equal to that object, which matches nothing. Before the fix, countDocuments() cast its filter with a code path that never applied that wrapping. The patch swaps it for the same _castConditions() path that find() and findOne() already used.
A count is a yes or no answer, and that is enough. Picture a route like /api/check-email that runs User.countDocuments({ email: req.body.email }) to say whether an address is taken. Send { "email": { "$regex": "^a" } } instead of a string and the count tells you whether any user's email starts with "a". Repeat letter by letter and you can read out values from fields you can filter on, including reset tokens if a count query touches them. The patch also adds a test that $where, which runs JavaScript inside MongoDB, is now refused on count queries when the setting is on.
How easily request data turns into objects depends on your framework. Express 4 parses query strings with the qs module, so ?email[$ne]=x becomes an object. Express 5 changed the default query parser to simple, which only produces strings. JSON request bodies produce objects in both, so a login or lookup endpoint that reads req.body is the usual way in.
The advisory names one method; the patch fixed two
The advisory title only mentions countDocuments(). The fix commits also change Query.prototype.cursor(), and the changelog for all four patched releases lists the fix for both countDocuments and cursor. On older lines the backport covers the legacy count() method too. cursor() matters more than a count, because it streams back whole documents. If you use Model.find(filter).cursor() to page through results for an API, that path was also unprotected, and the advisory's workaround does nothing for it.
If you cannot upgrade today
The advisory offers a plugin as a workaround, but as published it will not run: the plugin function takes no argument and then calls schema.pre, so it throws a ReferenceError when Mongoose applies it. The working version takes the schema as its parameter:
mongoose.plugin(function(schema) {
schema.pre('countDocuments', function() {
mongoose.sanitizeFilter(this.getFilter());
});
});
Register it before you call mongoose.model(), because global plugins only apply to models compiled after them. It only covers countDocuments(), not cursor(), so treat it as a stopgap. The stronger habit is to stop passing request objects straight into filters at all: cast fields to strings or validate the body before it reaches a query, and keep sanitizeFilter as a second layer.
If AI coding agents write some of your API routes, this is a good bug to check their output for, since "look up whether this email exists" is exactly the kind of route they generate quickly. HOL Guard is the tool we build for keeping a check on what agents do on your machine; it does not audit your app's queries, so the grep above is still your test here.
Timeline
- Aug 10, 2026: Mongoose 9.9.2, 8.24.3, 7.8.12 and 6.13.11 published to npm with the fix.
- Nov 10, 2026: advisory GHSA-pcrw-h46h-c93h and CVE-2026-125120 published, rated critical (CVSS 3.1 score 9.8, CWE-943).
Credit to koyokr for finding it. For other Node.js packages we have covered recently, see the body-parser compressed body bug, the Multer aborted upload bug and the October Next.js security update.
What do people ask about CVE-2026-125120?
Which Mongoose versions are affected by CVE-2026-125120?
Versions before 6.13.11, 7.0.0 through 7.8.11, 8.0.0 through 8.24.2, and 9.0.0 through 9.9.1. Upgrade to 9.9.2, 8.24.3, 7.8.12 or 6.13.11 or later.
Am I affected if I never set sanitizeFilter?
No. The bug is that the setting was skipped for countDocuments. If you never turned the setting on, none of your queries had that protection, so you need your own input validation either way.
Does CVE-2026-125120 let attackers read my whole database?
Not directly. countDocuments returns a number, so an attacker learns yes or no answers and has to guess values piece by piece. The related cursor() gap could return whole documents, and the same patch fixed it.
Does the advisory workaround work?
Not as printed. The plugin function needs schema as its parameter. Even fixed, it only covers countDocuments, not cursor(), so upgrading is the real fix.
Is CVE-2026-125120 being exploited?
There are no public reports of exploitation, and it is not on CISA's Known Exploited Vulnerabilities list.
