VNX-NODE-004 – Express App Without Helmet
Overview
This rule detects Express applications — identified by the presence of express() in a file — that do not import or apply the helmet middleware. Helmet sets a collection of HTTP response headers that instruct browsers to enforce security policies: Content Security Policy, HTTP Strict Transport Security, X-Frame-Options, X-Content-Type-Options, and others. Without these headers, browsers apply permissive defaults that enable clickjacking, MIME-type sniffing attacks, cross-site scripting amplification, and protocol downgrade attacks. This maps to CWE-693 (Protection Mechanism Failure).
Severity: Medium | CWE: CWE-693 – Protection Mechanism Failure
Why This Matters
HTTP security headers are a browser-enforced second line of defence. If an attacker finds an XSS vector in your application, a strong Content Security Policy can prevent injected scripts from loading external payloads or exfiltrating data. Without X-Frame-Options or the frame-ancestors CSP directive, your login page can be embedded in an invisible iframe on an attacker’s site and used to steal clicks or credentials (clickjacking). Without Strict-Transport-Security, a network-level attacker can downgrade HTTPS connections to HTTP on the first visit. Without X-Content-Type-Options: nosniff, a browser may execute a file uploaded as an image if it detects executable content — a classic polyglot attack.
These are all issues that bypass server-side controls entirely; they are enforced — or not — by the browser. Helmet provides all of them in a single, well-maintained package with sensible defaults that you can tighten incrementally.
What Gets Flagged
The first express() application created in a file that imports express, when no file in the same package (the files under the nearest directory with a package.json) applies security headers:
const express = require('express');
// FLAGGED: no helmet() or security-header middleware anywhere in the package
const app = express();
app.listen(3000);
A helmet setup that is commented out (// app.use(helmet()), or inside /* ... */), or named only in a string (console.warn("enable helmet()")), does not count.
Not flagged:
app.use(helmet()),helmet.frameguard()and other helmet middlewares, in this file or another module of the package;lusca, or middleware that setsContent-Security-Policy,X-Frame-Options,Strict-Transport-SecurityorX-Content-Type-Optionsitself, by name (res.setHeader("X-Frame-Options", ...)) or as a key of a headers object (res.set({ "X-Frame-Options": "DENY" }));express.Router(),express.json()and otherexpress.*factories;- test, example, sample, demo and benchmark code.
In a monorepo each package is judged on its own: helmet in services/billing does not cover services/users.
Remediation
Install helmet:
npm install helmetApply it as the first middleware, before any route definitions, so the headers are set on every response:
// SAFE: helmet applied to all routes const express = require('express'); const helmet = require('helmet'); const app = express(); app.use(helmet()); // sets all default headers app.use(express.json()); app.get('/', (req, res) => res.send('Hello')); app.listen(3000);The default
helmet()call sets these headers:Content-Security-Policy— restricts script, style, and resource originsStrict-Transport-Security— enforces HTTPS for future visitsX-Frame-Options: SAMEORIGIN— prevents clickjackingX-Content-Type-Options: nosniff— prevents MIME sniffingReferrer-Policy: no-referrer— limits referrer leakageX-DNS-Prefetch-Control: off— prevents DNS prefetching data leakageX-Download-Options: noopen— IE-specific protectionX-Permitted-Cross-Domain-Policies: none— blocks Adobe Flash/Acrobat
Tighten the Content Security Policy for your application. The default CSP is strict but may break inline scripts or third-party resources you rely on. Configure it explicitly rather than disabling it:
// SAFE: customised CSP that permits your CDN and disables inline scripts app.use( helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", 'https://cdn.example.com'], styleSrc: ["'self'", "'unsafe-inline'"], imgSrc: ["'self'", 'data:', 'https:'], }, }, }) );Add
helmetto your ESLint config as a required import using a custom rule or theeslint-plugin-securitypackage, so any new Express file that omits it fails the linter immediately.