C-01: Initialize config=None before conditional in auth/status to
prevent NameError on fresh instance (setup_required=True path)
C-02: Use generic "Authentication failed" on passkey lockout trigger
instead of leaking lockout state (consistent with F-02 remediation)
W-01: Add nginx rate limit for /api/auth/passkeys/passwordless
endpoints (enable accepts password — brute force protection)
W-02: Call record_successful_login in passkey unlock path to reset
failed_login_count (prevents unexpected lockout accumulation)
W-05: Auto-clear must_change_password on passkey login — user can't
provide old password in forced-change form after passkey auth
S-01: Pin webauthn to >=2.1.0,<3 (prevent major version breakage)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
F-01 (passkeys.py): Add constant-time DB no-op on login/begin when username not
found. Without it the absent credential-fetch query makes the "no user" path
measurably faster, leaking username existence via timing.
F-02 (session.py, auth.py, passkeys.py, totp.py): Change check_account_lockout
from HTTP 423 to 401 — status-code analysis can no longer distinguish a locked
account from an invalid credential. record_failed_login now returns remaining
attempt count; callers use it for progressive UX warnings (<=3 attempts left,
and on the locking attempt) without changing the 401 status code visible to
attackers. Session-lock 423 path in get_current_user is unaffected.
F-03 (nginx.conf): Replace set_real_ip_from 0.0.0.0/0 with RFC 1918 ranges
(172.16.0.0/12, 10.0.0.0/8) to prevent external clients from spoofing
X-Forwarded-For to bypass rate limiting.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move `request: Request` (no default) before parameters with defaults
to fix 'parameter without default follows parameter with default'.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>