BETTER-AUTH · GHSA-q747-mqgr-4gh7 · Package
FIX → 1.8.0-beta.5 +1
The better-auth phone number plugin saved phone numbers on accounts before the owner proved control of them, and phone sign-in then picked whichever account held the number. An attacker can attach a victim's number to an attacker-controlled account so the victim's later valid OTP sign-in yields a session for the attacker's account, and shared verification codes let an attacker cancel another user's pending code.
first public Nov 3, 2026 · 1 source
Could this affect your app?
Free first check from your website. Connect your repo for the full answer.
Applications that use the better-auth phone number plugin are exposed, since sign-up and sign-in flows can store a phone number before it is verified. The shared verification code problem also applies to apps using the default verifyOTP (from 1.2.6-beta.8), while apps with a custom verifyOTP function are not affected by that part. Apps that do not use the phone number plugin can skip this.
Not affected if custom-verifyotp-for-shared-codes, no-phone-plugin
Who cares better-auth, node, phone-auth, typescript
npm ls better-auth; versions at or above 0.4.12-beta.7 and below 1.7.16 (or to below ) are affected.You are affected if you use the phone number plugin from better-auth 0.4.12-beta.7 or later.
The shared code problem below affects 1.2.6-beta.8 and later, when you have not set a custom verifyOTP function.
Unverified numbers. Sign-up and other flows could save a phone number on an account before anyone proved they own it. Phone sign-in then picked whichever account held the number. A one-time code (OTP) proves who controls the phone, not who owns that account.
Shared codes. Phone verification codes were stored next to codes from other flows without a separate namespace. Wrong phone attempts could delete a pending code that another flow issued to a different user.
An attacker can save a victim's phone number on an account the attacker controls. When the victim later signs in with a valid OTP sent to their own phone, the victim gets a session for the attacker's account. Data the victim then enters may become available to the attacker. The attacker does not intercept or bypass the OTP, and does not get into the victim's own account.
With the shared code problem, anyone who can predict a pending code's record identifier can cancel it repeatedly. This blocks that user's sign-in or recovery attempt. It does not give access to the account.
Upgrade better-auth to 1.7.16, or to 1.8.0-beta.5 on the beta line.
1.7.16 stops new unverified numbers. Sign-up and sign-in requests that send a phoneNumber are rejected, and a number from a social profile is not stored, including on account linking. 1.8.0-beta.5 also requires proof of account ownership before a pending number is verified, which covers numbers saved before the upgrade.
Check it with your coding agent
It reads this page and tells you if your code is affected.
Are you affected?
Ask from here. InfraStack checks the affected versions against your stack and answers in your inbox.
InfraStack checks the affected versions against your stack and answers in Slack, with the evidence and the fix.
1.8.0-beta.01.8.0-beta.5user table has rows where phoneNumber is set but phoneNumberVerified is not true.Upgrade to 1.8.0-beta.5 +1
better-auth 1.7.16 on the 1.7 linebetter-auth 1.8.0-beta.5 on the beta lineOn 1.7.16, clear numbers saved before the upgrade as shown in Workarounds.
Clear phone numbers that were never verified. For example, on PostgreSQL with the default schema names:
UPDATE "user" SET "phoneNumber" = NULL
WHERE "phoneNumber" IS NOT NULL AND "phoneNumberVerified" IS NOT TRUE;
Users who had not finished verification need to add their number again.
Until you upgrade, stop public flows from saving phoneNumber before verification, for example by removing it from sign-up request bodies. A strict phoneNumberValidator reduces the shared code problem but does not fix it.
Phone OTP sign-in and verification looked up the account by phoneNumber without requiring phoneNumberVerified. 1.7.16 rejects a phoneNumber in sign-up and sign-in request bodies, drops an unverified number from createUser data and social profiles, and drops an unverified number written during a request outside the plugin's verification path and the admin update route. Verification codes now use a phone-specific identifier. With requireVerification, phone password reset now also needs a verified number.