WordPress / WordPress/two-factor
Email resend bypasses retry delay in 2FA challenge flow
@dknauss is already working on this.
Since Mar 24, 2026.
- Dominant language
- PHP
- Stars
- 825
- Forks
- 187
- Avg merge
- 2d 11h
- Merged PRs (30d)
- 17
Description
Summary
The email provider handles resend in pre_process_authentication(), but Two_Factor_Core::process_provider() applies is_user_rate_limited() only afterward. As a result, resend requests are not subject to the same retry-delay gate as verification attempts.
Why this matters
For email-based 2FA, a user who has already hit the plugin's retry delay after invalid verification attempts can still trigger Resend Code. (The button is presented to them.) Each resend generates a fresh token via generate_and_email_token(), replacing the one that an attacker may have been trying to guess. This effectively resets the brute-force window while the rate limit is still active — the delay blocks verification attempts but not the token rotation that undermines it.
This is an inference from current source, not a confirmed exploit. It looks like an auth-flow / abuse-hardening gap rather than a full auth bypass.
It's also highly annoying if you get trapped by this! You're told you're blocked but prompted to send yourself an new code, which you can do — but the (valid) code won't get you logged in.
Verified code path
Two_Factor_Core::process_provider()calls$provider->pre_process_authentication( $user )before it checksis_user_rate_limited( $user ).Two_Factor_Email::pre_process_authentication()resends whenevertwo-factor-email-code-resendis present.- The rate-limit check runs only later in
process_provider(), so it does not gate the resend branch.
Current behavior from source
- Resend is handled as provider pre-processing.
- Rate limiting is applied only to the subsequent verification path.
- I did not find a dedicated resend throttle in the email provider path.
Potential impact
- Repeated resend requests can generate additional email traffic even when verification attempts are temporarily delayed.
- Integrators embedding the provider UI inside another challenge flow may see resend behavior that does not match the plugin's normal retry messaging.
Suggested fix
Either:
- Move the rate-limit check ahead of provider pre-processing for resend-capable flows, or
- add a dedicated resend throttle in
Two_Factor_Email::pre_process_authentication().
Related context
- This seems adjacent to the broader throttling discussion in #477, but I could not find a report specifically about the email resend path surviving the existing retry-delay check.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.