Nhost automatically links an incoming OAuth identity to an existing Nhost account when the email addresses match. This is only safe when the email has been verified by the OAuth provider. Nhost's controller trusts a profile.EmailVerified boolean that is set by each provider adapter.
The vulnerability is that several provider adapters do not correctly populate this field they either silently drop a verified field the provider API actually returns (Discord), or they fall back to accepting unconfirmed emails and marking them as verified (Bitbucket). Two Microsoft providers (AzureAD, EntraID) derive the email from non-ownership-proving fields like the user principal name, then mark it verified.
The result is that an attacker can present an email they don't own to Nhost, have the OAuth identity merged into the victim's account, and receive a full authenticated session.
In services/auth/go/controller/sign_in_id_token.go, providerFlowSignIn() links a new provider identity to an existing account by email match with no verification guard:
// sign_in_id_token.go:267-296
func (ctrl *Controller) providerFlowSignIn(
ctx context.Context,
user sql.AuthUser,
providerFound bool,
provider string,
providerUserID string,
logger *slog.Logger,
) (*api.Session, *APIError) {
if !providerFound {
// Links attacker's provider identity to the victim's account.
// profile.EmailVerified is NEVER checked here.
ctrl.wf.InsertUserProvider(ctx, user.ID, provider, providerUserID, logger)
}
// Issues a full session to the attacker.
session, _ := ctrl.wf.NewSession(ctx, user, nil, logger)
return session, nil
}
The controller places full trust in whatever profile.EmailVerified the adapter returned. The vulnerabilities below show how that trust is violated.
providers/github.goGitHub fetches /user/emails and reads...
0.0.0-20260417112436-ec8dab3f2cf4Exploitability
AV:NAC:LAT:NPR:NUI:NVulnerable System
VC:HVI:HVA:NSubsequent System
SC:NSI:NSA:N9.3/CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N