{"id":"CVE-2026-53516","aliases":["EUVD-2026-44732","GHSA-g38m-r43w-p2q7"],"status":"published","title":"Better Auth: Account takeover via OAuth auto-link to unverified pre-registered email","description":"Better Auth is an authentication and authorization library for TypeScript. Prior to 1.6.11, Better Auth's OAuth callback auto-link gate in handleOAuthUserInfo accepts implicit account linking when the OAuth provider asserts email_verified: true without requiring the local user row's emailVerified field to also be true, allowing an attacker who pre-registers a victim email through /sign-up/email to bind the victim's OAuth identity to the attacker's account. The same primitive affects one-tap, and emailAndPassword.requireEmailVerification: true does not mitigate the link-time verification change. This issue is fixed in version 1.6.11.","severity":{"version":"3.1","score":8.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:L","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.00289,"percentile":0.21648,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-287","CWE-345"],"published":"2026-07-07T20:55:13Z","modified":"2026-10-28T02:31:10Z","fixes":[{"ecosystem":"npm","package":"better-auth","fixed_in":["1.6.11"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"better-auth","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.6.11"}]}]},{"source":"osv","ecosystem":"npm","package":"better-auth","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.6.11"}]}]}],"products":[{"vendor":"better-auth","product":"better-auth"},{"vendor":"better-auth","product":"better_auth"}],"references":[{"url":"https://github.com/better-auth/better-auth/security/advisories/GHSA-g38m-r43w-p2q7","tags":["x_refsource_CONFIRM","Mitigation","Vendor Advisory","advisory","web"]},{"url":"https://github.com/better-auth/better-auth/pull/9578","tags":["x_refsource_MISC","Issue Tracking","Patch","fix","web"]},{"url":"https://github.com/better-auth/better-auth/commit/da7e50beee849c59a2ed1ec6b3a38cc6ab9fb563","tags":["x_refsource_MISC","Patch","fix","web"]},{"url":"https://github.com/better-auth/better-auth/releases/tag/v1.6.11","tags":["x_refsource_MISC","Release Notes","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-53516","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-g38m-r43w-p2q7"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/53xxx/CVE-2026-53516.json","tags":["advisory"]},{"url":"https://github.com/better-auth/better-auth","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-53516","title":"Better Auth: Account takeover via OAuth auto-link to unverified pre-registered email","description":"Better Auth is an authentication and authorization library for TypeScript. Prior to 1.6.11, Better Auth's OAuth callback auto-link gate in handleOAuthUserInfo accepts implicit account linking when the OAuth provider asserts email_verified: true without requiring the local user row's emailVerified field to also be true, allowing an attacker who pre-registers a victim email through /sign-up/email to bind the victim's OAuth identity to the attacker's account. The same primitive affects one-tap, and emailAndPassword.requireEmailVerification: true does not mitigate the link-time verification change. This issue is fixed in version 1.6.11.","status":"published","published":"2026-07-15T17:13:16.431Z","modified":"2026-07-15T17:52:35.572Z","cvss":[{"version":"3.1","score":8.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:L","level":"high","assessor":"cna"}],"cwes":["CWE-287","CWE-345"],"updated_at":"2026-11-02T14:20:00.11477Z"}],"euvd":[{"id":"EUVD-2026-44732","description":"Better Auth is an authentication and authorization library for TypeScript. Prior to 1.6.11, Better Auth's OAuth callback auto-link gate in handleOAuthUserInfo accepts implicit account linking when the OAuth provider asserts email_verified: true without requiring the local user row's emailVerified field to also be true, allowing an attacker who pre-registers a victim email through /sign-up/email to bind the victim's OAuth identity to the attacker's account. The same primitive affects one-tap, and emailAndPassword.requireEmailVerification: true does not mitigate the link-time verification change. This issue is fixed in version 1.6.11.","status":"published","published":"2026-07-15T17:13:16Z","modified":"2026-07-15T17:52:35Z","cvss":[{"version":"3.1","score":8.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:L","level":"high"}],"updated_at":"2026-11-02T18:19:52.35951Z"}],"ghsa":[{"id":"GHSA-g38m-r43w-p2q7","title":"Better Auth has an account takeover issue via OAuth auto-link to unverified pre-registered email","description":"### Am I affected?\n\nUsers are affected if all of the following are true:\n\n- Their application uses `better-auth` at a version `\u003c 1.6.11` on the stable line, or any current `next` pre-release.\n- `emailAndPassword.enabled: true` is set in their application's `betterAuth({ ... })` configuration.\n- At least one OAuth or SSO provider is configured (any built-in social provider, or `genericOAuth(...)`, or any provider via `@better-auth/sso`).\n- `account.accountLinking.disableImplicitLinking` is not set to `true`.\n- `account.accountLinking.enabled` is not set to `false`.\n\nSetting either `disableImplicitLinking: true` or `enabled: false` closes the hole at the cost of breaking the standard \"add another login method\" UX. `emailAndPassword.requireEmailVerification: true` does not mitigate, because the link-time `emailVerified` flip promotes the attacker's row to verified, after which the password login becomes usable.\n\nFix:\n\n1. Upgrade to `better-auth@1.6.11` or later.\n2. If developers cannot upgrade, see workarounds below.\n\n### Summary\n\nThe OAuth callback's auto-link gate in `handleOAuthUserInfo` admits an implicit account link whenever the provider asserts `email_verified: true`, without requiring the local user row's `emailVerified` to also be `true`. An attacker who pre-registers a victim's email through `/sign-up/email` (which writes a row with `emailVerified: false`) can have the victim's later OAuth identity bound to the attacker's user row, granting both a password login and the victim's OAuth identity on the same account. This is the pre-account-hijacking class — the same shape as Microsoft \"nOAuth\" (2023) and the Sign in with Apple JWT flaw (2020).\n\n### Details\n\nThe auto-link gate validates only the OAuth provider's `userInfo.emailVerified` claim. The local row's `emailVerified` field is never read. When no `(accountId, providerId)` match exists, the user lookup falls back to email, which surfaces any pre-registered row at that email.\n\nA separate post-link step promotes the local `emailVerified` to `true` when the provider's claim is `true` and the local email matches the provider's email. This step is correct for legitimate first-time linking, but combined with the missing local-side check it becomes load-bearing for the takeover: after the link, the attacker's password row is treated as verified, defeating `requireEmailVerification: true` as a mitigation.\n\nThe fix adds the local-side ownership check to the gate: implicit linking now also rejects when `dbUser.user.emailVerified` is `false`. The same primitive lives in `one-tap` and inherits the same fix shape; the SSO `domainVerified` short-circuit follows separately as a hardening change.\n\n### Patches\n\nFixed in `better-auth@1.6.11`. Implicit linking now refuses to attach an OAuth identity to a local account whose `emailVerified` flag is `false`. The same gate change applies in the `one-tap` sign-in plugin, which previously had its own simpler linking path. The Google ID-token `email_verified` claim is also normalized through `toBoolean` so a string `\"false\"` is treated as falsy (some Google responses send the string, which the prior code treated as truthy).\n\nThe public surface for the new gate is `account.accountLinking.requireLocalEmailVerified`, defaulted to `true`. Applications whose users sign up through OAuth without ever verifying their email locally can opt out with `account: { accountLinking: { requireLocalEmailVerified: false } }` to retain the legacy permissive behavior. 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.\n\nTest fixtures across the `admin`, `oidc-provider`, `mcp`, `generic-oauth`, `last-login-method`, and `oauth-provider` suites now pre-verify created users via a `databaseHooks.user.create.before` hook (or the `disableTestUser` opt-in on the oauth-provider RP fixture) so those suites continue to exercise their role and flow logic rather than tripping the new gate.\n\n### Workarounds\n\nIf developers cannot upgrade their applications immediately:\n\n- **Disable implicit linking**: set `account.accountLinking.disableImplicitLinking: true`. Forces all linking through the authenticated `/link-social` endpoint where the user must already be signed in.\n- **Disable linking entirely**: set `account.accountLinking.enabled: false`. Closes the hole but breaks the multi-login-method UX entirely.\n\n`emailAndPassword.requireEmailVerification: true` alone does not mitigate, because the link-time `emailVerified` flip promotes the attacker's row to verified.\n\n### Impact\n\n- **Account takeover via pre-account hijacking**: the attacker holds a working password login plus the victim's OAuth identity on the same account, granting persistent access.\n- **`requireEmailVerification: true` bypass**: the attacker's password login becomes usable post-link.\n- **Cross-flow reach**: every OAuth and SSO sign-in path that calls `handleOAuthUserInfo` is affected (built-in social providers, generic-oauth, oauth-proxy, SSO OIDC, SSO SAML, one-tap).\n\n### Credit\n\nReported by @avrmeduard.\n\n### Resources\n\n- [CWE-287: Improper Authentication](https://cwe.mitre.org/data/definitions/287.html)\n- [CWE-345: Insufficient Verification of Data Authenticity](https://cwe.mitre.org/data/definitions/345.html)\n- [Sudhodanan \u0026 Paverd, Pre-hijacked accounts: an empirical study of security failures in user account creation on the web (USENIX Security 2022)](https://www.usenix.org/conference/usenixsecurity22/presentation/sudhodanan)","status":"published","published":"2026-07-07T20:55:13Z","modified":"2026-07-20T19:23:07Z","cvss":[{"version":"3.1","score":8.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:L","level":"high"}],"cwes":["CWE-287","CWE-345"],"updated_at":"2026-11-02T14:40:47.245721Z"}],"nvd":[{"id":"CVE-2026-53516","description":"Better Auth is an authentication and authorization library for TypeScript. Prior to 1.6.11, Better Auth's OAuth callback auto-link gate in handleOAuthUserInfo accepts implicit account linking when the OAuth provider asserts email_verified: true without requiring the local user row's emailVerified field to also be true, allowing an attacker who pre-registers a victim email through /sign-up/email to bind the victim's OAuth identity to the attacker's account. The same primitive affects one-tap, and emailAndPassword.requireEmailVerification: true does not mitigate the link-time verification change. This issue is fixed in version 1.6.11.","status":"published","published":"2026-07-15T18:16:47.967Z","modified":"2026-07-21T14:14:22.307Z","cvss":[{"version":"3.1","score":8.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:L","level":"high","assessor":"secondary"}],"cwes":["CWE-287","CWE-345"],"updated_at":"2026-11-02T14:12:29.879716Z"}],"osv":[{"id":"CVE-2026-53516","title":"Better Auth: Account takeover via OAuth auto-link to unverified pre-registered email","description":"Better Auth is an authentication and authorization library for TypeScript. Prior to 1.6.11, Better Auth's OAuth callback auto-link gate in handleOAuthUserInfo accepts implicit account linking when the OAuth provider asserts email_verified: true without requiring the local user row's emailVerified field to also be true, allowing an attacker who pre-registers a victim email through /sign-up/email to bind the victim's OAuth identity to the attacker's account. The same primitive affects one-tap, and emailAndPassword.requireEmailVerification: true does not mitigate the link-time verification change. This issue is fixed in version 1.6.11.","status":"published","published":"2026-07-15T17:13:16.431Z","modified":"2026-10-28T02:31:10.858258696Z","cvss":[{"version":"3.1","score":8.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:L","level":"high"}],"cwes":["CWE-287","CWE-345"],"updated_at":"2026-11-02T15:45:08.132336Z"},{"id":"GHSA-g38m-r43w-p2q7","title":"Better Auth has an account takeover issue via OAuth auto-link to unverified pre-registered email","description":"### Am I affected?\n\nUsers are affected if all of the following are true:\n\n- Their application uses `better-auth` at a version `\u003c 1.6.11` on the stable line, or any current `next` pre-release.\n- `emailAndPassword.enabled: true` is set in their application's `betterAuth({ ... })` configuration.\n- At least one OAuth or SSO provider is configured (any built-in social provider, or `genericOAuth(...)`, or any provider via `@better-auth/sso`).\n- `account.accountLinking.disableImplicitLinking` is not set to `true`.\n- `account.accountLinking.enabled` is not set to `false`.\n\nSetting either `disableImplicitLinking: true` or `enabled: false` closes the hole at the cost of breaking the standard \"add another login method\" UX. `emailAndPassword.requireEmailVerification: true` does not mitigate, because the link-time `emailVerified` flip promotes the attacker's row to verified, after which the password login becomes usable.\n\nFix:\n\n1. Upgrade to `better-auth@1.6.11` or later.\n2. If developers cannot upgrade, see workarounds below.\n\n### Summary\n\nThe OAuth callback's auto-link gate in `handleOAuthUserInfo` admits an implicit account link whenever the provider asserts `email_verified: true`, without requiring the local user row's `emailVerified` to also be `true`. An attacker who pre-registers a victim's email through `/sign-up/email` (which writes a row with `emailVerified: false`) can have the victim's later OAuth identity bound to the attacker's user row, granting both a password login and the victim's OAuth identity on the same account. This is the pre-account-hijacking class — the same shape as Microsoft \"nOAuth\" (2023) and the Sign in with Apple JWT flaw (2020).\n\n### Details\n\nThe auto-link gate validates only the OAuth provider's `userInfo.emailVerified` claim. The local row's `emailVerified` field is never read. When no `(accountId, providerId)` match exists, the user lookup falls back to email, which surfaces any pre-registered row at that email.\n\nA separate post-link step promotes the local `emailVerified` to `true` when the provider's claim is `true` and the local email matches the provider's email. This step is correct for legitimate first-time linking, but combined with the missing local-side check it becomes load-bearing for the takeover: after the link, the attacker's password row is treated as verified, defeating `requireEmailVerification: true` as a mitigation.\n\nThe fix adds the local-side ownership check to the gate: implicit linking now also rejects when `dbUser.user.emailVerified` is `false`. The same primitive lives in `one-tap` and inherits the same fix shape; the SSO `domainVerified` short-circuit follows separately as a hardening change.\n\n### Patches\n\nFixed in `better-auth@1.6.11`. Implicit linking now refuses to attach an OAuth identity to a local account whose `emailVerified` flag is `false`. The same gate change applies in the `one-tap` sign-in plugin, which previously had its own simpler linking path. The Google ID-token `email_verified` claim is also normalized through `toBoolean` so a string `\"false\"` is treated as falsy (some Google responses send the string, which the prior code treated as truthy).\n\nThe public surface for the new gate is `account.accountLinking.requireLocalEmailVerified`, defaulted to `true`. Applications whose users sign up through OAuth without ever verifying their email locally can opt out with `account: { accountLinking: { requireLocalEmailVerified: false } }` to retain the legacy permissive behavior. 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.\n\nTest fixtures across the `admin`, `oidc-provider`, `mcp`, `generic-oauth`, `last-login-method`, and `oauth-provider` suites now pre-verify created users via a `databaseHooks.user.create.before` hook (or the `disableTestUser` opt-in on the oauth-provider RP fixture) so those suites continue to exercise their role and flow logic rather than tripping the new gate.\n\n### Workarounds\n\nIf developers cannot upgrade their applications immediately:\n\n- **Disable implicit linking**: set `account.accountLinking.disableImplicitLinking: true`. Forces all linking through the authenticated `/link-social` endpoint where the user must already be signed in.\n- **Disable linking entirely**: set `account.accountLinking.enabled: false`. Closes the hole but breaks the multi-login-method UX entirely.\n\n`emailAndPassword.requireEmailVerification: true` alone does not mitigate, because the link-time `emailVerified` flip promotes the attacker's row to verified.\n\n### Impact\n\n- **Account takeover via pre-account hijacking**: the attacker holds a working password login plus the victim's OAuth identity on the same account, granting persistent access.\n- **`requireEmailVerification: true` bypass**: the attacker's password login becomes usable post-link.\n- **Cross-flow reach**: every OAuth and SSO sign-in path that calls `handleOAuthUserInfo` is affected (built-in social providers, generic-oauth, oauth-proxy, SSO OIDC, SSO SAML, one-tap).\n\n### Credit\n\nReported by @avrmeduard.\n\n### Resources\n\n- [CWE-287: Improper Authentication](https://cwe.mitre.org/data/definitions/287.html)\n- [CWE-345: Insufficient Verification of Data Authenticity](https://cwe.mitre.org/data/definitions/345.html)\n- [Sudhodanan \u0026 Paverd, Pre-hijacked accounts: an empirical study of security failures in user account creation on the web (USENIX Security 2022)](https://www.usenix.org/conference/usenixsecurity22/presentation/sudhodanan)","status":"published","published":"2026-07-07T20:55:13Z","modified":"2026-07-20T19:30:23.99296091Z","cvss":[{"version":"3.1","score":8.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:L","level":"high"}],"cwes":["CWE-287","CWE-345"],"updated_at":"2026-11-02T15:46:47.119724Z"}]}}
