
Better Auth is a TypeScript authentication framework that handles how people sign in and stay signed in. Teams building apps use it for sessions and 2FA, and its plugins can connect those accounts to desktop apps and Stripe billing.
The login response said to complete two-factor authentication. The desktop handoff had already issued the credential needed to get around it.
In another plugin, the same profile-update endpoint that correctly rejected an attempt to become an administrator accepted a change to the Stripe customer ID. Downstream billing operations then trusted the attacker-selected value.
These were two different ways to break a critical assumption: a valid session should mean authentication is complete, and a billing identity should belong to the authenticated user.
HIGH CVSS 8.1*
An Electron login could bypass 2FA
With a known victim password and the affected Electron configuration, an attacker could obtain a session for a 2FA-protected account.
MODERATE CVSS 5.9
A Stripe ID could point to someone else’s billing portal
A signed-in attacker who knew a victim’s Stripe customer ID could redirect billing operations to that customer.
*The High score belongs to the combined Electron advisory, including a separate secret-disclosure issue. Both advisories are patched in 1.7.16 and 1.8.0-beta.5.
Bug 1 · Electron 2FA bypass
How the Electron login bypassed 2FA
Better Auth’s Electron plugin supports browser-to-desktop sign-in. The browser authenticates the user, then a short-lived authorization code lets the desktop client obtain a session. The two-factor plugin adds another requirement before authentication can finish.
The vulnerable interaction depended on hook order. When electron() ran before twoFactor(), it could mint that desktop authorization code from the temporary session created during password verification.
The two-factor hook then removed the temporary session and returned twoFactorRedirect: true. The JSON response no longer included the desktop code. But the code had already been written to a cookie, and deleting the temporary session did not invalidate it.
The token-exchange endpoint validated the handoff’s state and PKCE verifier, then created a new session. Those checks protected the handoff, but did not establish that the user had completed 2FA.
What went wrong
A credential issued before authentication finishes must not produce a fully authenticated session.
Watch the 2FA bypass
The 2FA video shows a live local test on Better Auth 1.7.3. A password-only sign-in yields a full session and a profile write. Reversing the plugin order blocks the same path. The terminal shows each request and response, with credentials redacted.
We changed the victim’s profile without a 2FA code
The reproduction enrolled a real TOTP factor on a real Better Auth server. The attack supplied the password, but never supplied a TOTP. It obtained a full victim session through the Electron exchange, then used that session to change the victim’s profile. That final write established an observable account impact beyond receiving a token.
A negative control reversed the plugin order. With twoFactor() first, the desktop code was never minted and the bypass failed. That comparison isolated the interaction responsible for the vulnerability.
The affected path required both plugins, the vulnerable order, password sign-in for a 2FA-enabled user, and knowledge of the victim’s password. It was a failure of the protection expected after password compromise.
The fix: the desktop code now requires a live session
The patch binds Electron authorization codes to a hash of the issuing browser session’s token. Redemption requires that source session to still be live and unexpired. When the pending-2FA flow removes the temporary session, the handoff code loses its authority too. The fix preserves that relationship across plugin orderings.
This distinction matters for engineering teams. A registration-order workaround can suppress a trigger. Binding the credential to the required authentication state closes the underlying path.
Bug 2 · Stripe customer ID
Users could change their Stripe customer ID
The Stripe plugin stores stripeCustomerId on the user record. Billing operations use it to select the Stripe customer. Despite that authority, the field lacked input: false, the framework’s mechanism for rejecting client changes to server-managed fields.
An authenticated user could therefore change the value through a normal profile update. The billing flow treated the stored value as trusted because it came from the user record, even though the user could write it.
What went wrong
A user must not be able to choose the customer identity that authorizes their billing actions.
Watch the Stripe customer ID bug
The Stripe video shows a live local test on Better Auth 1.7.3. A client-supplied customer ID persists, while an admin-role update is rejected. The stubbed Stripe SDK receives the supplied ID. The terminal shows each request and response, with credentials redacted.
The changed ID reached billing operations
The reproduction confirmed that the customer-ID update persisted in the database and appeared in the session. A control request attempting to change role to admin was rejected with FIELD_NOT_ALLOWED. The protection existed and worked for another authority-bearing field on the same route.
We then traced the modified ID into the billing-portal and email-sync calls. The test used a real Better Auth server with the outbound Stripe SDK stubbed. It proved that billing operations received the attacker-selected customer ID. It did not acquire a victim’s ID, open a live victim portal, or charge a payment method.
The published advisory confirms the broader consequence: a signed-in user who knows another customer’s Stripe ID can access their billing portal, view billing details and invoices, change payment methods, and change or cancel subscriptions. Billing effects require subscription.enabled: true.
For a business moving money, unauthorized subscription changes and exposed billing records are material impacts.
The fix: users can no longer set a Stripe customer ID
The patch rejects customer IDs and managed subscription fields from sign-up and profile-update input. Billing routes also use the customer type established by authorization middleware. When several records share a customer ID, webhook attribution checks Stripe’s recorded owner.
PATCH EXCERPT / packages/stripe/src/schema.ts
stripeCustomerId: { type: "string", required: false,+ input: false,}
That boundary also applies to server calls through auth.api.updateUser. Code that legitimately manages these fields must write through the database adapter.
Updating the package does not repair values written before the update. Teams must inspect existing customer mappings and reconcile duplicates with Stripe’s recorded owner. A duplicate is an investigation lead, not automatic proof of tampering.
Vague-posting security updates
On October 29, Better Auth announced an upcoming security release, scheduled for November 3, with advisories to follow at the same time. The post told teams when to prepare without publishing the vulnerable paths.

The announcement set the release date without identifying the vulnerable paths. The November 3 advisories supplied the affected versions, exploit conditions, and fixes.
On November 3, the team announced the patched releases. Its release notice lists 12 security advisories. Octane is credited on the two covered here.
The practical problem for a CISO is the interval between a vulnerability existing and the organization fixing it. Once details are public, your team is working against an external clock. Proactive agentic research creates an earlier opportunity to discover the path, prove the impact, and close it. The best time to apply that capability is before deployment. For software already in use, coordinated research and disclosure still turn exploitable behavior into an actionable patch.
How we tested both bugs end to end
These findings illustrate why agentic research needs to reason across the application. A password check, a desktop handoff, and a two-factor hook can each appear plausible while their combined behavior violates the authentication contract. A harmless-looking schema field becomes dangerous when a later operation uses it to select a billing identity.
Octane combines agentic investigation with security researchers who direct the search and validate impact. The practical edge comes from keeping the application’s context through the entire investigation: what the attacker controls, what the code trusts, and what action becomes possible. A promising hypothesis earns its place in a report by surviving that scrutiny.
Here, the evidence joined an entry point to a state change, then to an unauthorized capability. The controls made the explanation testable: change the plugin order and the handoff disappears; change the field to an administrator role and the same update route refuses it. That gives maintainers a concrete repair target and security leaders a reason to prioritize the work.
For teams responsible for customer data and money, the same discipline belongs before deployment, during major changes, and continuously as dependencies evolve. For authentication, test every route capable of issuing a session. For billing, trace every writable field that can select an account, tenant, customer, or payment destination. Exercise combinations and failure states, then keep the resulting regression tests as those systems change.
Research against already released software still has immediate value. It gives maintainers a reproducible path to close and downstream users an actionable patch. The Better Auth release is an example of that process producing fixes for exploitable behavior in a widely reusable dependency.
What to update if you use Better Auth
Affected package versions from the published advisoriesPackageAffected stable / earlier lineAffected 1.8 beta line@better-auth/electron≥ 1.5.0-beta.12, < 1.7.16≥ beta.0, < beta.5@better-auth/stripe≥ 1.2.0-beta.18, < 1.7.16≥ beta.0, < beta.5
- Update the core and plugins together. The fixed releases are
1.7.16and1.8.0-beta.5. Check the advisories against the versions and configurations you deploy. - For Electron, follow the full advisory. It also covers a separate secret-disclosure issue. Previously deployed affected installations should treat the auth secret and configured social-provider client secrets as disclosed and rotate them. Reordering plugins only addresses the confirmed 2FA path.
- For Stripe, inspect existing mappings. Check for customer IDs shared by multiple users and compare them with the Stripe customer’s owner metadata. Apply the same check to organizations if organization billing is enabled.
Read the Electron advisory, Stripe advisory, and fix commit for the complete conditions and remediation.
Octane found both issues; Better Auth fixed them
Octane’s security team identified both the Electron 2FA bypass and the Stripe customer ID flaw in Better Auth.
We thank the Better Auth maintainers for the fixes and coordinated disclosure. The published patch and regression tests cover the session binding and customer-ID protections described above.
Test your login and billing flows
Octane applies agentic security research to the code and workflows that control customer data and money. Bring your highest-risk authentication or billing flow. Start with a concrete question: can an attacker cross this boundary?
Subscribe to our newsletter











