# HSTS header is present with reasonable max-age

- **Pattern:** `ab-000002` (`security-headers.transport.hsts-enabled`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-17
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000002
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000002)

## Why it matters

HTTPS enforcement prevents plaintext connections, but HSTS goes further: it instructs the browser to refuse HTTP for your domain entirely for up to a year after the first visit, eliminating SSL-stripping attacks that can silently downgrade an HTTPS connection before the browser even sees it. Without a `max-age` of at least 31536000, an attacker who intercepts the first request can strip the redirect and operate in plaintext for every subsequent session. RFC 6797 defines the spec; OWASP A02 identifies the absence of HSTS as a cryptographic failure that enables credential theft.

## Severity rationale

High because a missing or short-lived HSTS header leaves the window open for SSL-stripping attacks that HTTPS redirects alone cannot prevent.

## Remediation

Configure the `Strict-Transport-Security` header with a full-year `max-age` in your framework config or middleware. A short `max-age` is nearly as dangerous as no HSTS — browsers forget the policy quickly.

```js
// next.config.js
headers: [{
  key: 'Strict-Transport-Security',
  value: 'max-age=31536000; includeSubDomains'
}]
```

Verify the header is live with `curl -I https://yoursite.com | grep Strict-Transport` after deployment. Do not configure this only in development environments — production config is what matters.

## Detection

- **ID:** `hsts-enabled`
- **Severity:** `high`
- **What to look for:** Count every location where Strict-Transport-Security could be configured: framework config (`next.config.*` headers), middleware, deployment config (`vercel.json`, `netlify.toml`), and security middleware (helmet). Verify the `max-age` is at least 31536000 (1 year).
- **Pass criteria:** HSTS header is configured with `max-age` of at least 31536000 seconds (1 year). `includeSubDomains` is recommended but not required for pass.
- **Fail criteria:** No HSTS configuration found, or `max-age` is less than 31536000.
- **Skip (N/A) when:** Never — every HTTPS site should have HSTS.
- **Do NOT pass when:** HSTS is only configured in development/local config (e.g., `next.config.dev.js` or behind `if (process.env.NODE_ENV === 'development')`) but not in production config. Also do NOT pass when `max-age` is 0, which effectively disables HSTS.
- **Report even on pass:** Report the configured `max-age` value and whether `includeSubDomains` is present: "HSTS configured with max-age=31536000, includeSubDomains present."
- **Note:** This check fails even if `https-enforced` passes via platform default — HSTS must be explicitly configured with the correct max-age. `https-enforced` verifies transport-level HTTPS; this check verifies browser-side enforcement via the HSTS header.
- **Cross-reference:** For HTTPS enforcement in deployment config, the Performance & Load audit covers server configuration and TLS setup.
- **Detail on fail:** `"No Strict-Transport-Security header configured in next.config.js, middleware, or deployment config"` or `"HSTS max-age is 3600 (1 hour) — should be at least 31536000 (1 year)"`
- **Remediation:** HSTS tells browsers to always use HTTPS for your site, even if the user types `http://`. Without it, the first visit to your site could be intercepted. Add the header:

  ```js
  // next.config.js
  headers: [{
    key: 'Strict-Transport-Security',
    value: 'max-age=31536000; includeSubDomains'
  }]
  ```

  After deploying, verify with: `curl -I https://yoursite.com` and confirm the header appears.

## External references

- cwe CWE-319
- cwe CWE-523
- owasp A02
- external RFC-6797 — https://datatracker.ietf.org/doc/html/rfc6797

Taxons: cryptography-and-secrets

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