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 sets Content-Security-Policy, X-Frame-Options, Strict-Transport-Security or X-Content-Type-Options itself, 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 other express.* 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

  1. Install helmet:

    npm install helmet
    
  2. Apply 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 origins
    • Strict-Transport-Security — enforces HTTPS for future visits
    • X-Frame-Options: SAMEORIGIN — prevents clickjacking
    • X-Content-Type-Options: nosniff — prevents MIME sniffing
    • Referrer-Policy: no-referrer — limits referrer leakage
    • X-DNS-Prefetch-Control: off — prevents DNS prefetching data leakage
    • X-Download-Options: noopen — IE-specific protection
    • X-Permitted-Cross-Domain-Policies: none — blocks Adobe Flash/Acrobat
  3. 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:'],
          },
        },
      })
    );
    
  4. Add helmet to your ESLint config as a required import using a custom rule or the eslint-plugin-security package, so any new Express file that omits it fails the linter immediately.

References