MongoDB queries are objects, so if you pass a raw request value straight into a query, an attacker can send an operator object instead of a plain value. A classic example turns a login check into one that always passes. The fix is to validate and cast inputs to the type you expect, so a value is always a value, never an operator.
Why it's a problem
If your login query is find({ user, password }) and the client can send { "password": { "$ne": null } }, the query matches any record with a non-null password, bypassing the check entirely. The same technique extracts data or alters query logic anywhere unvalidated request input reaches a query.
The pattern
// req.body can be { "password": { "$ne": null } }
const user = await User.findOne({
username: req.body.username,
password: req.body.password,
});The fix
// force values to strings so operators can't be injected
const username = String(req.body.username);
const password = String(req.body.password);
const user = await User.findOne({ username, password });
// better: validate with a schema (zod, joi) before queryingWhy AI tools write this
Passing req.body fields straight into a query is the most direct way to write the feature, and it works for normal string input. The operator-injection path only opens when an attacker sends a crafted object, which a happy-path test never does.
The quick fix
- Cast request values to their expected primitive type before using them in a query.
- Validate input with a schema library so only expected shapes reach the database.
- Never pass a raw request object directly into a query filter.