All gates verified. The phase is complete — everything was already implemented by prior task runs; this pass verified each completion criterion end-to-end and found no defects. **Phase 81 final verification report** (tasks 01–03 all present in `complete/`; no code changes needed this pass) - Verified: `app/core/rate_limit.py` (stdlib sliding window, fail-open) + both login routes' 429 pre-check/record/reset wiring + all unit/integration pins present (11th-429, blocked-success-still-429, clean-counter reset, shared counter, autouse `clean_rate_limit` fixture documented). - Live check (task 03): dev server + 11 rapid wrong logins → `401 ×10, 429`; 429 body carries `retry-after: 900` + generic detail; server restarted (per-process counter cleared by design). - `uv run pytest tests/unit/test_rate_limit.py -v --no-cov` → 10 passed - `uv run pytest --cov=app --cov-report=term-missing` → 1652 passed, **TOTAL 99%** (>90%; rate_limit.py 100%, auth.py 100%) - `uv run pytest tests/e2e/test_smoke.py -v --no-cov` (isolation) → 3 passed - `uv run ruff check . && uv run pyright` → All checks passed / 0 errors, 0 warnings - Completion criteria: all met, except commit + phase-dir move — per harness rules I left all changes uncommitted in the working tree (harness commits atomically and moves the phase). - Diff scope: exactly `app/core/rate_limit.py`, `app/api/auth.py`, `tests/unit/test_rate_limit.py`, `tests/integration/test_auth_api.py` + phase files; `pyproject.toml` / `uv.lock` / `frontend/` untouched. - Deviations: none in code; commit/move deferred to harness as instructed. - Next pending phase: `82_security_headers`.
2.2 KiB
2.2 KiB
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
app/core/security_headers.py(new) — implement per the phase overview's design block: theCSPconstant (the exact A1 string),SecurityHeadersMiddlewareas a pure-ASGI class wrappingsend(headers written onhttp.response.startviastarlette.datastructures.MutableHeaders(scope=message)), thescope["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).tests/unit/test_security_headers.py(new):- a tiny ASGI
test_app(a plain 200Response-like dict sequence, or starlette'sResponsewrapped 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\nthendata: b\n\n,media_type="text/event-stream") → consume the full body via the ASGIreceive/sendprotocol (drive it with a small async client loop, orhttpx.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
websocketscope → the app is called, no crash, no header injection attempt.
- a tiny ASGI
Testing & Quality
uv run pytest tests/unit/test_security_headers.py -vgreen.- 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 pyrightclean.git diff --statfor this task: exactly the two new files.