You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
sec(auth): TOTP replay window and no lockout on MFA lifecycle failures (follow-up to #94) #442
Two residual MFA hardening items from #94 that the rate-limit fix does not cover:
TOTP replay (audit finding A03-016).verifyTOTP (internal/auth/service_mfa.go) accepts counters -1..+1 with no state, and the users row stores no last-accepted counter. An observed code stays valid for up to 90 seconds and can be spent again by Login, MFADisable or recovery-code regeneration. RFC 6238 section 5.2 requires rejecting a previously used code. Fix: persist the last accepted counter and reject anything at or below it.
Account lockout on MFA failures. The MFA service methods never call recordFailedLogin, so MaxFailedLoginAttempts / AccountLockoutDuration do not apply to the MFA lifecycle endpoints. The new mfa rate-limit bucket (10 per 15 min per IP and per user) bounds guessing, but it is in-memory per instance on multi-instance deploys (sec(server): "not Lambda" read as "single instance" pins the in-memory rate limiter on multi-replica platforms #109), so a durable lockout would close that gap.
Adding from the review of #443: login's TOTP/recovery-code leg is limited per IP only (rate_limiter.go ~27, handler_auth.go ~24), so rotating IPs bypasses it (a valid password is still required). Add a per-user key there. Also, the per-IP 'mfa' bucket in requireMFASession (middleware.go ~578) runs before requireSession, so unauthenticated requests use it up and users behind one NAT share 10 per 15 min; consider checking the session first.
Activity
cristim commentedon Sep 28, 2026
Adding from the review of #443: login's TOTP/recovery-code leg is limited per IP only (rate_limiter.go ~27, handler_auth.go ~24), so rotating IPs bypasses it (a valid password is still required). Add a per-user key there. Also, the per-IP 'mfa' bucket in requireMFASession (middleware.go ~578) runs before requireSession, so unauthenticated requests use it up and users behind one NAT share 10 per 15 min; consider checking the session first.
IAmJSD commentedon Oct 16, 2026
Claiming: working on this