# Imported security middleware is actually wired in

- **Pattern:** `ab-002614` (`project-snapshot.security.security-middleware-applied`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-23
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002614
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002614)

## Why it matters

When an AI coding assistant is prompted to "add security" or "rate-limit this API" it will reliably install `helmet`, `express-rate-limit`, `csurf`, `csrf-csrf`, or a CORS package, import the module at the top of a file, and then forget to actually register the middleware with the app. The package shows up in `package.json`, the import statement satisfies the grep-for-safety review instinct, and the code LOOKS defended — but without an `app.use(helmet())`, `app.use(limiter)`, `fastify.register(cors)`, or equivalent registration, none of the middleware runs on any request. Every response ships with no security headers, every endpoint accepts unbounded traffic, every state-changing POST is vulnerable to CSRF. OWASP A05 (Security Misconfiguration) is the exact mapping. This pattern is especially insidious because it passes visual code review — a human glance sees "imports helmet, imports rate-limit, looks fine" — and only deep inspection of the app-initialization code reveals that none of the middleware chain fired.

## Severity rationale

High because every unapplied middleware represents a missing control that the code pretends to have — attackers get XSS-prone responses, unlimited request rates, and unprotected state mutations while reviewers believe the app is hardened.

## Remediation

For every security middleware in `package.json`, register it in the app's initialization chain before route definitions:
  ```ts
  // src/server.ts
  import express from 'express';
  import helmet from 'helmet';
  import rateLimit from 'express-rate-limit';
  import { doubleCsrf } from 'csrf-csrf';

  const app = express();
  app.use(helmet());
  app.use(rateLimit({ windowMs: 60_000, max: 100 }));
  const { doubleCsrfProtection } = doubleCsrf({ getSecret: () => process.env.CSRF_SECRET! });
  app.use(doubleCsrfProtection);
  // ... routes below
  ```
  For Fastify use `fastify.register(...)`; for Hono use `app.use('*', ...)`. If the project is Next.js, helmet does not apply — configure headers in `next.config.ts` via the `headers()` export or in `middleware.ts`. Run `security-hardening` and `api-security` for deeper coverage including per-route rate limits, CSRF-token rotation policies, and CORS origin allowlists.

## Detection

- **ID:** `security-middleware-applied`
- **Severity:** `high`
- **What to look for:** Check `package.json` for: `helmet`, `express-rate-limit`, `csurf`, `csrf-csrf`, `next-csrf`, `cors` (as middleware, not just response headers), `express-session`, `passport`, `rate-limiter-flexible`, `@fastify/helmet`, `@fastify/rate-limit`, `@fastify/csrf-protection`, `hono/csrf`, `hono/secure-headers`. For each present, grep for imports. For each imported middleware, confirm registration via `.use(...)` (Express/Hono/Koa) or `.register(...)` (Fastify) somewhere reachable from the app entry (`src/server.ts`, `src/index.ts`, `src/app.ts`, `src/main.ts`, or a router-setup file).
- **Pass criteria:** Every middleware that is both in deps AND imported is also registered via the correct framework call.
- **Fail criteria:** Any middleware imported but not registered. Commented-out registration (`//` or `if (false)` / `if (NODE_ENV==='never')`) does NOT count. Registration that disables the protection — `rateLimit({ max: Infinity })`, `helmet({ contentSecurityPolicy: false, hsts: false, xssFilter: false, noSniff: false })` — does NOT count.
- **Skip (N/A) when:** No security-middleware packages in deps AND framework is Next.js / Astro / SvelteKit / Remix (where middleware is config-file-driven). Quote the dep list.
- **Before evaluating, quote:** `package.json` deps + each middleware import (file:line) + the entry-point file.
- **Report even on pass:** `"helmet: src/server.ts:3 imported, :12 registered via app.use(helmet())"`.
- **Detail on fail:** `"helmet imported in src/server.ts:2, no app.use(helmet()) call anywhere — responses ship with no headers. express-rate-limit imported in src/routes/auth.ts:4, no .use(limiter)"`.
- **Remediation:**
  ```ts
  // src/server.ts
  import express from 'express';
  import helmet from 'helmet';
  import rateLimit from 'express-rate-limit';
  const app = express();
  app.use(helmet());
  app.use(rateLimit({ windowMs: 60_000, max: 100 }));
  // ...routes below
  ```
  For Fastify: `fastify.register(...)`; Hono: `app.use('*', ...)`. Next.js: configure headers in `next.config.ts` via `headers()` or `middleware.ts`. If a middleware is intentionally disabled, delete the import.

## External references

- cwe CWE-1173
- cwe CWE-693
- owasp A05

Taxons: project-snapshot, access-control

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