# Phase 03 — Security headers on every location (audit M-4) **Story:** n/a **Context:** `.agents/PLAN.md` §3 (serving + edge), `.agents/remediation_plan.md` findings M-4 (headers) and L-2 (`server_tokens`, included as a one-liner in the same file) ## Objective Fix the nginx `add_header` inheritance trap: the `location = /index.html` and the hashed-asset location each define their own cache `add_header` directives, so they **lose** the server-level security headers (CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy). Extract the four security headers into a snippet and `include` it in **every** location, so every response class carries the full header set. Also set `server_tokens off;` (L-2) in the same pass. ## Dependencies — (none) ## Tasks 1. `01_extract_header_snippet.md` — Create `nginx-security-headers.inc`, wire it into the Dockerfile, and `include` it in the server block and both header-defining locations (plus `server_tokens off;`). 2. `02_add_header_verification.md` — Add `scripts/check-headers.sh` that builds and runs the real image and asserts all four headers on `/`, `/index.html`, and a hashed asset. ## Testing & Quality - Unit/integration: the container-based header check (task 02) is the integration test. It must assert all four headers on each of the three response classes. - Coverage: **>90%** on new/modified config (all three location paths exercised by the check script). - The Playwright E2E suite is unaffected (its webServer is plain python, not nginx) and must stay green. ## Completion Criteria - [ ] `nginx-security-headers.inc` exists and holds the four `add_header … always;` lines - [ ] `nginx.conf` has **no** inline security `add_header` lines (only the `include`), and both header-defining locations `include` the snippet - [ ] `server_tokens off;` present in the `server` block - [ ] `scripts/check-headers.sh` runs the real image and prints `HEADER CHECK PASSED` (all four headers on `/`, `/index.html`, and a hashed asset) - [ ] `./build.sh && npm test` fully green - [ ] no behavior change in completed work (terminal, vim, achievements, menu) **Out of scope (separate, unphased):** L-1 HSTS — that is an **edge/Caddy** responsibility (the container serves plain HTTP on 8080). Verify at the edge manually: `curl -sI https://reeseapps.com/ | grep -i strict-transport`. Do **not** add HSTS inside the 8080 container.