Node.js security best practices in 2026 are about more than adding a few middleware packages. Secure Node.js applications validate every untrusted input, enforce authorization close to the data, keep npm dependencies under control, protect secrets, and run application security checks before code merges.

This guide is built for engineering teams that ship Node.js APIs, Express services, server-rendered apps, background workers, and internal tools. Use it as a practical checklist during code review, CI/CD hardening, and quarterly AppSec reviews.

Node.js security checklist for 2026

Start with these controls:

  • Run a maintained Node.js LTS version and patch runtime images quickly.
  • Validate request bodies, query strings, route parameters, headers, and uploaded files on the server.
  • Encode output and avoid unsafe HTML sinks to prevent XSS.
  • Use parameterized SQL, safe ORM APIs, and strict NoSQL query construction.
  • Enforce authorization on every route and object access.
  • Harden sessions, cookies, JWT handling, and CSRF protections.
  • Set security headers and a Content Security Policy where applicable.
  • Apply request size limits, rate limits, and timeouts.
  • Keep npm dependencies updated and remove packages you do not use.
  • Keep secrets out of source code, logs, Docker images, and CI output.
  • Scan Node.js code, dependencies, secrets, and containers in pull requests.
  • Track reachable endpoints so teams fix exploitable issues first.

If your team already uses static analysis, pair this checklist with Corgea AI SAST for code-level Node.js findings and Corgea dependency scanning for npm package risk.

1. Validate input on the server

Client-side validation improves UX, but it is not a security boundary. Attackers can send requests directly to your API, modify JSON payloads, change headers, and bypass browser controls.

Use a schema validator such as Zod, Valibot, Joi, or a framework-native validator. Keep validation close to the route handler or service boundary so unsafe data does not spread.

import { z } from "zod";

const createUserSchema = z.object({
  email: z.string().email().max(320),
  displayName: z.string().trim().min(1).max(80),
  role: z.enum(["member", "viewer"])
});

app.post("/users", async (req, res) => {
  const parsed = createUserSchema.safeParse(req.body);

  if (!parsed.success) {
    return res.status(400).json({ error: "Invalid request" });
  }

  const user = await createUser(parsed.data);
  res.status(201).json({ id: user.id });
});

Validation should include length limits. Many production incidents come from valid-looking strings that are simply too large, nested too deeply, or expensive to parse.

2. Prevent SQL injection and NoSQL injection

The safest default is to never concatenate untrusted data into a query. Use parameterized queries, prepared statements, and ORM query builders that bind values separately from query structure.

// Safer: the driver binds userId as data, not SQL syntax.
const result = await db.query(
  "SELECT id, email FROM users WHERE id = $1 AND organization_id = $2",
  [userId, organizationId]
);

NoSQL injection deserves the same attention. Do not pass user-controlled objects directly into MongoDB query operators.

// Risky if req.body.email is an object like { "$ne": null }
await users.findOne({ email: req.body.email });

// Safer: coerce and validate the expected primitive type first.
const email = z.string().email().parse(req.body.email);
await users.findOne({ email });

For a deeper primer, read Corgea’s guide to SQL injection.

3. Enforce authorization on every route and object

Authentication answers “who is this user?” Authorization answers “can this user perform this action on this object?” Node.js security failures often happen because a route checks login state but not ownership.

app.get("/invoices/:invoiceId", requireUser, async (req, res) => {
  const invoice = await invoices.findById(req.params.invoiceId);

  if (!invoice || invoice.organizationId !== req.user.organizationId) {
    return res.status(404).json({ error: "Not found" });
  }

  res.json(invoice);
});

Returning 404 for unauthorized object access can avoid disclosing whether an object exists. More importantly, the object-level authorization check belongs in the code path that reads or mutates the object, not just in a UI component.

Corgea attack surface mapping helps teams connect reachable endpoints to vulnerable code paths so authorization gaps get prioritized by exposure.

4. Harden Express and API middleware

Express is not insecure by default, but it is intentionally minimal. Production services need explicit middleware choices:

  • Disable the X-Powered-By header.
  • Use helmet for security headers.
  • Set JSON body size limits.
  • Apply CORS only to trusted origins.
  • Add rate limits to login, password reset, token exchange, and expensive endpoints.
  • Use centralized error handling that does not leak stack traces.
import helmet from "helmet";
import rateLimit from "express-rate-limit";

app.disable("x-powered-by");
app.use(helmet());
app.use(express.json({ limit: "1mb" }));

app.use("/auth/login", rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 20,
  standardHeaders: true,
  legacyHeaders: false
}));

If your application is Express-heavy, read the dedicated Express.js security best practices guide.

5. Secure cookies, sessions, and JWTs

For cookie-backed sessions:

  • Set HttpOnly, Secure, and SameSite.
  • Use short idle timeouts for sensitive apps.
  • Rotate session IDs after login and privilege changes.
  • Store session secrets outside source control.
  • Do not use default cookie names that fingerprint the framework.
app.use(session({
  name: "sid",
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,
    secure: true,
    sameSite: "lax",
    maxAge: 60 * 60 * 1000
  }
}));

For JWTs, avoid treating the token as a permission cache forever. Verify issuer, audience, algorithm, expiration, and key rotation. Keep high-risk authorization decisions backed by server-side data where possible.

6. Set security headers and Content Security Policy

Security headers reduce browser-side attack surface:

Content-Security-Policy: default-src 'self'; object-src 'none'; frame-ancestors 'none'
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()

Content Security Policy is especially useful for reducing XSS impact. Start in report-only mode if your app has legacy scripts, then tighten sources as you remove inline code.

7. Manage npm dependency risk

Node.js teams inherit a large software supply chain through npm. A single package update can pull in new transitive dependencies, install scripts, or vulnerable code.

Practical npm controls:

  • Commit lockfiles and use deterministic installs such as npm ci.
  • Remove unused dependencies and dev dependencies from production images.
  • Review packages with install scripts, native extensions, or suspicious maintainership changes.
  • Use minimum release age policies where available to avoid brand-new malicious packages.
  • Monitor advisories and prioritize reachable or heavily used vulnerable packages.

Corgea dependency scanning helps teams prioritize npm vulnerabilities with usage context instead of treating every advisory as equally urgent.

8. Keep secrets out of code and builds

Never store API keys, database passwords, signing keys, or JWT secrets in source code. Also check the less obvious places:

  • .env files accidentally committed to Git
  • test fixtures and local config examples
  • CI logs and failed build output
  • Docker image layers
  • frontend bundles
  • error reporting breadcrumbs

Use environment-specific secret stores and scan pull requests with Corgea secrets scanning before credentials spread across branches, artifacts, and deployments.

9. Secure file uploads and filesystem access

File upload endpoints need strict controls:

  • Authenticate and authorize the upload action.
  • Limit file size and count.
  • Validate MIME type and magic bytes.
  • Generate server-side filenames.
  • Store files outside the web root or in private object storage.
  • Scan high-risk uploads before processing.
  • Never pass user-controlled paths into filesystem APIs.
import path from "node:path";

const baseDir = "/srv/app/uploads";
const safeName = `${crypto.randomUUID()}.pdf`;
const destination = path.join(baseDir, safeName);

if (!destination.startsWith(baseDir)) {
  throw new Error("Invalid upload path");
}

Path traversal is often easy to miss in review because the vulnerable code looks like ordinary file handling. This is exactly the kind of pattern that should be covered by static application security testing.

10. Run Node.js security checks in CI/CD

Run security checks where developers already review code:

  • SAST for JavaScript and TypeScript code
  • dependency scanning for npm packages
  • secrets scanning for committed credentials
  • container scanning for Node.js runtime images
  • IaC scanning for deployment manifests

For implementation patterns, see how to integrate static analysis tools into your CI/CD pipeline. The most effective rollout starts in pull requests with clear, low-noise findings and fix guidance.

Try Corgea scanning for Node.js

Want these Node.js security best practices enforced automatically? Try Corgea for Node.js and JavaScript applications:

Try Corgea today or book a custom demo for your Node.js services.

If you are comparing AppSec platforms, start with Corgea vs Snyk, Corgea vs Semgrep, and Corgea vs GitHub Advanced Security. If your team is evaluating broader enterprise SAST programs, also review Corgea vs Checkmarx.

FAQ

What are the most important Node.js security best practices?

The most important Node.js security best practices are validating input on the server, using parameterized database queries, enforcing authorization on every object access, hardening sessions and cookies, keeping npm dependencies updated, protecting secrets, and scanning code in pull requests.

Is Express security different from Node.js security?

Express security is a framework-specific layer of Node.js security. Node.js teams still need language and runtime controls, while Express apps also need secure middleware, request limits, security headers, route authorization, and safe session handling.

How do I reduce npm package security risk?

Use lockfiles, remove unused packages, pin and review high-risk dependencies, monitor advisories, and prioritize fixes with dependency scanning that understands which packages are actually used.

Can Corgea scan Node.js applications?

Yes. Corgea AI SAST helps scan Node.js and JavaScript code for code-level vulnerabilities, while Corgea dependency scanning helps prioritize vulnerable npm packages in developer workflows and CI/CD.