This is a real Mesrai review on a Mongo-backed search endpoint. The team was using the `$where` operator to support a 'contains-style' lookup. The PR was small. The bug was severe enough to bring the server down with a single request.
The vulnerable diff
// api/search-users.ts — original PR
import { Router } from "express";
import { db } from "../db";
export const searchUsersRouter = Router();
searchUsersRouter.get("/users", async (req, res) => {
const q = req.query.q as string;
if (!q) return res.status(400).json({ error: "q required" });
// BUG: $where evaluates the string as JavaScript on the server
const users = await db.users.find({
$where: `this.username == '${q}'`,
});
return res.json({ users });
});What is wrong
The `$where` operator in MongoDB accepts a JavaScript expression and evaluates it on the server, in the database process, against every document considered. When user input is interpolated into that expression, the user controls arbitrary JavaScript execution — the same security model as an `eval()` of untrusted input. This is CWE-943 (Improper Neutralization of Special Elements in Data Query Logic) and the MongoDB documentation has carried a warning against this pattern for over a decade.
The attack
With one crafted query string, an attacker takes down the database thread pool:
# Single request, infinite loop server-side
curl 'https://api.example.com/users?q=x%27%3B%20while(true)%20%7B%7D%3B%20var%20y%20%3D%20%27x'
# Decoded q: x'; while(true) {}; var y = 'xThe resulting `$where` string becomes `this.username == 'x'; while(true) {}; var y = 'x'` — a hot loop that hangs the database worker. Repeat the request and you have a one-shot denial of service. Data exfiltration is also possible — the JavaScript context has access to document fields.
Mesrai's review comment
mesraipilot · Bot · reviewed 2 min ago
[mesrai] [code-review] [Security] [CWE-943] [critical]
`$where` evaluates a string as JavaScript on the MongoDB server. With
`q` from `req.query.q`, you have arbitrary server-side code execution
from untrusted input.
This is not theoretical — a single request `?q=x'; while(true){}; var y='x`
will hang the database worker. The MongoDB docs have warned about this
pattern since 2014.
Replace `$where` with a structured query. The query you actually want
here is an exact-match on `username`:
const users = await db.users.find({ username: q });
If you need partial matching, use a regex (escaped) or a text index:
const users = await db.users.find({
username: { $regex: escapeRegex(q), $options: "i" }
});
Reference: https://www.mongodb.com/docs/manual/reference/operator/query/where/#securityThe fix
// api/search-users.ts — fixed
const users = await db.users.find({ username: q });
// or, for partial matching with a safe regex:
function escapeRegex(s: string) {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
const users = await db.users.find({
username: { $regex: escapeRegex(q), $options: "i" }
});The structured form lets MongoDB's query engine handle the lookup — no JavaScript evaluation, no injection vector, and (incidentally) it can use the `username` index, which the `$where` form could not.
Why human review missed it
Two reasons. The `$where` operator looks ordinary if you have not seen the warning before — it reads like any other Mongo query operator. And the team's lint config did not include a rule for it because the rest of the codebase used the Mongoose ODM, which encourages structured queries by default. This file was a one-off raw query, and there was no automated check to catch it.
Related rules + further reading
Mesrai rule pack: security/no-mongo-where — flags any use of $where with interpolated input.
MongoDB docs: Server-side JavaScript security — official warning, recommends disabling JS execution entirely with --noscripting where possible.
CWE-943 — Improper Neutralization of Special Elements in Data Query Logic. Parent class for NoSQL + SQL + LDAP + others.
Takeaway
`$where` with user input is a textbook injection vector. Mesrai catches the pattern in two minutes. If you have any Mongo queries with `$where` in your codebase right now, audit them today — there is no safe way to use that operator with user-controlled strings.