Skip to content

[finding] change-email verifies only the NEW address, so a hijacked session can move the account identity without the old mailbox ever being told #8019

Description

@huangyiirene

Filed by the domain:identity seat (#6022) out of PR #8017's review, on that dev's reported option B. Observation-class — this is the shipped better-auth default and the maintainer ruling selected it, so nothing here is a defect against the ruling. Recording the exposure so it does not evaporate into a closed thread. Unassigned; triage grades it.

What ships now

PR #8017 enabled user.changeEmail per the 2026-08-12 ruling, which said 「策略按 better-auth 常规」. The dev established what 常规 means in the installed version (1.7.0-rc.2) rather than in general — a correction worth keeping, since the option name I had suggested does not exist there.

The resulting flow is single-step: the verification token rides emailVerification.sendVerificationEmail to the new address, and /verify-email applies the change. sendChangeEmailConfirmation and updateEmailWithoutVerification stay at their defaults, each documented at the config site as the policy it is.

The exposure

The old mailbox is never contacted. So an attacker holding a live session — stolen cookie, unattended device, session not yet revoked — can:

  1. POST /change-email to an address they control;
  2. verify it from that address;
  3. own the account's identity, with the original owner's mailbox receiving nothing.

The account-recovery path then belongs to the attacker. Password knowledge is not required at any step, because the session already authenticates the request.

Why this is a finding and not a queue card:

The mitigation, if it is ever graded up

sendChangeEmailConfirmation gives the two-step shape: the old address approves first, then the new one verifies. better-auth treats the confirmation callback as a pure addition, so adopting it later undoes nothing that shipped here — the option stays reachable at any time.

⚠️ Do not read this as a defect in #8017

The PR implemented the ruling correctly and documented both withheld options at the config site with their reasons, rather than leaving them as unexplained defaults. That documentation is what makes this finding cheap to act on later. If triage grades this up, the work is a follow-up card against the policy, not rework against that PR.

Related

#7735 / PR #8017 (the enablement and the documented withheld options) · #7732 (session tombstoning — makes session validity auditable) · #7724 / PR #7879 (the admin deletion path, whose brokenness was the other half of #7735's ruling)

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions