---
id: "GHSA-q747-mqgr-4gh7"
page: "https://infrastack.ai/live/GHSA-q747-mqgr-4gh7"
kind: "vulnerability"
aliases: ["GHSA-q747-mqgr-4gh7"]
title: "better-auth: Phone numbers can be claimed before they are verified"
severity: "high"
cvss: 8.1
epss: null
exploited: false
status: "open"
category: "package"
products: [{"name": "better-auth", "fixed": ["1.7.16"], "affected": ">= 0.4.12-beta.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: ["better-auth", "cwe-287", "cwe-345", "cwe-706", "node", "package", "phone-auth", "typescript"]
not_affected_if: ["custom-verifyotp-for-shared-codes", "no-phone-plugin"]
published_at: "2026-11-03T21:37:45+00:00"
first_seen_at: "2026-11-09T22:53:20+00:00"
updated_at: "2026-11-09T22:54:15+00:00"
sources: [{"source": "ghsa-repo", "url": "https://github.com/better-auth/better-auth/security/advisories/GHSA-q747-mqgr-4gh7", "first_seen_at": "2026-11-09T22:53:20+00:00"}]
exploit_sources: []
---

# better-auth: Phone numbers can be claimed before they are verified

Link for people: https://infrastack.ai/live/GHSA-q747-mqgr-4gh7 (this .md is for agents)

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.

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

## Affected

- npm:better-auth affected >= 0.4.12-beta.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 version with `npm ls better-auth`; versions at or above `0.4.12-beta.7` and below `1.7.16` (or `1.8.0-beta.0` to below `1.8.0-beta.5`) are affected.
- Check whether the phone number plugin is enabled in your auth configuration.
- Check whether your `user` table has rows where `phoneNumber` is set but `phoneNumberVerified` is not true.

## Fix

- `better-auth` `1.7.16` on the 1.7 line
- `better-auth` `1.8.0-beta.5` on the beta line
- Clearing unverified numbers as shown in the advisory's workarounds is also described for 1.7.16.

## Timeline

- 2026-11-09T22:53:20+00:00 ghsa-repo https://github.com/better-auth/better-auth/security/advisories/GHSA-q747-mqgr-4gh7

## Documents

- [advisory] Maintainer's advisory: github.com https://github.com/better-auth/better-auth/security/advisories/GHSA-q747-mqgr-4gh7

## From the sources

## Am I 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.

## Summary

**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.

## Impact

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.

## Patches

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.

On 1.7.16, clear numbers saved before the upgrade as shown in Workarounds.

## Workarounds

Clear phone numbers that were never verified. For example, on PostgreSQL with the default schema names:

```sql
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.

## Technical details

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.

---

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