CVE-2026-53514
highpriority · track · 0OT risk · elevatedEPSS 0.002 · 10 %ileEUUSEPSS · scored 2026-10-27
Better Auth is an authentication and authorization library for TypeScript. Prior to 1.6.11, and in 1.6.14 and later when invitation IDs can be obtained outside the invited mailbox and requireEmailVerificationOnInvitation: true is not enabled, the organization plugin's acceptInvitation, rejectInvitation, getInvitation, and listUserInvitations recipient endpoints use session.user.email and an invitation ID without sufficient verified-email ownership proof, allowing a user with an unverified session for the invited email address to accept an organization invitation after obtaining the invitation ID. This issue is fixed for the original default behavior in version 1.6.11, while 1.6.14 restored compatibility for built-in opaque invitation IDs and leaves affected configurations requiring secure options.
cvss
Technical severity, 0–10. One row per scoring source.
base score 0–10 · higher = more severe · hover a vector to decode
ssvc
A stakeholder decision — act, attend, or track — from exploitation and impact.
weaknesses
The CWE weakness classes this flaw belongs to.
affected products (4)
Products and version ranges known to be affected.
advisories (1)
Vendor and ecosystem advisories describing this.
- GHSA-fmh4-wcc4-5jm3Better Auth vulnerable to unauthorized invitation acceptance via unverified email match in organization plugin2026-07-07ghsa · osvUS
### Am I affected? Users are affected if all of the following are true: - Their application uses `better-auth` with the `organization` plugin (`import { organization } from "better-auth/plugins/organization"`). - Their application enables a sign-up surface that allows arbitrary unverified email registration. Most commonly `emailAndPassword: { enabled: true }` without `requireEmailVerification: true`. - Their application has not set `requireEmailVerificationOnInvitation: true` on the `organization()` options. - Their application invitation distribution flow allows anyone other than the invited mailbox owner to obtain the `invitationId`. Examples: admin UI surfacing the link, copy-paste into chat, forwarded email, mail-forwarding rules at the recipient's domain, link previews logging the URL, or a custom `sendInvitationEmail` integration that sends to a non-owner channel. If their application set `emailAndPassword: { enabled: true, requireEmailVerification: true }` so unverified rows cannot reach a usable session, they are not affected. Setting `requireEmailVerificationOnInvitation: true` closes `acceptInvitation` and `rejectInvitation`, but `getInvitation` and `listUserInvitations` remain ungated even with that flag. Fix: 1. Upgrade to `better-auth@1.6.11` or later. 2. If developers cannot upgrade their application, see workarounds below. ### Summary The organization plugin's `acceptInvitation` endpoint trusts an email-string equality check as proof that the session user owns the invited address. With Better Auth's stock `emailAndPassword: { enabled: true }` configuration, `requireEmailVerification` defaults to `false`, so an attacker can sign up a row keyed to `victim@target.example` (auto-signed-in, `emailVerified: false`) before the legitimate owner. When an organization admin invites that address, the attacker presents the `invitationId` and accepts the invitation, joining the organization at the invited role. ### Details The recipient gate compares `invitation.email.toLowerCase()` to `session.user.email.toLowerCase()` and returns 403 on mismatch. The opt-in `requireEmailVerificationOnInvitation` flag adds an `emailVerified` check, but it defaults to `false` and only fires on `acceptInvitation` and `rejectInvitation`; `getInvitation` and `listUserInvitations` have no `emailVerified` gate at all. The bearer token (`invitationId`) is by default 32 chars over `[a-zA-Z0-9]` (~190 bits), so the realistic attack vector is leakage of the invitation link rather than brute force. The fix shape defaults the `emailVerified` gate to on and extends it across all four invitation endpoints (`acceptInvitation`, `rejectInvitation`, `getInvitation`, `listUserInvitations`). This is the same trust-primitive class as GHSA-g38m-r43w-p2q7 (OAuth auto-link); both ship the rule "email equality is not ownership proof; both sides must prove ownership". ### Patches Fixed in `better-auth@1.6.11`. All four invitation recipient endpoints (`acceptInvitation`, `rejectInvitation`, `getInvitation`, `listUserInvitations`) now require the session user's `emailVerified` to be `true` in addition to the email-string match. The `requireEmailVerificationOnInvitation` option default flips from `false` to `true`, so applications are secure out of the box. `getInvitation` and `listUserInvitations` use the new `EMAIL_VERIFICATION_REQUIRED_FOR_INVITATION` error code so the wording matches the operation; `acceptInvitation` and `rejectInvitation` keep the existing `EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION` code. Server-side calls to `listUserInvitations` that pass `ctx.query.email` without an authenticated session continue to bypass the gate; the gate is specific to session-authenticated recipient calls. Integrators who intentionally accept invitations on unverified sessions can preserve the legacy permissive behavior with `organization({ requireEmailVerificationOnInvitation: false })`. The option is marked `@deprecated`; the gate at each call site carries a `FIXME` pointing at the next-minor follow-up that drops the option and makes the check unconditional. Operators that take this opt-out should understand the takeover risk before doing so. ### Workarounds If developers cannot upgrade their applications immediately: - **Set `organization({ requireEmailVerificationOnInvitation: true })`**. Closes `acceptInvitation` and `rejectInvitation` against unverified sessions. Does not close `getInvitation` or `listUserInvitations`. - **Set `emailAndPassword.requireEmailVerification: true`** (or remove email/password sign-up entirely). Closes the pre-registration step itself. - **Layer middleware** on the organization invitation routes that asserts `session.user.emailVerified === true` and rejects otherwise. ### Impact - **Account takeover via pre-account hijacking on the org invitation surface**: the attacker, holding only an unverified self-issued session and the leaked `invitationId`, joins the organization as a member at the invited role. - **Organization membership reach**: the attacker reads invitation contents and any organization-scoped data the joined role can see, and acts as a member of the victim organization. ### Credit Reported by @widavies. ### Resources - [CWE-287: Improper Authentication](https://cwe.mitre.org/data/definitions/287.html) - [CWE-345: Insufficient Verification of Data Authenticity](https://cwe.mitre.org/data/definitions/345.html) - [CWE-862: Missing Authorization](https://cwe.mitre.org/data/definitions/862.html) - [CWE-441: Unintended Proxy or Intermediary](https://cwe.mitre.org/data/definitions/441.html)
also known as (3)
Other names and identifiers for this entity.
sources
Which sources assert this record, and when.
references (13)
External citations — patches, advisories, analysis.
- https://github.com/better-auth/better-auth/commit/23094a628f007f801be6d26e5b15dc5fc6fc4eb8cnnvd
- https://github.com/better-auth/better-auth/pull/9577cnnvd
- https://github.com/better-auth/better-auth/releasesPatchcnnvd
- https://github.com/better-auth/better-auth/releases/tag/v1.6.11cnnvd
- https://github.com/better-auth/better-auth/security/advisories/GHSA-fmh4-wcc4-5jm3cnnvd
- https://github.com/better-auth/better-auth/commit/23094a628f007f801be6d26e5b15dc5fc6fc4eb8enisa_euvd
- https://github.com/better-auth/better-auth/pull/9577enisa_euvd
- https://github.com/better-auth/better-auth/releases/tag/v1.6.11enisa_euvd
Attackers weaponise vulnerabilities like this against internet-exposed services. See what is exposed on your perimeter with a free passive scan.
Free scan