OWASP Top 10 reads like a list. In real SaaS builds it reads like a pattern book — the same few issues show up across OTP platforms, marketplaces, and streaming apps, in slightly different costumes. This is what I actually see in the wild, framed as research.
Injection — still the cheapest win
It's not 2010 SQL-string-concatenation anymore; it's second-order injection and trusting client shape. The fix is the same idea: never interpolate, always parameterize and schema-parse.
- SQL: parameterized queries / the ORM's bind. Never string-build.
- Inputs: zod-parse at the boundary. If it isn't the shape you expect, reject before it touches a query, a shell, or a template.
const Body = z.object({
email: z.string().email(),
amount: z.number().int().positive(),
});
// parse → reject → then use the typed value
const { email, amount } = Body.parse(req.body);
Broken Access Control (IDOR)
This is the one I see most. A user fetches /api/orders/123 and it works —
because the handler checks "is logged in" but not "does order 123 belong to
you". That's an IDOR. Object-level authorization on every fetch/mutate,
not just route-level auth.
const order = await getOrder(id);
if (order.userId !== session.userId) return notFound(); // not 403 — don't leak existence
Return 404, not 403, so you don't confirm the object exists.
Identification & Auth failures
JWT discipline:
- Short access-token TTL (minutes), refresh-token rotation, server-side verification only.
- Invalidate refresh tokens on reuse (rotation detection).
- Don't put secrets in the JWT payload — it's base64, not encrypted.
The mistake I see most: long-lived access tokens stored in localStorage, with no rotation. Treat access tokens like perishables.
Security Misconfiguration
- Verbose errors off in production — stack traces are a map for attackers.
- Secrets in env, never in the bundle or the repo.
process.env.SECRET, not a hardcoded string. - CORS: explicit origins, not
*when credentials are involved. - Headers:
Strict-Transport-Security,X-Content-Type-Options: nosniff, a reasonable CSP.
Vulnerable & Outdated Components
Lockfiles are a dependency manifest and an attack surface. Keep them pinned,
enable Dependabot/Renovate, and review transitive deps. The supply-chain
lesson from the last few years is that your node_modules is part of your
perimeter.
The throughline
Every item above is the same idea in different clothes: don't trust input, don't trust the client, don't trust the caller's claim about identity. Verify at the boundary, authorize at the object, and treat the framework as a tool that still needs you to think. The Top 10 isn't a checklist to memorize — it's a habit to build: assume the input is hostile, then write the handler anyway.