---
id: "GHSA-2f9c-7xj7-gh55"
page: "https://infrastack.ai/live/GHSA-2f9c-7xj7-gh55"
kind: "vulnerability"
aliases: ["GHSA-2f9c-7xj7-gh55"]
title: "better-auth: Email OTP attempt limit can be raced and codes are loosely bound to addresses"
severity: "medium"
cvss: 4.8
epss: null
exploited: false
status: "open"
category: "package"
products: [{"name": "better-auth", "fixed": ["1.7.16"], "affected": ">= 1.3.7, < 1.7.16", "ecosystem": "npm"}, {"name": "better-auth", "fixed": ["1.8.0-beta.5"], "affected": ">= 1.8.0-beta.0, < 1.8.0-beta.5", "ecosystem": "npm"}]
ecosystems: ["npm"]
tags: ["authentication", "better-auth", "cwe-307", "cwe-362", "cwe-639", "email-otp", "node", "package"]
not_affected_if: ["changeemail-disabled", "no-emailotp-plugin", "non-accent-insensitive-db"]
published_at: "2026-11-03T21:38:55+00:00"
first_seen_at: "2026-11-09T22:53:20+00:00"
updated_at: "2026-11-09T22:57:06+00:00"
sources: [{"source": "ghsa-repo", "url": "https://github.com/better-auth/better-auth/security/advisories/GHSA-2f9c-7xj7-gh55", "first_seen_at": "2026-11-09T22:53:20+00:00"}]
exploit_sources: []
---

# better-auth: Email OTP attempt limit can be raced and codes are loosely bound to addresses

Link for people: https://infrastack.ai/live/GHSA-2f9c-7xj7-gh55 (this .md is for agents)

The emailOTP() plugin in better-auth can be made to count concurrent guesses as one attempt, letting an attacker exceed allowedAttempts and sign in or reset a password. Email change codes could also be used on a different account, and on databases that ignore accents, a reset code could go to a different address.

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- npm:better-auth affected >= 1.3.7, < 1.7.16 fixed 1.7.16
- npm:better-auth affected >= 1.8.0-beta.0, < 1.8.0-beta.5 fixed 1.8.0-beta.5

## How to check

- Check the installed `better-auth` version with `npm ls better-auth`.
- Look for `emailOTP(` in your auth config and check whether `changeEmail.enabled` is `true`.
- Check whether your database collation ignores accents (common on MySQL).

## Fix

- `better-auth` `1.7.16` on the 1.7 line
- `better-auth` `1.8.0-beta.5` on the 1.8 beta line
- Email change codes sent before the upgrade must be requested again.

## Timeline

- 2026-11-09T22:53:20+00:00 ghsa-repo https://github.com/better-auth/better-auth/security/advisories/GHSA-2f9c-7xj7-gh55

## Documents

- [advisory] Maintainer's advisory: github.com https://github.com/better-auth/better-auth/security/advisories/GHSA-2f9c-7xj7-gh55

## From the sources

## Am I affected?

You are affected if you use the `emailOTP()` plugin from `better-auth` 1.3.7 or later. Each problem below adds its own condition:

- **Attempt limit:** any email OTP flow.
- **Email change codes:** `changeEmail.enabled: true` in the plugin, from 1.5.0.
- **Accented addresses:** a database collation that ignores accents, common on MySQL.

## Summary

**Attempt limit.** Guesses that arrive at the same time could be counted as one attempt, so an attacker could make more guesses than `allowedAttempts` allows.

**Email change codes.** Codes for changing an email address were stored under a key built from both addresses. Two different address pairs could produce the same key, so a code requested from one account could be used from another.

**Accented addresses.** On databases that ignore accents, a lookup for an address could return an account with a different stored address. Password reset then sent the code to the requested address, not the account's own.

## Impact

With the attempt limit problem, an unauthenticated attacker can make extra guesses against an active OTP and, if a guess hits, sign in or reset the password.

With the email change problem, a signed-in user can mark an unused address on their own account as verified. The real owner of that address may later be signed in to the attacker's account.

With the accented address problem, an attacker who receives mail at an address that differs from the victim's only by accents can reset the victim's password, with no action from the victim.

## Patches

Upgrade `better-auth` to 1.7.16, or to 1.8.0-beta.5 on the beta line. Email change codes sent before the upgrade must be requested again.

## Workarounds

Turn off email change in the email OTP plugin, which is the default, and use the core email change flow instead. Lowering `allowedAttempts` and the OTP lifetime reduces the attempt limit problem but does not fix it.

## Technical details

The attempt counter was read and written in separate steps, so concurrent checks could each see the same count. Every guess now counts against the limit, and a correct check still leaves the OTP usable for the follow-up call. Email change codes are now bound to the requesting account and the new address. Looking up a user by email now returns an account only when its stored address matches the requested one, ignoring case, and the reset routes send the code to the address on the account.

## Credit

The accented address problem was discovered by Anthropic's OSS Scanner, as vulnerability ANT-2026-Y7X5RTID.

---

_Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai_
