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 rule flags any source file that calls express() but does not reference the string helmet anywhere in the same file.
// FLAGGED: Express app with no helmet
const express = require('express');
const app = express();
app.use(express.json());
app.get('/', (req, res) => res.send('Hello'));
app.listen(3000);
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.