Your Node.js app is not secure by default.
No security headers, no dependency scanning, no file upload validation.
These 10 packages drop in today — each closing a specific attack vector the framework leaves open.
TL;DR: These 10 tools cover the gap between a working Node.js app and a secure one — all of them free.
Table of Contents
- helmet — HTTP security headers in one middleware call
- express-rate-limit — stop brute force before it hits your handlers
- pompelmi — scan uploaded files before they touch your storage
- joi — schema validation that keeps bad input out of your database
- snyk — catch vulnerable dependencies before they ship
- semgrep — static analysis for security patterns in your code
- jsonwebtoken — JWTs with algorithm control that actually matters
- dotenv-safe — crash loud when environment variables are missing
- audit-ci — block CI builds that introduce known vulnerabilities
- lusca — CSRF protection for session-based applications
1) helmet — security headers in one line
What it is: Express middleware setting 11 security HTTP headers — CSP, X-Frame-Options, HSTS — with safe defaults.
Why it matters in 2026: A vanilla Express app sends no security headers. Clickjacking, MIME sniffing, and missing CSP all have header-level mitigations browsers enforce automatically — but only if the server sends them. There's no argument for skipping this.
Best for: All Express applications, any server serving browser clients
Links: | Website
Helmet
Helmet helps secure Node/Express apps. It sets HTTP response headers such as Content-Security-Policy and Strict-Transport-Security. It aims to be quick to integrate and be low maintenance afterward.
Quick start
import helmet from "helmet";
const app = express();
app.use(helmet());
This will set 13 HTTP response headers in your app.
Helmet can also be used in standalone Node.js, or with other frameworks.
Configuration
Each header can be disabled. To disable a header:
// Disable the Content-Security-Policy and X-Download-Options headers
app.use(
helmet({
contentSecurityPolicy: false,
xDownloadOptions: false,
}),
);
To configure a header, pass header-specific options:
// Configure the Content-Security-Policy header.
app.use(
helmet({
contentSecurityPolicy: {
directives: {
"script-src": ["'self'", "example.com"],
},
},
})…2) express-rate-limit — the simplest fix for brute force
What it is: Express middleware limiting repeated requests per IP with configurable windows and Redis store support.
Why it matters in 2026: Login brute force and credential stuffing require volume. Express ships with no rate limiting. Adding this to auth endpoints takes ten minutes and eliminates a whole class of attacks — pair with Redis and limits work across all instances.
Best for: Login routes, password reset, OTP verification, any sensitive endpoint
express-rate-limit
/
express-rate-limit
Basic rate-limiting middleware for the Express web server
express-rate-limit
Basic rate-limiting middleware for Express. Use to limit repeated requests to public APIs and/or endpoints such as password reset Plays nice with express-slow-down and ratelimit-header-parser.
Usage
The full documentation is available on-line.
import { rateLimit } from 'express-rate-limit'
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
limit: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes).
standardHeaders: 'draft-8', // draft-6: `RateLimit-*` headers; draft-7 & draft-8: combined `RateLimit` header
legacyHeaders: false, // Disable the `X-RateLimit-*` headers.
ipv6Subnet: 56, // Set to 60 or 64 to be less aggressive, or 52 or 48 to be more aggressive
// store: ... , // Redis, Memcached, etc. See below.
})
// Apply the rate limiting middleware to all requests.
app.use(limiter)
Data Stores
The…
3) pompelmi — file upload scanning that runs on your server
What it is: A minimal Node.js wrapper around ClamAV that scans any uploaded file and returns a typed verdict — Clean, Malicious, or ScanError. Zero runtime dependencies, no cloud, no daemon required.
Why it matters in 2026: File uploads are the most underdefended attack surface in most Node.js apps. A user uploads — your code saves it without checking the content. pompelmi adds antivirus scanning as a middleware step, with no data leaving your server.
Best for: Express, Fastify, NestJS, Next.js, SvelteKit, any Node.js app that accepts user file uploads
Links: | Website
pompelmi
/
pompelmi
Minimal Node.js wrapper around ClamAV — scan any file and get Clean, Malicious, or ScanError. Handles installation and database updates automatically.
pompelmi
ClamAV for humans
A minimal Node.js wrapper around ClamAV that scans any file and returns a typed Verdict Symbol: Verdict.Clean, Verdict.Malicious, or Verdict.ScanError. No daemons. No cloud. No native bindings. Zero runtime dependencies.
Table of contents
- Quickstart
- How it works
- API
- Docker / remote scanning
- Examples
- Internal utilities
- Supported platforms
- Installing ClamAV manually
- Testing
- Contributing
- Security
- License
Quickstart
npm install pompelmi
const { scan, Verdict } = require('pompelmi');
const result = await scan('/path/to/file.zip');
if (result === Verdict.Malicious) {
throw new Error('File rejected: malware detected');
}
How it works
- Validate — pompelmi checks that the argument is a string and that the file exists before spawning anything.
-
Scan — pompelmi spawns
clamscan --no-summary <filePath>as a child process and reads the exit code. - Map — the exit…
Your users uploading files to your unprotected endpoint. Source: Giphy
4) joi — server-side validation that doesn't trust the client
What it is: A schema description language and data validator that validates request payloads at runtime against defined shapes and constraints.
Why it matters in 2026: TypeScript types are a compile-time promise — at runtime your API receives whatever the client sends. Joi validates shape, type, and constraints before data reaches business logic. It's the boundary guard your types can't be.
Best for: API request validation, form data, webhook payloads, any externally supplied data
Links: | Website
joi
The most powerful schema description language and data validator for JavaScript.
Installation
npm install joi
Visit the joi.dev Developer Portal for tutorials, documentation, and support
Useful resources
5) snyk — dependency vulnerability scanning in CI
What it is: A CLI scanning package.json and lockfiles against a curated vulnerability database, integrating with GitHub and CI systems.
Why it matters in 2026: Transitive dependencies introduce CVEs silently. npm audit has a high false positive rate; Snyk's curated database integrates into CI, blocking deploys on high-severity findings.
Best for: CI pipelines, pre-commit hooks, scheduled production dependency scans
Links: | Website
Getting started with the Snyk CLI
Introduction to the Snyk CLI
Snyk is a developer-first, cloud-native security tool to scan and monitor your software development projects for security vulnerabilities. Snyk scans multiple content types for security issues:
- Snyk Open Source: Find and automatically fix open-source vulnerabilities
- Snyk Code: Find and fix vulnerabilities in your application code in real time
- Snyk Container: Find and fix vulnerabilities in container images and Kubernetes applications
- Snyk IaC: Find and fix insecure configurations in Terraform and Kubernetes code
The Snyk CLI brings the functionality of Snyk into your development workflow. You can run the CLI locally from the command line or in an IDE. You can also run the CLI in your CI/CD pipeline. The following shows an example of Snyk CLI test command output.
Snyk CLI test command output example
Snyk CLI scanning supports many languages and tools. For detailed…
6) semgrep — static analysis for security patterns
What it is: A static analysis engine using pattern-matching rules to find vulnerabilities in source code without executing it.
Why it matters in 2026: Dynamic testing finds reachable vulnerabilities. Static analysis finds ones in untested code paths — Semgrep's Node.js rules catch SQL injection, prototype pollution, and unsafe eval in seconds.
Best for: Pre-commit hooks, CI pipelines, finding injection vulnerabilities before they ship
Links: | Website
semgrep
/
semgrep
Lightweight static analysis for many languages. Find bug variants with patterns that look like source code.
Code scanning at ludicrous speed
Semgrep is a fast, open-source, static analysis tool that searches code, finds bugs, and enforces secure guardrails and coding standards. Semgrep supports 30+ languages and can run in an IDE, as a pre-commit check, and as part of CI/CD workflows.
Semgrep is semantic grep for code. While running grep "2" would only match the exact string 2, Semgrep would match x = 1; y = x + 1 when searching for 2. Semgrep rules look like the code you already write; no abstract syntax trees, regex wrestling, or painful DSLs.
Note that in security contexts, Semgrep Community Edition will miss many true positives as it can only analyze code within the boundaries of a single function or file. If you want to use Semgrep for security purposes (SAST, SCA, or secrets scanning), the Semgrep AppSec Platform is strongly recommended…
Finding an SQL injection in CI vs finding it in a pentest report. Source: Giphy
7) jsonwebtoken — JWTs with algorithm control
What it is: The standard Node.js library for signing and verifying JSON Web Tokens with explicit algorithm control.
Why it matters in 2026: JWT libraries have a history of alg: none attacks accepting unsigned tokens. jsonwebtoken handles this correctly by default and gives you explicit algorithm control.
Best for: Authentication systems, API token issuance, microservice-to-microservice trust
auth0
/
node-jsonwebtoken
JsonWebToken implementation for node.js http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html
jsonwebtoken
An implementation of JSON Web Tokens.
This was developed against draft-ietf-oauth-json-web-token-08. It makes use of node-jws
Install
$ npm install jsonwebtoken
Migration notes
Usage
jwt.sign(payload, secretOrPrivateKey, [options, callback])
(Asynchronous) If a callback is supplied, the callback is called with the err or the JWT.
(Synchronous) Returns the JsonWebToken as string
payload could be an object literal, buffer or string representing valid JSON.
Please note that
expor any other claim is only set if the payload is an object literal. Buffer or string payloads are not checked for JSON validity.
If
payloadis not a buffer or a string, it will be coerced into a string usingJSON.stringify.
secretOrPrivateKey is a string (utf-8 encoded), buffer, object, or KeyObject containing either the secret for HMAC algorithms or the PEM
encoded private key for RSA and ECDSA. In…
8) dotenv-safe — environment validation that fails loud
What it is: A drop-in dotenv replacement requiring all variables in .env.example to be present at startup.
Why it matters in 2026: An app missing DATABASE_URL doesn't fail at boot — it fails 500ms later with a confusing error buried in a stack trace. dotenv-safe moves this failure to startup where it's obvious and actionable.
Best for: Node.js apps with external dependencies, CI environments, Docker deployments
rolodato
/
dotenv-safe
Load environment variables from .env and ensure they are all present
Identical to dotenv, but ensures that all needed environment variables are defined after reading from .env
The names of the needed variables are read from .env.example, which should be commited along with your project.
dotenv-safe only checks if all the needed variable names exist in process.env after initialising. It does not assume anything about the presence, format or validity of the values.
Installation
npm install dotenv-safe
pnpm install dotenv-safe
yarn add dotenv-safe
Example
# .env.example, committed to repo
SECRET=
TOKEN=
KEY=
# .env, private
SECRET=topsecret
TOKEN=
// index.js
require('dotenv-safe').config();
Or, if you are using ES modules:
// index.mjs
import { config } from 'dotenv-safe';
config();
Since the provided .env file does not contain all the variables defined in
.env.example, an exception is thrown:
MissingEnvVarsError: The following variables…9) audit-ci — CI gate for known vulnerabilities
What it is: A CLI running npm audit with configurable severity thresholds that fails CI builds when new vulnerabilities are introduced.
Why it matters in 2026: npm audit locally is optional. audit-ci in CI makes it mandatory. Allowlist support handles false positives, configurable severity gates block only what matters, and it works with npm, Yarn, and pnpm.
Best for: GitHub Actions, GitLab CI, any pipeline where dependency security is a requirement
IBM
/
audit-ci
Audit NPM, Yarn, PNPM, and Bun dependencies in continuous integration environments, preventing integration if vulnerabilities are found at or above a configurable threshold while ignoring allowlisted advisories
audit-ci
This module is intended to be consumed by your favourite continuous integration tool to
halt execution if npm audit, yarn audit, or pnpm audit finds vulnerabilities at or above the specified
threshold while ignoring allowlisted advisories.
Note: Use our codemod to update to
audit-civ6.0.0
Requirements
- Node >=16
- (Optional) Yarn ^1.12.3 || Yarn >=2.4.0 && <4.0.0
- (Optional) PNPM >=4.3.0
- (Optional) Bun
Limitations
- Yarn Classic workspaces does not audit
devDependencies. See this issue for more information. - Yarn v4 is not supported because it provides similar functionality to
audit-ci. For more information, see the documentation onyarn npm audit. If you'd likeaudit-cito support Yarn v4, voice your opinion on this issue. - Bun is supported by exporting the
bun.lockbinto a Yarn v1yarn.lockfile. Accordingly, auditing abun.lockbfile withaudit-cirequires Yarn v1.
Set up
(Recommended) Install audit-ci during your CI environment…
10) lusca — CSRF protection for session-based apps
What it is: Express middleware for CSRF token validation supporting synchronizer token and double-submit cookie patterns.
Why it matters in 2026: CSRF is declared dead every time SPAs are discussed. It isn't — not for apps with session cookies, form submissions, or state-changing operations without custom headers. Lusca handles the full CSRF lifecycle in a few lines of configuration.
Best for: Server-rendered Express apps, session-based auth, forms that mutate state
lusca
Web application security middleware.
Usage
var express = require('express'),
app = express(),
session = require('express-session'),
lusca = require('lusca');
//this or other session management will be required
app.use(session({
secret: 'abc',
resave: true,
saveUninitialized: true
}));
app.use(lusca({
csrf: true,
csp: { /* ... */},
xframe: 'SAMEORIGIN',
p3p: 'ABCDEF',
hsts: {maxAge: 31536000, includeSubDomains: true, preload: true},
xssProtection: true,
nosniff: true,
referrerPolicy: 'same-origin'
}));
Setting any value to false will disable it. Alternately, you can opt into methods one by one:
app.use(lusca.csrf());…Final thoughts
Most Node.js security incidents are boring gaps: no rate limiting, no headers, no scanning. These 10 tools close each one. What's missing from your stack?




Top comments (0)