Skip to main content
interlace
Plugin: express-securityRules

no-disabled-helmet-protections

This rule detects helmet options that switch a shipped security-header default off, leaving a mounted helmet with the exposure of no helmet at all

Disallow disabling helmet security-header defaults: contentSecurityPolicy, frameguard/xFrameOptions, noSniff/xContentTypeOptions, referrerPolicy, hidePoweredBy/xPoweredBy, crossOriginResourcePolicy, crossOriginOpenerPolicy

Severity: 🔴 High CWE: CWE-693

Rule Details

require-helmet proves the middleware is mounted. It cannot see that the mount turned the protections off:

app.use(helmet({ contentSecurityPolicy: false, frameguard: false }));

That app ships no Content-Security-Policy and no X-Frame-Options — identical headers to an app with no helmet — while every reviewer scanning for app.use(helmet( reads it as protected. The false is usually a temporary unblock (a third-party widget, an iframe embed) that never gets revisited.

The rule fires on helmet({ <protection>: false }) for the protections below, in both the helmet ≤6 and helmet 7+ spellings:

Option (helmet ≤6 / 7+)Header no longer sent
contentSecurityPolicyContent-Security-Policy
frameguard / xFrameOptionsX-Frame-Options
noSniff / xContentTypeOptionsX-Content-Type-Options
referrerPolicyReferrer-Policy
hidePoweredBy / xPoweredByX-Powered-By removal
crossOriginResourcePolicyCross-Origin-Resource-Policy
crossOriginOpenerPolicyCross-Origin-Opener-Policy

hsts / strictTransportSecurity belong to require-strict-transport-security, and CSP directive contents to no-unsafe-csp-directives — no finding is reported twice.

Examples

❌ Incorrect

// CSP off — an injected <script> executes normally
app.use(helmet({ contentSecurityPolicy: false }));

// Clickjacking guard off (helmet ≤6 spelling)
app.use(helmet({ frameguard: false, hsts: { maxAge: 31536000 } }));

// MIME-sniffing guard off (helmet 7+ spelling)
app.use(helmet({ xContentTypeOptions: false }));

// Referrer-Policy off — full URLs leak to third parties
app.use(helmet({ referrerPolicy: false }));

// Framework fingerprint kept
app.use(helmet({ hidePoweredBy: false }));

✅ Correct

// Keep the defaults
app.use(helmet());

// Customise a protection instead of disabling it
app.use(
  helmet({
    contentSecurityPolicy: { directives: { defaultSrc: ["'self'"] } },
    referrerPolicy: { policy: 'no-referrer' },
  }),
);

// Protections that are not security headers are not policed
app.use(helmet({ dnsPrefetchControl: false, ieNoOpen: false }));

Suggestions

The rule offers one editor suggestion (no auto-fix): remove the <option>: false entry so the helmet default applies again. Whichever comma keeps the object literal valid is removed with it.

Options

OptionTypeDefaultDescription
allowDisabledstring[][]Helmet option names that may be disabled without a report (e.g. a CSP set at the CDN edge)
{
  "rules": {
    "express-security/no-disabled-helmet-protections": [
      "error",
      { "allowDisabled": ["contentSecurityPolicy"] }
    ]
  }
}

When Not To Use It

If the security headers are set by a reverse proxy or CDN in front of every environment (and that config is itself reviewed), list the options in allowDisabled rather than disabling the rule wholesale.

Known False Negatives

The following patterns are not detected due to static analysis limitations:

Config Held In A Variable

Why: No data-flow analysis — only an object literal passed directly to helmet() is inspected.

// ❌ NOT DETECTED
const helmetConfig = { contentSecurityPolicy: false };
app.use(helmet(helmetConfig));

Mitigation: Inline the config, or lint the config module with a project-specific rule.

Non-Literal Disable Value

Why: The value must be the false literal.

// ❌ NOT DETECTED
app.use(helmet({ noSniff: process.env.NODE_ENV !== 'production' }));

Mitigation: Prefer a literal; environment-conditional protections are a production incident waiting for a bad deploy.

Computed Option Keys

Why: { [key]: false } has no statically known option name.

// ❌ NOT DETECTED
app.use(helmet({ [flagName]: false }));

Further Reading

Did this rule catch something? Star the repo to get new CWE coverage as we ship it — or follow the AI-code-security benchmarks behind these rules.