# Helmet security middleware imported is actually applied

- **Pattern:** `ab-000279` (`ai-slop-security-theater.unapplied-middleware.helmet-imported-and-applied`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000279
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000279)

## Why it matters

Importing `helmet` and never calling `app.use(helmet())` means none of its 15 default security headers — `X-Content-Type-Options`, `X-Frame-Options`, `Strict-Transport-Security`, `X-XSS-Protection`, `Referrer-Policy`, and others — are actually set on any response. OWASP A05 (Security Misconfiguration) is the exact mapping. Without these headers, browsers don't enforce clickjacking protection, MIME-type sniffing defenses, or HSTS. An attacker who discovers the headers are absent can exploit XSS more easily or frame your app in an iframe for credential harvesting.

## Severity rationale

High because the absence of security headers removes browser-enforced defenses against XSS, clickjacking, and MIME sniffing on every HTTP response the application returns.

## Remediation

Mount helmet as middleware in your Express or Fastify entry point, before route definitions. The call must be in the application chain reachable from `index.ts` or `server.ts`.

```ts
// src/server.ts
import express from 'express'
import helmet from 'helmet'

const app = express()
app.use(helmet())  // applies all 15 default headers
app.get('/health', (req, res) => res.json({ ok: true }))
```

If you need to disable specific directives (e.g., `contentSecurityPolicy: false` for development), be explicit — don't disable everything. For Next.js, use `next.config.js` `headers()` instead; helmet doesn't apply to that framework.

## Detection

- **ID:** `helmet-imported-and-applied`
- **Severity:** `high`
- **What to look for:** When `helmet` is in `package.json` `dependencies`, walk source files and count all `import helmet` or `const helmet = require('helmet')` — at least 1 import is required for this check to apply. Before evaluating, extract and quote the import statement. For each file that imports helmet, count whether the same file (or a file in the import chain reachable from the app entry point: `index.ts`, `server.ts`, `app.ts`, `main.ts`) contains a call matching `app.use(helmet(`, `app.use(helmet())`, `fastify.register(helmet)`, `server.use(helmet`. The application call must be present.
- **Pass criteria:** Helmet is imported in at least 1 source file AND a corresponding `app.use(helmet(` (or framework-specific equivalent like `fastify.register(helmet)`) call exists somewhere in the application chain reachable from the entry point.
- **Report even on pass:** "Helmet imported in [file] AND applied in [file]."
- **Fail criteria:** Helmet is imported but no `.use(helmet(` call exists anywhere in source.
- **Do NOT pass when:** Helmet is imported and the `.use(helmet({ contentSecurityPolicy: false, hsts: false, crossOriginEmbedderPolicy: false }))` configuration disables every protection — that's still "applied" but worth noting in the detail.
- **Skip (N/A) when:** `helmet` is not in `package.json` dependencies, OR framework is Next.js / Astro / SvelteKit / Remix (helmet doesn't apply — those frameworks use config-file headers instead).
- **Cross-reference:** For framework-native security header configuration, the Security Headers & Basics audit (`security-headers`) covers Next.js / Astro / SvelteKit patterns.
- **Detail on fail:** `"helmet imported in src/server.ts but no app.use(helmet()) call found anywhere in source. Security headers not actually applied."`
- **Remediation:** Importing helmet does nothing on its own — it's middleware that must be wired into the Express app. Add the `.use()` call:

  ```ts
  // Bad: imported but never invoked
  import helmet from 'helmet'
  const app = express()
  app.get('/', handler)  // helmet never applied

  // Good: applied as middleware
  import helmet from 'helmet'
  const app = express()
  app.use(helmet())  // applies all default headers
  app.get('/', handler)
  ```

## External references

- cwe CWE-20
- cwe CWE-1173
- owasp A05

Taxons: placeholder-hygiene, injection-and-input-trust

HTML version: https://auditbuffet.com/patterns/ab-000279
