phase: 82_security_headers
All completion criteria verified green — no defects found, nothing to fix. Final report: **Phase 82 (security headers) — final verification pass: all green** - Verified prior-run implementation: `app/core/security_headers.py` (pure-ASGI, header-only, exact A1 CSP), registration in `app/main.py` after `configure_caching` (outermost), unit/integration/E2E suites. - Deviation confirmed sound: `data:`-URI favicon (blocked by locked CSP) → static `frontend/assets/favicon.svg` in 5 templates + Containerfile `cp`; SVG element byte-identical to the old data-URI (verified programmatically); serves 200 with all three headers. - Curl check (server booted like e2e conftest, log: `/tmp/curl_security_headers_final.log`): `/`, `/api/health`, `/assets/styles.css`, `/nope` (404) → all three headers, CSP exactly `default-src 'self'; base-uri 'none'; frame-ancestors 'none'`. - `uv run pytest tests/unit/test_security_headers.py tests/integration/test_security_headers.py -v --no-cov` → 13 passed (incl. SSE byte-identity pin). - `uv run pytest tests/e2e/test_security_headers.py -v --no-cov` (isolated) → 2 passed (headers + zero CSP violations + painted page). - SSE tripwire `uv run pytest tests/e2e/test_chat_rag.py -v --no-cov` → 3 passed. - `uv run pytest --cov=app --cov-report=term-missing` → 1665 passed, app/ 99% (>90%); `uv run ruff check . && uv run pyright` → clean (0 errors). - `git diff --stat` limited to phase-82 files + the two documented deviations (favicon set, `tests/unit/__init__.py`); no `pyproject.toml`/`uv.lock`/JS diffs. - Commit + phase-dir move left to the harness per pipeline rules (not executed by me). Next pending phase: `83_chat_save_payload_limits`.
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
# Task 01 — The pure-ASGI header middleware + unit suite
|
||||
|
||||
**Phase:** `82_security_headers` · **Story:** n/a (security hardening — audit SEC-04)
|
||||
|
||||
## Objective
|
||||
`app/core/security_headers.py` exists exactly per the phase design block, and `tests/unit/test_security_headers.py` pins the header values, the 404 path, the SSE streaming passthrough (byte-identical body + headers present), and the non-http scope passthrough.
|
||||
|
||||
## Work
|
||||
1. `app/core/security_headers.py` (new) — implement per the phase overview's design block: the `CSP` constant (the exact A1 string), `SecurityHeadersMiddleware` as a pure-ASGI class wrapping `send` (headers written on `http.response.start` via `starlette.datastructures.MutableHeaders(scope=message)`), the `scope["type"] != "http"` fast path, and the module docstring content listed in the design block (audit basis, the No-CDN verification, the pure-ASGI/SSE rationale).
|
||||
2. `tests/unit/test_security_headers.py` (new):
|
||||
- a tiny ASGI `test_app` (a plain 200 `Response`-like dict sequence, or starlette's `Response` wrapped in an ASGI callable) + the middleware: assert all three headers with exact values;
|
||||
- a 404-shaped response (status 404) → same three headers;
|
||||
- a streaming response (an async generator yielding `data: a\n\n` then `data: b\n\n`, `media_type="text/event-stream"`) → consume the full body via the ASGI `receive`/`send` protocol (drive it with a small async client loop, or `httpx.AsyncClient(transport=ASGITransport(...))` if already used in the unit suite) → body is exactly the two frames in order AND the three headers are present (the SSE pin);
|
||||
- a `websocket` scope → the app is called, no crash, no header injection attempt.
|
||||
|
||||
## Testing & Quality
|
||||
- `uv run pytest tests/unit/test_security_headers.py -v` green.
|
||||
- Coverage: **>90%** on `app/core/security_headers.py` (every branch: http start, http other messages, non-http scope).
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] `uv run pytest tests/unit/test_security_headers.py -v` — all tests green, including the SSE byte-identity pin.
|
||||
- [ ] `uv run ruff check app/core/security_headers.py tests/unit/test_security_headers.py && uv run pyright` clean.
|
||||
- [ ] `git diff --stat` for this task: exactly the two new files.
|
||||
Reference in New Issue
Block a user