{"id":"GHSA-9hm7-xhfp-cx58","kind":"vulnerability","aliases":["GHSA-9hm7-xhfp-cx58"],"related":[],"title":"better-auth: OAuth sign-in can treat an unverified provider email as verified","description":"## Am I affected?\n\nYou are affected if you use OAuth sign-in from `better-auth` 0.4.9 or later and **any** of these is true:\n\n- A provider sends `email_verified` as a string and lets its users keep an unverified address. Amazon Cognito's user info endpoint is one example.\n- You use `genericOAuth()` with a provider that has a JWKS endpoint.\n- You use the Zoom provider with `disableSignUp`, `requireEmailVerification`, or `overrideUserInfoOnSignIn`.\n\nYou are not affected for providers listed in `account.accountLinking.trustedProviders`, where linking by email is intended.\n\n## Summary\n\n**String claims.** Better Auth treated any non-empty `email_verified` value as verified, so the string `\"false\"` passed.\n\n**Profile and token mismatch.** With generic OAuth, the user info profile was not checked against the ID token's subject, and direct ID-token sign-in could load the profile from a submitted access token.\n\n**Zoom options.** The Zoom provider ignored its configured provider options.\n\n## Impact\n\nSomeone who can hold another person's address unverified at an affected provider can sign in and be linked to the local account that owns it, and gets a session for that account. Accounts created this way are marked verified. A profile mismatch can attach a different provider identity than the one the ID token names. On Zoom, closed registration and email verification requirements did not apply.\n\n## Patches\n\nUpgrade `better-auth` to 1.7.16, or to 1.8.0-beta.5 on the beta line.\n\nAn email now counts as verified only when the claim is `true` or the string `\"true\"`, including values returned from `mapProfileToUser`.\n\n## Workarounds\n\nFor a provider that sends string claims, provide a `getUserInfo` function that converts `email_verified` to a boolean. For Zoom, turn off the provider if you rely on the ignored options.\n\n## Technical details\n\nThe linking code tested `emailVerified` for truthiness when deciding whether an untrusted provider may link to an existing user and when storing the value for a new user. It now normalizes the claim at those points. Generic OAuth providers with a `jwks_uri` now reject a profile whose `sub` differs from the ID token, and ID-token sign-in ignores submitted access and refresh tokens. Direct ID-token sign-in already applied `disableSignUp` from 1.7.7. This release applies the Zoom provider's options the same way.","summary":"better-auth treated any non-empty email_verified claim as verified, so the string \"false\" passed, and some OAuth providers and Zoom options were not enforced. An attacker who controls an unverified address at an affected provider can sign in and be linked to the local account that owns that address.","severity":"medium","cvss":5.9,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"better-auth","fixed":["1.7.16"],"affected":">= 0.4.9, < 1.7.16","ecosystem":"npm"},{"name":"better-auth","fixed":["1.8.0-beta.5"],"affected":">= 1.8.0-beta.0, < 1.8.0-beta.5","ecosystem":"npm"}],"ecosystems":["npm"],"tags":["authentication","better-auth","cwe-1287","cwe-287","cwe-290","node","oauth","package"],"not_affected_if":["email-password-only","trusted-providers-listed"],"how_to_check":"- Check the installed version with `npm ls better-auth`; affected if below `1.7.16` on the stable line, or between `1.8.0-beta.0` and `1.8.0-beta.4` on the beta line.\n- Check whether you use OAuth sign-in from a provider that sends `email_verified` as a string (Amazon Cognito user info is one example).\n- Check whether you use `genericOAuth()` with a provider that has a JWKS endpoint.\n- Check whether you use the Zoom provider with `disableSignUp`, `requireEmailVerification`, or `overrideUserInfoOnSignIn`.\n- Check whether the affected providers are listed in `account.accountLinking.trustedProviders`; those are not affected.","fix":"- `better-auth` `1.7.16` (stable line)\n- `better-auth` `1.8.0-beta.5` (beta line)","status":"open","published_at":"2026-11-03T21:39:17+00:00","first_seen_at":"2026-11-09T22:53:20+00:00","triaged_at":"2026-11-09T22:56:12+00:00","updated_at":"2026-11-09T22:56:12+00:00","category":"package","confidence":0.8,"triage":{"at":"2026-11-09T22:56:12Z","model":"claude-haiku-5-5","output":{"fix":"- `better-auth` `1.7.16` (stable line)\n- `better-auth` `1.8.0-beta.5` (beta line)","reach":"internet","title":"better-auth: unverified OAuth email can count as verified, enabling account takeover","impact":"high","summary":"better-auth treated any non-empty email_verified claim as verified, so the string \"false\" passed, and some OAuth providers and Zoom options were not enforced. An attacker who controls an unverified address at an affected provider can sign in and be linked to the local account that owns that address.","audience":"developers","category":"package","nicknames":[],"who_cares":["better-auth","node","oauth","authentication"],"confidence":0.8,"fix_status":"available","needs_auth":"no","worst_case":"attacker signs in as another user and gets a session","analyst_note":"","how_to_check":"- Check the installed version with `npm ls better-auth`; affected if below `1.7.16` on the stable line, or between `1.8.0-beta.0` and `1.8.0-beta.4` on the beta line.\n- Check whether you use OAuth sign-in from a provider that sends `email_verified` as a string (Amazon Cognito user info is one example).\n- Check whether you use `genericOAuth()` with a provider that has a JWKS endpoint.\n- Check whether you use the Zoom provider with `disableSignUp`, `requireEmailVerification`, or `overrideUserInfoOnSignIn`.\n- Check whether the affected providers are listed in `account.accountLinking.trustedProviders`; those are not affected.","plain_action":"","product_name":"","needs_analyst":false,"not_affected_if":["email-password-only","trusted-providers-listed"],"who_is_affected":"Applications that use better-auth for OAuth sign-in and link provider logins to existing local accounts by email. Exposure depends on whether the identity provider sends string email_verified values, whether genericOAuth() is used with a JWKS provider, or whether the Zoom provider is used with the affected options. Apps that only use email and password, or that list the relevant providers in trustedProviders, are not affected.","user_interaction":"yes","product_ecosystem":"","needs_special_setup":"yes","severity_if_unknown":"unknown"},"cost_usd":0.000918,"input_tokens":5391,"output_tokens":757,"severity_before":"medium"},"analyst_status":null,"analyst_note":null,"search":"'-1287':37B '-287':39B '-290':41B '0.4.9':115C '1.7.16':326C '1.7.7':468C '1.8.0':329C '9hm7':18A,23A 'access':240C,453C 'account':93B,275C,285C,286C 'account.accountlinking.trustedproviders':178C 'address':79B,97B,139C,260C 'affect':82B,100C,103C,173C,264C 'alreadi':464C 'amazon':140C 'anoth':257C 'appli':319C,465C,471C 'attach':297C 'attack':74B 'auth':3A,28A,31A,35B,47B,114C,189C,324C 'authent':32B 'beta':333C 'beta.5':330C 'better':2A,27A,30A,34B,46B,113C,188C,323C 'better-auth':1A,26A,29A,33B,45B,112C,322C 'boolean':374C 'check':218C 'claim':55B,187C,344C,363C,422C 'close':311C 'code':392C 'cognito':141C 'configur':249C 'control':76B 'convert':369C 'could':233C 'count':338C 'creat':287C 'cwe':36B,38B,40B 'cx58':20A,25A 'decid':398C 'detail':389C 'differ':299C,439C 'direct':226C,457C 'disablesignup':166C,466C 'email':13A,53B,127C,182C,195C,314C,336C,370C 'emailverifi':394C 'empti':52B,194C 'endpoint':145C,159C 'enforc':72B 'exampl':148C 'exist':407C 'fals':61B,203C 'function':367C 'generic':210C,426C 'genericoauth':151C 'get':280C 'getuserinfo':366C 'ghsa':17A,22A 'ghsa-9hm7-xhfp-cx58':16A,21A 'hold':256C 'id':221C,228C,306C,442C,446C,459C 'id-token':227C,445C,458C 'ident':301C 'ignor':247C,386C,451C 'impact':252C 'includ':351C 'info':144C,214C 'intend':184C 'jwks':158C,431C 'keep':136C 'later':117C 'let':133C 'line':334C 'link':89B,180C,271C,391C,404C 'list':176C 'load':234C 'local':92B,274C 'mapprofiletous':355C 'mark':291C 'may':403C 'mismatch':208C,295C 'name':308C 'new':416C 'node':42B 'non':51B,193C 'non-empti':50B,192C 'normal':420C 'oauth':4A,43B,65B,107C,211C,427C 'one':147C,304C 'option':69B,243C,251C,387C,476C 'overrideuserinfoonsignin':169C 'own':95B,277C 'packag':44B 'pass':62B,204C 'patch':320C 'person':258C 'point':425C 'profil':205C,215C,236C,294C,436C 'provid':12A,66B,83B,125C,154C,164C,175C,246C,250C,265C,300C,359C,364C,380C,402C,428C,474C 'refresh':455C 'registr':312C 'reject':434C 'releas':470C 'reli':383C 'requir':316C 'requireemailverif':167C 'return':353C 'send':126C,361C 'session':282C 'sign':6A,85B,109C,231C,267C,449C,462C 'sign-in':5A,108C,230C,448C,461C 'someon':253C 'store':411C 'string':60B,131C,186C,202C,349C,362C 'sub':438C 'subject':224C 'submit':239C,452C 'summari':185C 'technic':388C 'test':393C 'token':207C,222C,229C,241C,307C,443C,447C,456C,460C 'treat':9A,48B,190C 'true':123C,346C,350C 'truthi':396C 'turn':377C 'untrust':401C 'unverifi':11A,78B,138C,261C 'upgrad':321C 'uri':432C 'use':106C,150C,161C 'user':135C,143C,213C,408C,417C 'valu':197C,352C,413C 'verif':315C 'verifi':15A,54B,57B,128C,196C,199C,292C,340C,371C 'way':289C,479C 'whether':399C 'whose':437C 'workaround':356C 'xhfp':19A,24A 'zoom':68B,163C,242C,245C,310C,376C,473C","nicknames":[],"mitigations":[],"who_affected":"Applications that use better-auth for OAuth sign-in and link provider logins to existing local accounts by email. Exposure depends on whether the identity provider sends string email_verified values, whether genericOAuth() is used with a JWKS provider, or whether the Zoom provider is used with the affected options. Apps that only use email and password, or that list the relevant providers in trustedProviders, are not affected.","tier":"C","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"developers","plain_action":null,"pkeys":["betterauth"],"ptokens":["auth","better"],"labels":["authentication","betterauth","cwe1287","cwe287","cwe290","medium","node","npm","oauth","package"],"campaign_type":null,"search_a":"'9hm7':18A,23A 'auth':3A,28A,31A 'better':2A,27A,30A 'better-auth':1A,26A,29A 'cx58':20A,25A 'email':13A 'ghsa':17A,22A 'ghsa-9hm7-xhfp-cx58':16A,21A 'oauth':4A 'provid':12A 'sign':6A 'sign-in':5A 'treat':9A 'unverifi':11A 'verifi':15A 'xhfp':19A,24A","search_b":"'-1287':37B '-287':39B '-290':41B 'account':93B 'address':79B,97B 'affect':82B 'attack':74B 'auth':35B,47B 'authent':32B 'better':34B,46B 'better-auth':33B,45B 'claim':55B 'control':76B 'cwe':36B,38B,40B 'email':53B 'empti':52B 'enforc':72B 'fals':61B 'link':89B 'local':92B 'node':42B 'non':51B 'non-empti':50B 'oauth':43B,65B 'option':69B 'own':95B 'packag':44B 'pass':62B 'provid':66B,83B 'sign':85B 'string':60B 'treat':48B 'unverifi':78B 'verifi':54B,57B 'zoom':68B","search_c":"'0.4.9':115C '1.7.16':326C '1.7.7':468C '1.8.0':329C 'access':240C,453C 'account':275C,285C,286C 'account.accountlinking.trustedproviders':178C 'address':139C,260C 'affect':100C,103C,173C,264C 'alreadi':464C 'amazon':140C 'anoth':257C 'appli':319C,465C,471C 'attach':297C 'auth':114C,189C,324C 'beta':333C 'beta.5':330C 'better':113C,188C,323C 'better-auth':112C,322C 'boolean':374C 'check':218C 'claim':187C,344C,363C,422C 'close':311C 'code':392C 'cognito':141C 'configur':249C 'convert':369C 'could':233C 'count':338C 'creat':287C 'decid':398C 'detail':389C 'differ':299C,439C 'direct':226C,457C 'disablesignup':166C,466C 'email':127C,182C,195C,314C,336C,370C 'emailverifi':394C 'empti':194C 'endpoint':145C,159C 'exampl':148C 'exist':407C 'fals':203C 'function':367C 'generic':210C,426C 'genericoauth':151C 'get':280C 'getuserinfo':366C 'hold':256C 'id':221C,228C,306C,442C,446C,459C 'id-token':227C,445C,458C 'ident':301C 'ignor':247C,386C,451C 'impact':252C 'includ':351C 'info':144C,214C 'intend':184C 'jwks':158C,431C 'keep':136C 'later':117C 'let':133C 'line':334C 'link':180C,271C,391C,404C 'list':176C 'load':234C 'local':274C 'mapprofiletous':355C 'mark':291C 'may':403C 'mismatch':208C,295C 'name':308C 'new':416C 'non':193C 'non-empti':192C 'normal':420C 'oauth':107C,211C,427C 'one':147C,304C 'option':243C,251C,387C,476C 'overrideuserinfoonsignin':169C 'own':277C 'pass':204C 'patch':320C 'person':258C 'point':425C 'profil':205C,215C,236C,294C,436C 'provid':125C,154C,164C,175C,246C,250C,265C,300C,359C,364C,380C,402C,428C,474C 'refresh':455C 'registr':312C 'reject':434C 'releas':470C 'reli':383C 'requir':316C 'requireemailverif':167C 'return':353C 'send':126C,361C 'session':282C 'sign':109C,231C,267C,449C,462C 'sign-in':108C,230C,448C,461C 'someon':253C 'store':411C 'string':131C,186C,202C,349C,362C 'sub':438C 'subject':224C 'submit':239C,452C 'summari':185C 'technic':388C 'test':393C 'token':207C,222C,229C,241C,307C,443C,447C,456C,460C 'treat':190C 'true':123C,346C,350C 'truthi':396C 'turn':377C 'untrust':401C 'unverifi':138C,261C 'upgrad':321C 'uri':432C 'use':106C,150C,161C 'user':135C,143C,213C,408C,417C 'valu':197C,352C,413C 'verif':315C 'verifi':128C,196C,199C,292C,340C,371C 'way':289C,479C 'whether':399C 'whose':437C 'workaround':356C 'zoom':163C,242C,245C,310C,376C,473C","cause_entry":null,"cause_path":null,"disclosures_checked_at":null,"first_public_at":"2026-11-03T21:39:17+00:00","kev":null,"cvss_vector":null,"cvss_by":null,"poc_count":0,"sources":[{"source":"ghsa-repo","external_id":"GHSA-9hm7-xhfp-cx58","url":"https://github.com/better-auth/better-auth/security/advisories/GHSA-9hm7-xhfp-cx58","severity":"medium","published_at":"2026-11-03T21:39:17+00:00","modified_at":"2026-11-03T21:39:17+00:00","first_seen_at":"2026-11-09T22:53:20+00:00","fetched_at":"2026-11-09T22:53:20+00:00","note":null}],"documents":[{"id":110503,"url":"https://github.com/better-auth/better-auth/security/advisories/GHSA-9hm7-xhfp-cx58","title":"Maintainer's advisory: github.com","kind":"vendor","source":"ghsa-repo","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: github.com"}],"disclosures":[],"exploit_sources":[],"notice":"Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai"}