NodeJSNode.js Security Overview

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.

Every input that reaches your server is untrusted until validated and sanitized
HTTP is unauthenticated at the transport level — anyone can send any bytes to your server. Headers can be forged, cookies can be replayed, JSON bodies can be crafted by curl, and query strings can contain anything. The entire [validation section](/nodejs/validation-intro) and [auth section](/nodejs/auth-intro) rest on this axiom: validate structure and types with a schema, authorize against a verified identity, and never make security decisions based on client-supplied values you haven't independently verified.
Quick-wins: what every Express app should have on day one

JS
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
Defaults are insecure — Express does not ship with security headers or rate limits
A plain `express()` app has no security headers (your responses advertise X-Powered-By and lack Content-Security-Policy), no body-size limits (an attacker can send a gigabyte of JSON to crash the process), and no rate limiting (brute-force attacks proceed unchecked). These three lines — helmet, body limit, rate limit — are the minimum viable security baseline. Add them before the first deploy, not after the first incident.
Security != perfection
Security is a continuous practice, not a one-time checklist
No application is perfectly secure. The realistic goal is to raise the cost of attack above the value of the target, respond quickly when something is found, and minimise blast radius when a breach does happen. Practical steps: run `npm audit` regularly, use static analysis in CI, rotate secrets, log authentication events, and have a plan for what to do when (not if) an incident occurs. The pages in this section cover the technical controls; the mindset is that you keep revisiting them as the app evolves.
Next
Know what you're up against: [OWASP Top 10 for Node](/nodejs/owasp-top-10).