--- phase: "07.4" slug: security-token-fingerprinting-token-binding-inserted status: verified threats_open: 0 asvs_level: 1 created: 2026-06-06 --- # Phase 07.4 — Security > Per-phase security contract: threat register, accepted risks, and audit trail. --- ## Trust Boundaries | Boundary | Description | Data Crossing | |----------|-------------|---------------| | client → API (login/refresh) | Client `User-Agent` and `Accept-Language` headers are untrusted; HMAC treats them as opaque bytes — no injection vector | Header strings → HMAC-SHA256 digest (hex) | | JWT payload → get_current_user | `fgp` claim extracted from decoded (ES256 signature-verified) JWT; attacker cannot forge without ES256 private key | fgp hex string (16 chars) | | fgp comparison | Fixed-length (16-char hex) strings compared with `hmac.compare_digest` | Constant-time boolean | | test harness → test DB | xfail stubs (Plan 01) create no DB state; no trust boundary crossed | None | --- ## Threat Register | Threat ID | Category | Component | Disposition | Mitigation | Status | |-----------|----------|-----------|-------------|------------|--------| | T-07.4-01 | Spoofing | Access token replay from different device | mitigate | `fgp` mismatch → HTTP 401 in `deps/auth.py:98-101`; attacker must match UA + Accept-Language from original client context | closed | | T-07.4-02 | Information Disclosure | Timing attack on fingerprint comparison | mitigate | `hmac.compare_digest` (constant-time) used at `deps/auth.py:98`; satisfies SEC-06 requirement | closed | | T-07.4-03 | Spoofing | Empty-string collision (CLI clients share one fingerprint) | accept | D-02 deliberate design decision — CLI tools with no UA headers bind to `fgp("","")` and are internally consistent; equivalent to pre-7.4 security level for those clients | closed | | T-07.4-04 | Tampering | fgp HTTPException swallowed by broad `except` | mitigate | fgp validation block placed **outside** try/except in `deps/auth.py:88-104` (pure computation, no I/O); HTTP 401 always propagates | closed | | T-07.4-SC | Tampering | Supply-chain via npm/pip/cargo installs | accept | No new third-party packages installed in either plan; only stdlib `hmac` and `hashlib` used | closed | *Status: open · closed* *Disposition: mitigate (implementation required) · accept (documented risk) · transfer (third-party)* --- ## Accepted Risks Log | Risk ID | Threat Ref | Rationale | Accepted By | Date | |---------|------------|-----------|-------------|------| | AR-07.4-01 | T-07.4-03 | CLI clients (no User-Agent or Accept-Language) all share `fgp("")` — they are internally consistent but one CLI session token cannot distinguish another CLI session. Design decision D-02 documents this as acceptable: CLI use is already authenticated via the same token family; the fingerprint adds value for browser clients where header variance is meaningful. | project owner (gsd workflow) | 2026-06-06 | | AR-07.4-02 | T-07.4-SC | Plans 01 and 02 introduce zero new dependencies; stdlib `hmac`/`hashlib` only. Supply-chain risk is identical to baseline. | project owner (gsd workflow) | 2026-06-06 | --- ## Implementation Evidence | File | Evidence | |------|----------| | `backend/services/auth.py:87-88` | `_compute_fgp(user_agent, accept_lang)` — HMAC-SHA256 producing 16-char hex digest | | `backend/services/auth.py:115` | `"fgp": _compute_fgp(user_agent, accept_lang)` embedded in access token payload | | `backend/api/auth.py:293, 372` | Login and refresh both pass `user_agent=request.headers.get("User-Agent", "")` | | `backend/deps/auth.py:88-104` | fgp validation block — **outside** try/except, `hmac.compare_digest` comparison, `detail="Token fingerprint mismatch"` on mismatch | | `backend/tests/test_auth_fgp.py` | 4 FGP tests (FGP-01..04) — all passing (promoted from xfail stubs in Plan 01) | --- ## Security Audit Trail | Audit Date | Threats Total | Closed | Open | Run By | |------------|---------------|--------|------|--------| | 2026-06-06 | 5 | 5 | 0 | gsd-secure-phase (automated — short-circuit: register_authored_at_plan_time=true, threats_open=0) | --- ## Sign-Off - [x] All threats have a disposition (mitigate / accept / transfer) - [x] Accepted risks documented in Accepted Risks Log - [x] `threats_open: 0` confirmed - [x] `status: verified` set in frontmatter **Approval:** verified 2026-06-06