Rules Hub
Coding Rules Library
Rule priority, scope & exceptions
Use this to align rules with the senior-level structure (P0/P1/P2, scope, exceptions/tradeoffs).
Coerce and validate types to prevent NoSQL operator injection
Never feed raw request objects into Mongo/NoSQL filters — enforce scalar types so query operators ($ne, $gt, $where) can't be injected.
Bad example
| 1 | // Express + Mongoose/MongoDB |
| 2 | app.post('/login', async (req, res) => { |
| 3 | const { username, password } = req.body; |
| 4 | // ❌ if body is { username: 'admin', password: { "$ne": null } } |
| 5 | // the filter becomes { password: { $ne: null } } -> matches & bypasses auth |
| 6 | const user = await Users.findOne({ username, password }); |
| 7 | res.json({ ok: Boolean(user) }); |
| 8 | }); |
| 9 |
|
| 10 | // also dangerous: await Coll.find({ ...req.query }); |
Explanation (EN)
JSON request bodies can carry objects, so a field expected to be a string can arrive as { $ne: null } or { $gt: '' }. Passed straight into findOne, these become MongoDB query operators that bypass the intended match — classic NoSQL injection / auth bypass.
Objašnjenje (HR)
JSON tijela zahtjeva mogu nositi objekte, pa polje koje se očekuje kao string može stići kao { $ne: null } ili { $gt: '' }. Proslijeđeni izravno u findOne, postaju MongoDB operatori koji zaobilaze predviđeno podudaranje — klasična NoSQL injekcija / zaobilaženje autentikacije.
Good example
| 1 | import { z } from 'zod'; |
| 2 |
|
| 3 | const LoginSchema = z.object({ |
| 4 | username: z.string().min(1).max(64), |
| 5 | password: z.string().min(1), |
| 6 | }); |
| 7 |
|
| 8 | app.post('/login', async (req, res) => { |
| 9 | // ✅ parse rejects non-string fields before they reach the query |
| 10 | const { username, password } = LoginSchema.parse(req.body); |
| 11 | const user = await Users.findOne({ username }); |
| 12 | const ok = user ? await verifyHash(password, user.passwordHash) : false; |
| 13 | res.json({ ok }); |
| 14 | }); |
Explanation (EN)
A schema (Zod here) coerces and validates each field to a scalar before it is used, so an object payload is rejected outright. The query only ever receives a string, making operator injection impossible. (Passwords are also compared via a hash, not stored in the filter.)
Objašnjenje (HR)
Shema (ovdje Zod) koercira i validira svako polje na skalar prije upotrebe, pa se objektni payload odmah odbacuje. Upit uvijek prima samo string, što čini injekciju operatora nemogućom. (Lozinke se usto uspoređuju preko hasha, a ne pohranjuju u filter.)
Notes (EN)
Same principle for query strings: arrays/objects can appear in req.query (?password[$ne]=). Validate query params too.
Bilješke (HR)
Isto načelo vrijedi za query stringove: nizovi/objekti se mogu pojaviti u req.query (?password[$ne]=). Validiraj i query parametre.
Exceptions / Tradeoffs (EN)
When you intentionally accept operator-based filtering from clients, restrict to an explicit allow-list of fields and operators and reject the $where/$function operators (which evaluate JS) entirely. Sanitizing middleware (e.g. express-mongo-sanitize) is defense in depth, not a substitute for typed validation.
Iznimke / Tradeoffi (HR)
Kad namjerno prihvaćaš filtriranje temeljeno na operatorima od klijenata, ograniči se na eksplicitnu allow-listu polja i operatora te potpuno odbij $where/$function operatore (koji izvršavaju JS). Sanitizacijski middleware (npr. express-mongo-sanitize) je obrana u dubinu, ne zamjena za tipiziranu validaciju.