CVE-2026-67335
better-auth before 1.6.2 OAuth State Validation Bypass
better-auth versions before 1.6.2 fail to validate the OAuth state parameter against the stored nonce when using cookie-backed state storage without PKCE. Attackers can forge the state parameter and supply an attacker-controlled authorization code to create authenticated sessions bound to the attacker's external identity or persistently link attacker accounts to victim profiles.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- High
- Privileges needed
- None
- User interaction
- Passive
What an attacker can affect
- Confidentiality
- None
- Integrity
- High
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
6.0
better-auth before 1.6.2 OAuth State Validation Bypass
better-auth versions before 1.6.2 fail to validate the OAuth state parameter against the stored nonce when using cookie-backed state storage without PKCE. Attackers can forge the state parameter and supply an attacker-controlled authorization code to create authenticated sessions bound to the attacker's external identity or persistently link attacker accounts to victim profiles.
- Record
CVE-2026-67335- CVSS 4.0
- 6.0 Medium
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:P/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N - CVSS 3.1
- 5.3 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:N - Weakness
- CWE-287
- Updated
- 2026-11-02
EUVDEUVD-2026-51852
6.0
better-auth versions before 1.6.2 fail to validate the OAuth state parameter against the stored nonce when using cookie-backed state storage without PKCE. Attackers can forge the state parameter and supply an attacker-controlled authorization code to create authenticated sessions bound to the attacker's external identity or persistently link attacker accounts to victim profiles.
- Record
EUVD-2026-51852- CVSS 4.0
- 6.0 Medium
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:P/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-4hp3-gxv8-7g37
6.0
better-auth versions before 1.6.2 fail to validate the OAuth state parameter against the stored...
better-auth versions before 1.6.2 fail to validate the OAuth state parameter against the stored nonce when using cookie-backed state storage without PKCE. Attackers can forge the state parameter and supply an attacker-controlled authorization code to create authenticated sessions bound to the attacker's external identity or persistently link attacker accounts to victim profiles.
- Record
GHSA-4hp3-gxv8-7g37- CVSS 4.0
- 6.0 Medium
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:P/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 5.3 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:N - Weakness
- CWE-287
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-wxw3-q3m9-c3jr
5.3
Better Auth: OAuth callback accepts mismatched `state` when cookie-backed state storage is used without PKCE
Am I affected?
Users are affected if all of the following are true:
- The application uses
better-authat a version below1.6.2(or@better-auth/ssopaired with such a version). betterAuth({ account: { storeStateStrategy } })is set to"cookie". The default"database"is not affected.- The application wires at least one OAuth provider through
genericOAuth({ config })withpkce: false, or it supplies a customgetTokenortokenUrlthat does not require the storedcodeVerifier. Stock social providers with PKCE on are not affected. - The provider returns arbitrary
codevalues to the configured callback URL.
If users are on [email protected] or later, they are not affected.
Fix:
- Upgrade to
[email protected]or later (current stable is1.6.10). - If users cannot upgrade, see workarounds below.
Summary
In parseGenericState, the cookie branch decrypted the oauth_state cookie and validated expiry, but did not compare the incoming OAuth state query parameter to the nonce that generateGenericState issued at sign-in. Any callback to /api/auth/oauth2/callback/<providerId> that arrived with a forged state and any code was therefore accepted as long as the browser still held a live oauth_state cookie. With pkce: false (or any getToken path that does not enforce a code-verifier round-trip), an attacker who forced the victim to deliver an attacker-controlled authorization code to the callback would mint a session bound to the attacker's external identity in the victim's browser. Account-linking flows behaved the same way, binding the attacker's external account to an authenticated victim row.
Details
The cookie branch of parseGenericState did not compare the cookie's stored nonce to the incoming state parameter. The database branch (the default) was not affected because the verification row is keyed by state and the lookup itself enforces equality.
The fix re-binds the cookie to the nonce: generateGenericState writes oauthState: state into the encrypted payload before storage, and parseGenericState rejects when parsedData.oauthState !== state. The same primitive covers every caller (generic-oauth, social, account-link, oauth-proxy passthrough, OIDC SSO, SAML relay state).
Patches
Fixed in [email protected] via PR #8949 (commit 9deb7936a, merged 2026-04-09). The cookie branch of parseGenericState now rejects when the encrypted payload's nonce does not match the incoming state parameter; the database branch gained a defense-in-depth equality check.
Workarounds
If users cannot upgrade immediately:
- Switch
storeStateStrategyback to"database"(the default). This closes the cookie-only bypass without a code change. - Enable
pkce: trueon every affectedgenericOAuthprovider. ThecodeVerifieris the missing primitive that the attacker cannot supply.
Impact
- Forced-login (CSRF on OAuth callback): the attacker forces the victim's browser into an authenticated session bound to the attacker's external identity, allowing the attacker to observe the victim's actions inside the application.
- Persistent account linking: account-link flows bind the attacker's external account to the victim's authenticated row, granting persistent access until the link is removed.
Credit
Reported by @Jvr2022 via private advisory disclosure, and by @alavesa (PatchPilots audit) via the public duplicate issue #8897.
Resources
- CWE-352: Cross-Site Request Forgery (CSRF)
- CWE-345: Insufficient Verification of Data Authenticity
- CWE-287: Improper Authentication
- [RFC 6749 §10.12: Cross-Site Request…
- Record
GHSA-wxw3-q3m9-c3jr- CVSS 3.1
- 5.3 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:N - Weakness
- CWE-287, CWE-345, CWE-352
- Updated
- 2026-11-02
NVD
6.0
better-auth versions before 1.6.2 fail to validate the OAuth state parameter against the stored nonce when using cookie-backed state storage without PKCE. Attackers can forge the state parameter and supply an attacker-controlled authorization code to create authenticated sessions bound to the attacker's external identity or persistently link attacker accounts to victim profiles.
- Record
CVE-2026-67335- CVSS 4.0
- 6.0 Medium
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:P/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 5.3 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:N - Weakness
- CWE-287
- Updated
- 2026-11-02
OSV
6.0
better-auth before 1.6.2 OAuth State Validation Bypass
better-auth versions before 1.6.2 fail to validate the OAuth state parameter against the stored nonce when using cookie-backed state storage without PKCE. Attackers can forge the state parameter and supply an attacker-controlled authorization code to create authenticated sessions bound to the attacker's external identity or persistently link attacker accounts to victim profiles.
- Record
CVE-2026-67335- CVSS 4.0
- 6.0 Medium
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:P/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N - Weakness
- CWE-287
- Updated
- 2026-11-02
OSVGHSA-wxw3-q3m9-c3jr
5.3
Better Auth: OAuth callback accepts mismatched `state` when cookie-backed state storage is used without PKCE
Am I affected?
Users are affected if all of the following are true:
- The application uses
better-authat a version below1.6.2(or@better-auth/ssopaired with such a version). betterAuth({ account: { storeStateStrategy } })is set to"cookie". The default"database"is not affected.- The application wires at least one OAuth provider through
genericOAuth({ config })withpkce: false, or it supplies a customgetTokenortokenUrlthat does not require the storedcodeVerifier. Stock social providers with PKCE on are not affected. - The provider returns arbitrary
codevalues to the configured callback URL.
If users are on [email protected] or later, they are not affected.
Fix:
- Upgrade to
[email protected]or later (current stable is1.6.10). - If users cannot upgrade, see workarounds below.
Summary
In parseGenericState, the cookie branch decrypted the oauth_state cookie and validated expiry, but did not compare the incoming OAuth state query parameter to the nonce that generateGenericState issued at sign-in. Any callback to /api/auth/oauth2/callback/<providerId> that arrived with a forged state and any code was therefore accepted as long as the browser still held a live oauth_state cookie. With pkce: false (or any getToken path that does not enforce a code-verifier round-trip), an attacker who forced the victim to deliver an attacker-controlled authorization code to the callback would mint a session bound to the attacker's external identity in the victim's browser. Account-linking flows behaved the same way, binding the attacker's external account to an authenticated victim row.
Details
The cookie branch of parseGenericState did not compare the cookie's stored nonce to the incoming state parameter. The database branch (the default) was not affected because the verification row is keyed by state and the lookup itself enforces equality.
The fix re-binds the cookie to the nonce: generateGenericState writes oauthState: state into the encrypted payload before storage, and parseGenericState rejects when parsedData.oauthState !== state. The same primitive covers every caller (generic-oauth, social, account-link, oauth-proxy passthrough, OIDC SSO, SAML relay state).
Patches
Fixed in [email protected] via PR #8949 (commit 9deb7936a, merged 2026-04-09). The cookie branch of parseGenericState now rejects when the encrypted payload's nonce does not match the incoming state parameter; the database branch gained a defense-in-depth equality check.
Workarounds
If users cannot upgrade immediately:
- Switch
storeStateStrategyback to"database"(the default). This closes the cookie-only bypass without a code change. - Enable
pkce: trueon every affectedgenericOAuthprovider. ThecodeVerifieris the missing primitive that the attacker cannot supply.
Impact
- Forced-login (CSRF on OAuth callback): the attacker forces the victim's browser into an authenticated session bound to the attacker's external identity, allowing the attacker to observe the victim's actions inside the application.
- Persistent account linking: account-link flows bind the attacker's external account to the victim's authenticated row, granting persistent access until the link is removed.
Credit
Reported by @Jvr2022 via private advisory disclosure, and by @alavesa (PatchPilots audit) via the public duplicate issue #8897.
Resources
- CWE-352: Cross-Site Request Forgery (CSRF)
- CWE-345: Insufficient Verification of Data Authenticity
- CWE-287: Improper Authentication
- [RFC 6749 §10.12: Cross-Site Request…
- Record
GHSA-wxw3-q3m9-c3jr- CVSS 3.1
- 5.3 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:N - Weakness
- CWE-287, CWE-345, CWE-352
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
better-authnpm |
before 1.6.2 | 1.6.2 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-67335 is a medium vulnerability (CVSS 6.0) in Better-auth, published May 15, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.28%, the 20th percentile. Fixed in better-auth 1.6.2.
References
github.com/better-auth/better-auth/security/advisories/GHSA-wxw3-q3m9-c3jr
vulncheck.com/advisories/better-auth-before-oauth-state-validation-bypass
nvd.nist.gov/vuln/detail/CVE-2026-67335
github.com/advisories/GHSA-4hp3-gxv8-7g37
github.com/better-auth/better-auth/issues/8897
github.com/better-auth/better-auth/pull/8949
Left out here: the rest of 2 source descriptions. The JSON record has them.