Budibase's OIDC SSO login links an incoming SSO identity to an existing Budibase account by email address alone, without ever checking the email_verified claim of the OIDC ID token. Budibase first tries to match the IdP sub; when that misses (any fresh attacker IdP account) it silently falls back to matching by the email claim and merges into the existing account by email, preserving that account's _id and roles. Because the email_verified flag is never read, an attacker who can make a configured/trusted IdP emit a token carrying email = <victim> with email_verified = false is logged into Budibase as the victim, inheriting the victim's roles (including global admin/builder). Per OIDC Core §5.7 the email claim MUST NOT be used as an identity key unless email_verified is true; Budibase effectively delegates all account-linking trust to every configured IdP's email-verification policy while checking nothing itself. Full account takeover of any existing Budibase user, including the instance owner.
The OIDC verify callback extracts the email and never consults email_verified:
packages/backend-core/src/middleware/passport/sso/oidc.ts:59 — email: getEmail(profile, jwtClaims).getEmail (oidc.ts:113-135) returns profile._json.email ->jwtClaims.email -> preferred_username. No email_verified check.buildJwtClaims (oidc.ts:99-107) assembles claims from _json.email/emails[0].value — no verification flag is read. grep -r email_verified packages/ -> 0 hits.The email is then used as the account-linking key:
sso.authenticate(...) -> packages/backend-core/src/middleware/passport/sso/sso.ts:
:38,44 users.getById(generateGlobalUserID(details.userId)) keyed on the IdP sub; for a fresh attacker IdP account this 404s and is swallowed (:45-54).:57-59 fallback: dbUser = await users.getGlobalUserByEmail(details.email) -> loads the victim's account...Exploitability
AV:NAC:LAT:PPR:LUI:NVulnerable System
VC:HVI:HVA:HSubsequent System
SC:HSI:HSA:H9.0/CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:HAuthentication