[PR #8067] [MERGED] fix(security): prevent OTP reuse via race condition #15969

Closed
opened 2026-04-13 10:19:53 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/better-auth/better-auth/pull/8067
Author: @Oluwatobi-Mustapha
Created: 2/19/2026
Status: Merged
Merged: 2/22/2026
Merged by: @Bekacru

Base: canaryHead: fix/h1-otp-race-condition


📝 Commits (10+)

  • 0fccff3 fix(security): prevent OTP race condition vulnerability (H1)
  • 8dacca0 Merge branch 'canary' into fix/h1-otp-race-condition
  • ba032d8 refactor: extract atomicVerifyOTP helper to eliminate duplication
  • 6f0bcf5 refactor: extract atomicVerifyOTP helper to eliminate duplication
  • e74239d fix: add missing GenericEndpointContext import
  • aeb1e8c fix: resolve lint errors
  • 7f0581c Merge branch 'canary' into fix/h1-otp-race-condition
  • fa2f3e4 test: add race condition protection tests for OTP reuse
  • 048e1fa Merge branch 'canary' into fix/h1-otp-race-condition
  • 0d94b44 fix: format tests and ensure users exist

📊 Changes

3 files changed (+164 additions, -97 deletions)

View changed files

📝 docs/components/ui/sparkles.tsx (+1 -2)
📝 packages/better-auth/src/plugins/email-otp/email-otp.test.ts (+90 -0)
📝 packages/better-auth/src/plugins/email-otp/routes.ts (+73 -95)

📄 Description

Description

Problem

Critical race condition in OTP verification allows attackers to reuse OTPs through concurrent requests, enabling account takeover.

Attack Vector:
Multiple concurrent requests can verify the same OTP before deletion, bypassing single-use constraint and attempt limits.

Affected Endpoints:

  • email-otp/verify-email
  • sign-in/email-otp
  • email-otp/reset-password

Solution

Implement atomic delete-before-verify pattern:

  1. Fetch verification token
  2. Delete immediately (atomic operation)
  3. Verify OTP
  4. Re-create with incremented attempts if invalid

Impact

  • Severity: HIGH
  • Exploitability: Medium (requires timing)
  • Scope: All OTP-based authentication flows

Testing

  • Verified single OTP cannot be used twice
  • Confirmed attempt counter still increments on failure
  • Tested concurrent request handling

Security Considerations

This fix eliminates the race window entirely. Only one concurrent request can successfully delete the token, preventing OTP reuse attacks.


Reviewer Focus Areas:

  • Atomicity of delete operation
  • Attempt counter preservation logic
  • Error handling for expired/invalid tokens

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/better-auth/better-auth/pull/8067 **Author:** [@Oluwatobi-Mustapha](https://github.com/Oluwatobi-Mustapha) **Created:** 2/19/2026 **Status:** ✅ Merged **Merged:** 2/22/2026 **Merged by:** [@Bekacru](https://github.com/Bekacru) **Base:** `canary` ← **Head:** `fix/h1-otp-race-condition` --- ### 📝 Commits (10+) - [`0fccff3`](https://github.com/better-auth/better-auth/commit/0fccff39ace6509c1d3993d2bdee0b73b29d41d3) fix(security): prevent OTP race condition vulnerability (H1) - [`8dacca0`](https://github.com/better-auth/better-auth/commit/8dacca0e685dcbc4936c876136f6a8d4118c4468) Merge branch 'canary' into fix/h1-otp-race-condition - [`ba032d8`](https://github.com/better-auth/better-auth/commit/ba032d8453d0d7331407fc8e0b0db10cdcb6276c) refactor: extract atomicVerifyOTP helper to eliminate duplication - [`6f0bcf5`](https://github.com/better-auth/better-auth/commit/6f0bcf56deba881e70fce253772b8cfe01bbc24f) refactor: extract atomicVerifyOTP helper to eliminate duplication - [`e74239d`](https://github.com/better-auth/better-auth/commit/e74239db72f914eecb09801e92b767ecfd2b6724) fix: add missing GenericEndpointContext import - [`aeb1e8c`](https://github.com/better-auth/better-auth/commit/aeb1e8c2921bfa1abdf978b931d3c604b6ab475c) fix: resolve lint errors - [`7f0581c`](https://github.com/better-auth/better-auth/commit/7f0581c784cd5d0b261aa47cc81b74273507c4a4) Merge branch 'canary' into fix/h1-otp-race-condition - [`fa2f3e4`](https://github.com/better-auth/better-auth/commit/fa2f3e4cc98fd347ad02a93757fd1658d0ae22ba) test: add race condition protection tests for OTP reuse - [`048e1fa`](https://github.com/better-auth/better-auth/commit/048e1fa1f827752328039cb0bf76defa2d5ec25c) Merge branch 'canary' into fix/h1-otp-race-condition - [`0d94b44`](https://github.com/better-auth/better-auth/commit/0d94b442214308642c4bdc6f50d53e9d0e05cb61) fix: format tests and ensure users exist ### 📊 Changes **3 files changed** (+164 additions, -97 deletions) <details> <summary>View changed files</summary> 📝 `docs/components/ui/sparkles.tsx` (+1 -2) 📝 `packages/better-auth/src/plugins/email-otp/email-otp.test.ts` (+90 -0) 📝 `packages/better-auth/src/plugins/email-otp/routes.ts` (+73 -95) </details> ### 📄 Description ## Description ### Problem Critical race condition in OTP verification allows attackers to reuse OTPs through concurrent requests, enabling account takeover. **Attack Vector:** Multiple concurrent requests can verify the same OTP before deletion, bypassing single-use constraint and attempt limits. **Affected Endpoints:** - `email-otp/verify-email` - `sign-in/email-otp` - `email-otp/reset-password` ### Solution Implement atomic delete-before-verify pattern: 1. Fetch verification token 2. **Delete immediately** (atomic operation) 3. Verify OTP 4. Re-create with incremented attempts if invalid ### Impact - **Severity:** HIGH - **Exploitability:** Medium (requires timing) - **Scope:** All OTP-based authentication flows ### Testing - [ ] Verified single OTP cannot be used twice - [ ] Confirmed attempt counter still increments on failure - [ ] Tested concurrent request handling ### Security Considerations This fix eliminates the race window entirely. Only one concurrent request can successfully delete the token, preventing OTP reuse attacks. --- **Reviewer Focus Areas:** - Atomicity of delete operation - Attempt counter preservation logic - Error handling for expired/invalid tokens --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-04-13 10:19:53 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#15969