# Core security response headers are configured

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

## Why it matters

Three response headers carry most of the browser-enforced defenses that make modern web apps resistant to common attacks: `Strict-Transport-Security` (HSTS) prevents TLS-strip downgrades, `Content-Security-Policy` (CSP) caps the blast radius of any XSS bug, and `Referrer-Policy` stops URLs containing tokens or session identifiers from leaking to third parties in referrer strings. AuditBuffet's own production telemetry across 37 first-run `security-headers` audits shows HSTS missing in 76% of scans, CSP missing in 65%, and Referrer-Policy missing in 54% — the majority of AI-generated sites ship with all three absent. The attacker's path is concrete: without HSTS, a user on a hostile Wi-Fi network can be downgraded to HTTP and have their session cookie stolen; without CSP, a single reflected-XSS bug becomes a full account-takeover via arbitrary script execution; without Referrer-Policy, password-reset links, magic-login tokens, and OAuth callbacks leak into analytics pipelines and third-party ad networks. GDPR Article 32 (appropriate technical measures) treats referrer leakage of personal identifiers as a reportable data incident.

## Severity rationale

High because each missing header leaves a distinct browser-level defense un-enforced — the composite exposure spans network-layer downgrade attacks, XSS amplification, and session-token leakage through referrers simultaneously.

## Remediation

Configure all three headers in `next.config.ts` (Next.js), `middleware.ts`, or `vercel.json`:
  ```ts
  // next.config.ts
  export default {
    async headers() {
      return [{
        source: '/:path*',
        headers: [
          { key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
          { key: 'Content-Security-Policy', value: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'" },
          { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
        ],
      }];
    },
  };
  ```
  Start with a permissive CSP and tighten over time — even a loose CSP is better than none, and having the header present means the browser enforces directive limits where you have set them. For a full header-hardening pass including `Permissions-Policy`, `X-Content-Type-Options`, `Cross-Origin-Opener-Policy`, nonce-based CSP, and SRI coverage, run the `security-headers` and `security-headers-ii` Pro audits.

## Detection

- **ID:** `security-headers-present`
- **Severity:** `high`
- **What to look for:** Search for response-header config in: (a) `next.config.{ts,js,mjs}` — `headers()` async export returning `[{ source, headers: [{ key, value }, ...] }]`; (b) `middleware.ts` with `NextResponse` + `.headers.set(...)`; (c) `vercel.json` top-level `headers` array; (d) Astro `astro.config.mjs`, SvelteKit `hooks.server.ts`, Remix `entry.server.ts`; (e) Express/Fastify `app.use(helmet())` or `res.setHeader(...)`. From the union, check three headers: (1) `Strict-Transport-Security` with `max-age` ≥ 15552000 (6 months); (2) `Content-Security-Policy` with any non-empty value; (3) `Referrer-Policy` set to anything other than `unsafe-url`.
- **Pass criteria:** All three headers present and valid.
- **Fail criteria:** Any missing; HSTS `max-age` below 15552000; Referrer-Policy = `unsafe-url`. Headers defined in a config the framework doesn't use (e.g. `vercel.json` in a non-Vercel deploy, `middleware.ts` whose matcher excludes live routes) do NOT count. CSP set to `default-src *; script-src * 'unsafe-inline' 'unsafe-eval'` is functionally absent.
- **Skip (N/A) when:** Not a web app — CLI, npm library, native-mobile, backend-only API whose responses never reach a browser. Quote `package.json` `main`/`bin` + project structure.
- **Before evaluating, quote:** Each header-configuration file (at least the `headers()` export or `headers` array) + middleware matcher if present.
- **Report even on pass:** Exact file path + exact values: `"All 3 in next.config.ts: HSTS max-age=63072000 includeSubDomains preload, CSP 'default-src self...', Referrer-Policy strict-origin-when-cross-origin"`.
- **Detail on fail:** `"HSTS missing in next.config.ts, middleware.ts, vercel.json. CSP present but Referrer-Policy missing — add to headers() at next.config.ts:12"`.
- **Remediation:**
  ```ts
  // next.config.ts
  export default {
    async headers() {
      return [{ source: '/:path*', headers: [
        { key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
        { key: 'Content-Security-Policy', value: "default-src 'self'; script-src 'self' 'unsafe-inline'" },
        { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
      ]}];
    },
  };
  ```
  Any non-empty CSP is better than none — tighten directives incrementally. For full coverage (Permissions-Policy, X-Content-Type-Options, COOP, nonce CSP, SRI), run `security-headers` / `security-headers-ii`.

## External references

- cwe CWE-693
- cwe CWE-319
- owasp A05

Taxons: project-snapshot, access-control

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