Developed By Ken Key

Node.js Security in 2026: What Actually Matters (and What Doesn’t)

By Ken Key Jan 15, 2026 5 min read Updated Oct 16, 2026

Node.js Security in 2026: What Actually Matters (and What Doesn’t)
The short answer

Supply chain attacks are the main Node.js threat in 2026, so I start with dependencies: run npm audit, use npm ci with a committed lockfile, and disable install scripts where possible. Your own code is usually fine. The 400 packages under it are not.

Part of the complete web development guide.

On this page

Most Node.js security advice you find online is a list of twenty things, half of which stopped mattering years ago. The real attack surface in 2026 is narrower than people think, but the mistakes are more expensive because everything is connected. Here’s what I actually check when I audit a Node app, in the order I check it.

Start With Dependencies, Because That’s Where You’ll Get Hit

Your own code is probably fine. The 400 packages under it are not. Supply chain attacks are the dominant Node.js threat right now, and they don’t look like the old “typosquatted package” stories. They look like a maintainer’s npm token getting phished, or a package adding a postinstall script that phones home during your CI build.

First thing: run npm audit and actually read it. Not the summary line. The details. Then set up automated scanning that fails your build on high and critical findings.

# Check what you actually ship, not just what's in package.json
npm audit --omit=dev --audit-level=high

# See the full dependency tree, including transitive deps
npm ls --all --depth=10

# Lockfile integrity: make sure CI uses the lockfile, never ignores it
npm ci # not npm install

Three habits that matter more than any scanner:

  • Commit your lockfile and use npm ci in CI. npm install can silently resolve to a different version than what you tested.
  • Disable lifecycle scripts where you can. npm 11+ supports --ignore-scripts, and it blocks a whole class of postinstall attacks.
  • Pin your direct dependencies and review every new one. If a package has one maintainer, no tests, and was published last week, you don’t need it.

Input Validation Is Still the Whole Game

Injection doesn’t go away, it just changes shape. NoSQL injection, prototype pollution, and template injection are the 2026 versions of the same old problem: you trusted user input.

If you’re on MongoDB, remember that query operators are objects. Passing a raw request body into a query is how {"$gt": ""} ends up logging someone in.

// BAD: attacker sends { "email": { "$gt": "" }, "password": { "$gt": "" } }
const user = await db.collection('users').findOne(req.body);

// GOOD: validate and coerce the shape first
import { z } from 'zod';

const LoginSchema = z.object({
 email: z.string().email().max(254),
 password: z.string().min(12).max(128),
});

const parsed = LoginSchema.safeParse(req.body);
if (!parsed.success) return res.status(400).json({ error: 'Invalid input' });

const user = await db.collection('users').findOne({
 email: parsed.data.email,
 passwordHash: await hash(parsed.data.password),
});

Validate at the boundary, once, and pass typed data inward. If you’re doing this by hand in every route handler, you’re going to miss one, and the one you miss will be the one that matters.

Prototype Pollution Is Quietly Common

Any code that merges user-controlled objects into an existing object is a candidate. Recursive merge functions from older utility libraries are the usual culprit. Modern versions of lodash, and any library that uses Object.create(null) internally, are safe. If you wrote your own deep merge, check it against __proto__, constructor, and prototype keys.

Authentication: Stop Rolling Your Own Sessions

I see more broken auth than broken crypto, and it’s always the same pattern: someone built a session system from scratch because a library felt heavy. Use a maintained library. If you’re doing JWT, the mistakes are predictable:

  • Not pinning the algorithm. jwt.verify(token, secret) without algorithms: ['HS256'] has bitten people for a decade.
  • Long-lived tokens with no revocation path. If you can’t log someone out server-side, you don’t have real auth.
  • Storing tokens in localStorage. Any XSS becomes full account takeover. Use httpOnly, Secure, SameSite cookies.
import jwt from 'jsonwebtoken';

// Verify with an explicit algorithm allowlist
const payload = jwt.verify(token, process.env.JWT_SECRET, {
 algorithms: ['HS256'],
 issuer: 'your-app',
 audience: 'your-api',
 clockTolerance: 5,
});

For passwords, Argon2id is the current default recommendation. bcrypt is still acceptable if you’re already on it, but bump the cost factor as hardware gets faster. And always use a constant-time comparison for anything token-like, because === leaks timing information.

The Server-Level Stuff People Forget

Application code gets all the attention, but a surprising amount of real-world damage happens at the HTTP layer.

Set a Content Security Policy

CSP is the single highest-use header you can add. It’s also the one people skip because it’s annoying to get right. Start in report-only mode, watch the violations for a week, then enforce.

import helmet from 'helmet';

app.use(helmet({
 contentSecurityPolicy: {
 directives: {
 defaultSrc: ["'self'"],
 scriptSrc: ["'self'"], // no 'unsafe-inline', ever
 objectSrc: ["'none'"],
 baseUri: ["'self'"],
 frameAncestors: ["'none'"],
 },
 },
 crossOriginEmbedderPolicy: true,
 hsts: { maxAge: 31536000, includeSubDomains: true, preload: true },
}));

Rate Limit by Identity, Not Just IP

IP-based rate limiting is trivially bypassed with a proxy pool. Limit on the account or API key for authenticated routes, and on IP only for login and signup where there’s no identity yet. Add a small delay on failed logins and log the attempts. You don’t need a WAF to stop credential stuffing, you need a counter.

Run the Process as a Non-Root User

This is boring and it’s the difference between a contained incident and a bad week. In Docker, create a user, drop to it, and mount your filesystem read-only where you can.

FROM node:22-alpine

WORKDIR /app
COPY --chown=node:node . .
RUN npm ci --omit=dev --ignore-scripts

USER node
EXPOSE 3000
CMD ["node", "server.js"]

What I’d Actually Do First

If you have a Node app in production and you’re not sure where you stand, do these in order:

  • Run npm audit --omit=dev and fix anything critical today.
  • Grep your codebase for jwt.verify and confirm every call pins the algorithm.
  • Check that your session cookies are httpOnly and Secure, and that you have a server-side revocation path.
  • Add helmet with a real CSP, starting in report-only.
  • Confirm your container isn’t running as root.

That list takes an afternoon and closes most of the realistic gaps. The rest is maintenance, and maintenance is easier when the app is built on a foundation that doesn’t fight you. If you’re working with a codebase that’s grown organically and needs a security pass plus some structural cleanup, custom web development work like that is most of what I do. I’m based on Long Island, so if you’re local, we can also just grab coffee and look at the code together.

FAQ

Is npm audit enough to catch supply chain attacks?

No. It catches known vulnerabilities in published versions. It won’t catch a malicious new release that hasn’t been flagged yet. Pair it with lockfile pinning, --ignore-scripts, and a policy of reviewing new dependencies before you add them.

Should I use JWT or server sessions in 2026?

For a normal web app, server sessions with httpOnly cookies are simpler and safer. JWTs make sense for stateless APIs called by multiple services, or when you genuinely can’t share session state. If you pick JWT, plan for revocation from day one.

How often should I update dependencies?

Patch and minor updates continuously through automation you trust. Major versions on a schedule, maybe quarterly, so you’re never three versions behind on something security-relevant. The teams that get burned are the ones that let it slide for a year and then have to do it all at once.

Have a project like this?

I do custom web development for Long Island businesses. If you need this done right, let’s talk.