Blog/Engineering

Node.js Security Checklist for Enterprise Teams (OWASP-Aligned, 2026)

By GauravMay 14, 202614 min read
Node.js Security Checklist for Enterprise Teams (OWASP-Aligned, 2026)

Node.js security in production is a discipline, not a feature. The runtime itself is reasonable. The npm ecosystem, the patterns engineers learn from tutorials, and the speed at which teams ship are what create most incidents. This checklist is OWASP-aligned and built from the actual incidents we have seen across enterprise Node.js codebases in 2025 and 2026.

If you need engineers who already work this way, see our Node.js hiring page. For our broader AI-era security work, read our OWASP LLM Top 10 implementation checklist and prompt injection defense guide.

What is the actual threat model for an enterprise Node.js service?

Before any control, name the threats:

  • External attackers hitting your public APIs, abusing weak validation or auth.
  • Compromised dependencies shipped via npm, executed at install or runtime.
  • Insider threats with legitimate access using over-broad permissions.
  • Lateral movement from one compromised service to others sharing networks or secrets.
  • Data exfiltration via SSRF, log leakage, or backup misconfiguration.

Different controls address different threats. Listing the threats first stops you from adding controls that solve nothing.

How do you validate input properly?

Every endpoint that accepts a request body, query string, header, or path parameter validates it. No exceptions. The patterns:

  • Schema validation at the framework boundary. zod, class-validator, or Fastify's JSON schema. Reject invalid input before it touches your domain code.
  • Allowlist, not denylist. Specify exactly what shapes are valid. Anything else is rejected.
  • Strict types after validation. Once validated, the rest of the code uses TypeScript types from the schema. No any.
  • Size limits. body-parser or framework defaults often allow megabytes. Cap to what you actually need.
  • Content-type enforcement. Reject unexpected content types instead of trying to parse them.

The single most common Node.js vulnerability we find on audit is an endpoint that trusts the request body shape because the TypeScript interface "looks right." TypeScript types do not exist at runtime. Validation does.

How do you avoid prototype pollution?

Prototype pollution is uniquely Node.js-shaped. An attacker mutates Object.prototype via a JSON payload, then your code reads a polluted property and behaves wrongly. Defenses:

  • Use validation libraries that reject __proto__ and constructor keys. zod and Joi do this. Hand-rolled parsers often do not.
  • Use Object.create(null) or Map for user-controlled key spaces. No prototype to pollute.
  • Avoid recursive merge utilities on untrusted input. lodash.merge had this exact CVE. Use safer alternatives or write the merge yourself.
  • Run npm audit and Snyk on every build. Most prototype pollution CVEs land in transitive dependencies.

What are the JWT pitfalls and how do you avoid them?

JWTs are misused more than any other auth primitive in Node.js. The recurring problems:

  1. Treating JWTs as bearer truth without revocation. Once issued, you cannot un-issue. Use short TTLs (5 to 15 minutes) and refresh tokens with revocable sessions.
  2. Accepting alg: none or weak algorithms. Pin the algorithm explicitly when verifying. Never trust the header.
  3. Missing audience and issuer checks. A token issued for service A should not be accepted by service B.
  4. Storing secrets in JWTs. JWTs are signed, not encrypted. Anyone with the token reads the payload.
  5. Long-lived JWTs as session cookies. Use a real session store (Redis) with a session ID cookie. JWTs do not give you what cookies give you for free.

For most enterprise cases, treat JWTs as access tokens with very short TTLs and back them with a revocable refresh token model. If you cannot revoke a credential, you cannot recover from a leak.

How do you prevent SSRF in webhook and integration handlers?

Server-Side Request Forgery is the most underrated Node.js vulnerability. Any time your service makes an outbound HTTP request to a URL the user can influence, the user can potentially:

  • Hit internal-only services (cloud metadata, private databases, internal admin endpoints)
  • Scan your internal network
  • Steal cloud credentials from instance metadata (169.254.169.254)

Defenses:

  • Allowlist destination hosts. Webhook and import endpoints should only call hosts you explicitly trust.
  • Resolve and validate the IP after DNS lookup. Block private and link-local ranges. Beware DNS rebinding (validate twice or pin the resolved IP).
  • Run integrations from a network with no access to internal services. Defense in depth at the infra layer.
  • Set timeouts and response size limits. An attacker-controlled URL can hang or stream forever.

How do you secure the npm supply chain?

Your code is a small fraction of what runs in production. The rest is npm. Treat dependencies like third-party code that wants to break in.

  • Lockfiles always. package-lock.json or pnpm-lock.yaml committed. CI installs from lockfile only.
  • Dependency review. Every new dependency gets reviewed: who maintains it, how often, what permissions does it need.
  • SCA on every build. npm audit, Snyk, Socket, or GitHub Dependabot. Block PRs with high or critical issues.
  • Pin or scope automatic updates. Renovate or Dependabot with grouped weekly updates is better than auto-merge.
  • Disable lifecycle scripts where possible. npm install --ignore-scripts in CI for known-good dependencies. Many supply-chain attacks fire at install time.
  • Use a private registry mirror. Verdaccio, JFrog, or AWS CodeArtifact. Cache and scan packages before they hit your build.
  • Watch for typosquats. express-form vs express_form is exactly how a real-world incident lands.

If a dependency disappears (left-pad, color, etc.), your build breaks. If a dependency is compromised, your build runs attacker code as root. Both are real outcomes from the last few years.

How do you handle secrets and credentials?

  • No secrets in env vars in code. Use a secret manager (AWS Secrets Manager, Vault, Doppler).
  • No secrets in logs. Structured logger with redaction, plus a CI test that catches obvious leaks.
  • No secrets in git. git-secrets, gitleaks, or trufflehog in pre-commit and CI.
  • Short-lived credentials. IAM roles for AWS, workload identity in Kubernetes, no long-lived API keys where avoidable.
  • Per-service credentials. Service A and service B do not share a database password. Compromise of one stays contained.

What about authentication and authorization?

  • Use a battle-tested library. Passport, Clerk, Auth0, or your platform's built-in. Do not build auth from scratch.
  • Multi-factor for any privileged user. Engineers, admins, finance roles.
  • Authorization on every endpoint. Even internal endpoints. Default deny.
  • Centralize permission decisions. A single policy service or library so you can audit and change rules in one place.
  • Audit logs for sensitive actions. Who did what, when, from where. Tamper-evident storage.

How do you secure Node.js services at the network and runtime layer?

  • Run as non-root in containers. USER directive in Dockerfile. Most npm exploits fail without root.
  • Read-only root filesystem where possible. Reduces attacker mobility post-exploit.
  • mTLS between internal services. Service mesh or framework-level mTLS.
  • Network policies in Kubernetes. Default deny ingress and egress, allowlist what each service needs.
  • Egress filtering. Especially for services that should never call the public internet.
  • Container scanning in CI. Trivy, Grype, or your registry's built-in scanner.

How do you respond when something goes wrong?

Even with every control, you will have incidents. The minimum incident readiness:

  1. Detection. Log aggregation with anomaly alerting. Failed-auth spikes, unusual outbound traffic, unexpected error patterns.
  2. Runbooks. Top five incident classes with explicit steps, including how to revoke tokens, rotate credentials, and isolate a service.
  3. Tabletop exercises. Walk through a credential leak or compromised dependency once a quarter. The first real run is not the time to learn.
  4. Post-mortems with actions. Every incident produces concrete follow-ups with owners and dates. Track them to done.

Where does Workforce Next help?

We place Node.js engineers who treat security as part of their job, not as a separate team's problem. Most have shipped production code with input validation, JWT and session design, dependency hygiene, and SSRF defenses already wired in. If you want help raising the security baseline of your Node.js estate, see our Node.js hiring page or talk to us about your stack.

Frequently asked questions

What are the most common Node.js security issues in production?
Missing input validation, JWT misuse without revocation, prototype pollution from untrusted JSON, SSRF in webhook handlers, and supply-chain compromise via npm. None of these are exotic. Most teams know about them and ship the bug anyway because the basics get skipped under deadline pressure.
How should we validate input in a Node.js service?
Use schema validation at the framework boundary with zod, class-validator, or Fastify JSON schema. Allowlist what is valid and reject everything else. Enforce size limits and content types. After validation, use the schema's TypeScript types for the rest of the code. Never trust that an interface matches runtime data.
What is prototype pollution and how do we prevent it?
An attacker mutates Object.prototype via a JSON payload, then your code reads a polluted property and behaves wrongly. Prevent it by using validation libraries that reject __proto__ and constructor keys, using Object.create(null) or Map for user-controlled key spaces, and avoiding recursive merge utilities on untrusted input.
What are the most common JWT mistakes in Node.js?
Treating JWTs as bearer truth without revocation, accepting weak algorithms or alg: none, missing audience and issuer checks, storing secrets in JWT payloads (which are signed but not encrypted), and using long-lived JWTs as session cookies. Use short TTLs with revocable refresh tokens for most enterprise cases.
How do we prevent SSRF in webhook and integration handlers?
Allowlist destination hosts, resolve and validate the IP after DNS lookup to block private and link-local ranges, run integrations from a network with no access to internal services, and set strict timeouts and response size limits. Beware DNS rebinding by validating the resolved IP twice or pinning it.
How do we secure the npm supply chain?
Always commit lockfiles. Run SCA tools like npm audit, Snyk, or Socket on every build and block high or critical issues. Review every new dependency before adding it. Use a private registry mirror, disable lifecycle scripts where possible, and watch for typosquatted package names.
Where should secrets live in a Node.js application?
Never in code or hard-coded env vars. Use AWS Secrets Manager, HashiCorp Vault, or Doppler. Use short-lived credentials via IAM roles or workload identity. Per-service credentials so compromise of one service does not expose another. Run gitleaks or trufflehog in pre-commit and CI to catch leaks.
Should we build authentication ourselves in Node.js?
No. Use a battle-tested library or service like Passport, Clerk, or Auth0. Build authorization on top with default deny on every endpoint, multi-factor for privileged users, centralized permission decisions, and audit logs for sensitive actions in tamper-evident storage.

Ready to build your team?

Tell us what you are building and we will find the right engineers for your project. 48-hour matching, 1-week paid trial.