Skip to content

Security

Input Validation and Query Injection

Operator injection through request bodies, and why you never pass user JSON straight to find().

IntermediateAbout 5 minutes

SQL injection is string concatenation. MongoDB injection is letting the client supply query operators. find(req.body) treats { email: { $ne: null } } as a filter, not as an email. You did not concatenate; you still lost.

// Wrong — body is the query
await users.find(req.body).toArray()

// Right — you choose the keys and types
const email = typeof req.query.email === "string" ? req.query.email : ""
await users.find({ email }).toArray()
Refuse operator objects. Build the filter from scalars you validate.
import { ObjectId } from "mongodb";

if (!ObjectId.isValid(req.params.id)) return res.status(400).end()
const user = await users.findOne({ _id: new ObjectId(req.params.id) })
_id from the URL is a 24-char hex string, then ObjectId — not req.body
  • Allowlist fields. Only email, city, status you named — never spread req.body into find or $set.
  • Scalars. Coerce to string/number/boolean. If typeof email === 'object', reject.
  • Updates. $set: req.body is the same class of bug: a client-sent $rename or nested operator. Pick fields.
  • `$where` / `$function`. Server-side JS in queries. Do not enable; do not take them from users. Practice sandboxes reject them for this reason.

Mongoose strict strips unknown schema paths on save; it does not make User.find(req.body) safe. Collection $jsonSchema does not parse HTTP. Validation is your filter builder. Login must not be findOne({ email, password }) with operator-shaped password — compare a hash in the app after loading by email only.

Interview question

What is NoSQL / MongoDB query injection, and how do you prevent it?

Think about it first.