Skip to content

GHSA-fmh4-wcc4-5jm3 CVE-2026-53514

Better Auth vulnerable to unauthorized invitation acceptance via unverified email match in organization plugin

High npm
Severity
7.7 / 10
High · CVSS 3.1
Exploit chance (EPSS)
0.20%
Chance of exploitation in the next 30 days. Higher than 9.98% of scored vulnerabilities.
Known exploited
No
Not in CISA's catalog
Weakness
CWE-287 Improper Authentication
CWE-345 Insufficient Verification of Data Authenticity
CWE-441 Unintended Proxy or Intermediary ('Confused Deputy')
CWE-862 Missing Authorization
Fix
1.6.11
Upgrade better-auth to this version or later

Description

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

Affected versions

npm

PackageVulnerableFixed in
better-auth
< 1.6.11
1.6.11

How to fix

Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.

better-auth npm → 1.6.11

npm
npm install better-auth@1.6.11
yarn
yarn add better-auth@1.6.11
pnpm
pnpm add better-auth@1.6.11

Severity breakdown

CVSS 3.1 7.7

CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:C/C:H/I:H/A:N

Attack vector
Network
Attack complexity
High
Privileges required
Low
User interaction
Required
Scope
Changed
Confidentiality
High
Integrity
High
Availability
None

Weakness

CWE-287: Improper Authentication

When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.

How to prevent it
  • Architecture and Design: Use an authentication framework or library such as the OWASP ESAPI Authentication feature.

CWE-345: Insufficient Verification of Data Authenticity

The product does not sufficiently verify the origin or authenticity of data, in a way that causes it to accept invalid data.

CWE-441: Unintended Proxy or Intermediary ('Confused Deputy')

The product receives a request, message, or directive from an upstream component, but the product does not sufficiently preserve the original source of the request before forwarding the request to an external actor that is outside of the product's control sphere. This causes the product to appear to be the source of the request, leading it to act as a proxy or other intermediary between the upstream component and the external actor.

How to prevent it
  • Architecture and Design: Enforce the use of strong mutual authentication mechanism between the two parties.
  • Architecture and Design: Whenever a product is an intermediary or proxy for transactions between two other components, the proxy core should not drop the identity of the initiator of the transaction. The immutability of the identity of the initiator must be maintained and should be forwarded all the way to the target.

CWE-862: Missing Authorization

The product does not perform an authorization check when an actor attempts to access a resource or perform an action.

How to prevent it
  • Architecture and Design: Divide the product into anonymous, normal, privileged, and administrative areas. Reduce the attack surface by carefully mapping roles with data and functionality. Use role-based access control (RBAC) [REF-229] to enforce the roles at the appropriate boundaries. Note that this approach may not protect against horizontal authorization, i.e., it will not protect a user from attacking others with the same role.
  • Architecture and Design: Ensure that access control checks are performed related to the business logic. These checks may be different than the access control checks that are applied to more generic resources such as files, connections, processes, memory, and database records. For example, a database may restrict access for medical records to a specific database user, but each record might only be intended to be accessible to the patient and the patient's doctor [REF-7].
  • Architecture and Design: Use a vetted library or framework that does not allow this weakness to occur or provides constructs that make this weakness easier to avoid. For example, consider using authorization frameworks such as the JAAS Authorization Framework [REF-233] and the OWASP ESAPI Access Control feature [REF-45].
  • Architecture and Design: For web applications, make sure that the access control mechanism is enforced correctly at the server side on every page. Users should not be able to access any unauthorized functionality or information by simply requesting direct access to that page. One way to do this is to ensure that all pages containing sensitive information are not cached, and that all such pages restrict access to requests that are accompanied by an active and authenticated session token associated with a user who has the required permissions to access that page.
  • System Configuration, Installation: Use the access control capabilities of your operating system and server environment and define your access control lists accordingly. Use a "default deny" policy when defining these ACLs.

References

Timeline

  1. Published
  2. Last updated by the source
  3. Latest EPSS score
AdvisorySeverityWhy it's related
GHSA-r4xp-prcw-77qf
better-auth: OAuth Proxy lets an attacker sign in as another user
Critical Also affects better-auth
GHSA-965c-763c-88jm
better-auth: OAuth state can be used as a Magic Link to sign in as another user
Critical Also affects better-auth
GHSA-44jh-23m7-hpcf
@better-auth/drizzle-adapter: Concurrent requests can exceed rate limits on PostgreSQL
Medium Also affects better-auth
GHSA-q84f-53jg-9ppm
better-auth: Device login approval does not show the requesting client or permissions
High Also affects better-auth
GHSA-qq9h-g4jm-xgf3
Better Auth: Account takeover via pre-account hijacking on magic-link and email-OTP sign-in
High Also affects better-auth
GHSA-2vg6-77g8-24mp
Better Auth: Stale sessions persist after user deletion across admin, anonymous, and SCIM flows
Medium Also affects better-auth
GHSA-7w99-5wm4-3g79
@better-auth/oauth-provider's OAuth authorization-code grant allows concurrent redemption when two token requests race the find-then-delete primitive
High Also affects better-auth
GHSA-392p-2q2v-4372
Better Auth: OAuth refresh-token rotation forks the token family on concurrent redemption
High Also affects better-auth
Is this in your code?

MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.

Check your repos with MergeHarbor