BugForge
All lessons
Node.js · 18/19 6 min

Web security basics for APIs

Injection, mass assignment, CORS, rate limits and the leaks tests look for.

Injection

  • NoSQL: { email: req.body.email } where email is { "$ne": null } matches everyone. Check typeof === "string".
  • SQL: always use placeholders: db.query("SELECT * FROM users WHERE id = $1", [id]), never string concatenation.
  • Regex: escape user text before new RegExp(q).

Mass assignment

await User.updateOne({ _id: id }, req.body);           // ❌ client sends { role: "admin" }
const { name, bio } = req.body;
await User.updateOne({ _id: id }, { name, bio });      // ✅ allow-list

Broken access control

The most common real-world bug: checking that a user is logged in, but not that they own the record. Every /:id route that returns private data needs an ownership or role check.

Leaks

  • Password hashes, tokens, internal ids in responses.
  • Stack traces in 500 responses.
  • Different messages for "unknown email" and "wrong password" (lets attackers enumerate accounts).

CORS

CORS is enforced by browsers, not by your server. cors() with no options allows every origin; fine for a public API, wrong for cookies. With credentials, set an explicit origin list and credentials: true. CORS does not protect against curl or server-to-server calls.

Rate limiting

Login, signup and password reset need limits per IP and per account. Return 429 with Retry-After. Key by req.ip only after configuring trust proxy correctly, or every user behind the load balancer shares one bucket.

Headers

helmet() sets sensible defaults (no X-Powered-By, content-type sniffing off, and so on). Cheap, and reviewers notice.

Next in Node.js
Reading stack traces and logs