Node.js Security Overview
Security is not a feature you bolt on after launch — it's a set of disciplines woven into every layer of the application. Node.js apps face the same threats as any web application: injection, broken authentication, exposed secrets, vulnerable dependencies, weak transport. The difference is that Node's ecosystem moves fast and the blast radius of a mistake is high — a single npm package with a prototype-pollution bug or a hardcoded secret in source control can compromise the whole system. This section works through each threat category in depth; this page sets the mental model.
The attack surface of a Node.js app
Layer | Threat | Covered in |
|---|---|---|
HTTP headers | Clickjacking, MIME sniffing, referrer leaks | |
Input | Injection, XSS, path traversal | |
Auth | CSRF, session hijacking, token theft | |
Secrets | Leaked keys, hardcoded passwords | |
Dependencies | Supply-chain attacks, CVEs | |
Transport | Interception, downgrade attacks |
The security mindset
Assume breach — design so a compromised component limits the damage to others.
Trust nothing from the client — every header, body, query param, and cookie is attacker-controlled.
Principle of least privilege — each component has access only to what it needs.
Defence in depth — no single control; layer them so breaking one doesn't break all.
Fail secure — when something goes wrong, default to denying access, not granting it.
Keep the surface small — fewer features, dependencies, and open ports = fewer ways in.
Quick-wins: what every Express app should have on day one
import helmet from 'helmet'
import rateLimit from 'express-rate-limit'
import { json } from 'express'
app.use(helmet()) // security headers
app.use(json({ limit: '100kb' })) // body size limit
app.use(rateLimit({ windowMs: 60_000, max: 100 })) // rate limiting
// Never in .env or source control:
// DATABASE_URL, JWT_SECRET, API_KEYS — env variables only