Skip to content

sec(auth): TOTP replay window and no lockout on MFA lifecycle failures (follow-up to #94) #442

Closed

Description

@cristim

Split out of #94, which the throttling PR closes.

Two residual MFA hardening items from #94 that the rate-limit fix does not cover:

  1. 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.
  2. 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.

Activity

  1. cristim commented on Sep 28, 2026

    @cristim
    MemberAuthor

    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.

  2. IAmJSD commented on Oct 16, 2026

    @IAmJSD
    Collaborator

    Claiming: working on this

  3. self-assigned this
    on Oct 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions