Apr 20, 2026·7 min read·2 visits
A logic flaw in Nhost's OAuth implementation allows attackers to take over existing accounts by registering an unverified matching email address on third-party identity providers like Discord or Bitbucket.
Nhost is vulnerable to a critical Improper Authentication flaw (CWE-287) that permits full account takeover. The vulnerability exists in the OAuth authentication flow, where multiple provider adapters fail to enforce email verification checks before automatically linking incoming external identities to existing local accounts.
Nhost provides an open-source backend utilizing Go for its authentication services. The platform supports federated authentication, allowing users to sign in via third-party OAuth providers such as Discord, Bitbucket, AzureAD, and EntraID. A core feature of this authentication subsystem is automated account linking. When an incoming OAuth identity presents an email address that matches an existing local user record, Nhost automatically merges the profiles and provisions an authenticated session.
This architecture fundamentally relies on the assumption that external identity providers rigorously verify email ownership before returning the profile payload to the application. However, multiple OAuth adapters within the Nhost codebase failed to validate the verification status flag provided by the external services. This oversight introduces a severe CWE-287 (Improper Authentication) vulnerability into the sign-in flow.
Because the verification flag is ignored, an attacker can register an account on a supported third-party service using a target victim's email address. The attacker does not need to complete the email confirmation process on the external platform. When the attacker initiates an OAuth login flow into the target Nhost application, the system ingests the unverified email, executes the automated account linking procedure, and grants the attacker a valid session belonging to the victim.
The underlying flaw exists at the intersection of adapter-level data parsing and controller-level business logic. The providerFlowSignIn function located in services/auth/go/controller/sign_in_id_token.go is responsible for handling incoming identity profiles. This function executed the InsertUserProvider method to link accounts based exclusively on an email match. The logic did not evaluate the profile.EmailVerified boolean before proceeding with the linking and session generation operations.
Compounding this controller-level deficiency, several individual provider adapters improperly managed the verification state during the initial data ingestion phase. The Discord and EntraID adapters mapped incoming JSON payloads into internal structs that omitted the verified and email_verified fields entirely. Due to these omissions, the Go JSON decoder silently discarded the true verification state broadcast by the provider API. The adapters subsequently defaulted the EmailVerified property to true whenever an email string was present in the payload.
Other adapters exhibited distinct but equally problematic behaviors. The Bitbucket adapter executed a fallback mechanism that accepted unconfirmed email addresses if no confirmed addresses were available in the user profile, explicitly marking these unconfirmed addresses as verified. The AzureAD adapter utilized unverified fields, such as preferred_username or User Principal Name (UPN), as the primary email source when the standard email field was empty. The combination of these adapter inaccuracies and the controller's unconditional acceptance logic enabled the authentication bypass.
The pre-patch implementation of providerFlowSignIn bypassed necessary authorization checks during the OAuth callback phase. The controller unconditionally registered the provider linkage and issued a new session. The vulnerable implementation is shown below:
func (ctrl *Controller) providerFlowSignIn(
ctx context.Context,
user sql.AuthUser,
providerFound bool,
provider string,
providerUserID string,
logger *slog.Logger,
) (*api.Session, *APIError) {
if !providerFound {
// profile.EmailVerified is NEVER checked prior to this insertion
ctrl.wf.InsertUserProvider(ctx, user.ID, provider, providerUserID, logger)
}
session, _ := ctrl.wf.NewSession(ctx, user, nil, logger)
return session, nil
}The fix, introduced in commit ec8dab3f2cf46e1131ddaf893d56c37aa00380b2, addresses both the type system and the control flow. The developers replaced the binary EmailVerified bool with a strict EmailVerificationStatus enumeration (Unknown, Verified, Unverified). This structural change forces adapters to explicitly declare the verification state rather than relying on boolean zero-value defaults.
Additionally, the patch introduced the ensureProviderLinkAllowed guard function. This routine is now invoked before any database operations occur. If the provider profile does not explicitly carry a verified status, the application aborts the authentication flow:
func (ctrl *Controller) ensureProviderLinkAllowed(
ctx context.Context,
profile oidc.Profile,
provider string,
logger *slog.Logger,
) *APIError {
if profile.EmailVerified.IsVerified() {
return nil
}
logger.WarnContext(ctx, "refusing to link provider: email not verified by provider")
return ErrUnverifiedUser
}This fix is robust because it centralizes the security enforcement within the main controller while simultaneously correcting the data extraction logic inside the specific provider adapters.
Exploiting this vulnerability requires zero interaction from the target victim and necessitates only public knowledge of the victim's registered email address. The attacker first selects a vulnerable OAuth provider supported by the target Nhost application, such as Discord. The attacker creates a new account on this third-party provider or modifies an existing account, updating the profile email to match the target victim's email address.
Crucially, the external provider will dispatch an email confirmation link to the victim's inbox. The attacker ignores this step and leaves the email address unverified within the provider's ecosystem. The attacker then accesses the target Nhost application and initiates the "Sign in with Discord" OAuth flow.
The Nhost application receives the profile payload from Discord. Due to the missing struct field in the Discord adapter, Nhost incorrectly assumes the provided email string is verified. The controller queries the local database, identifies the victim's account via the matching email, and commits the OAuth provider identity to the victim's user record. Nhost then issues a JSON Web Token (JWT) or session cookie to the attacker, providing them with full, unauthenticated access to the victim's data and privileges.
The impact of this vulnerability is critical, warranting a CVSS v4.0 score of 9.3. Successful exploitation results in complete account takeover. An attacker assumes the identical authorization context as the victim, obtaining full read and write access to all resources owned by that user.
If the victim possesses administrative privileges within the Nhost-backed application, the attacker inherits those rights. This facilitates extensive lateral movement, data exfiltration, and potential compromise of the entire application environment. The exploit is executed entirely over the network with low attack complexity, requiring no specialized tools or prerequisite authentication state.
The vulnerability is highly reliable because the account linking process is deterministic. The flaw's existence across multiple popular OAuth adapters (Discord, AzureAD, EntraID, Bitbucket) broadens the attack surface, allowing threat actors multiple avenues of exploitation depending on which providers the application administrator has enabled.
Organizations deploying Nhost must upgrade their installations to a patched version immediately. The vulnerability is resolved in commit ec8dab3f2cf46e1131ddaf893d56c37aa00380b2. For teams building from source, this corresponds to a pseudo-version post-dating April 17, 2026. Applying this update introduces the centralized ensureProviderLinkAllowed guard and the updated provider adapters.
For environments where immediate patching is strictly impossible, administrators should disable the vulnerable OAuth adapters (Discord, Bitbucket, AzureAD, EntraID) via the application configuration. Restricting authentication strictly to username/password mechanisms or unaffected OAuth providers will close the attack vector. This mitigation degrades functionality but secures existing user accounts.
Development teams managing custom OAuth adapters for Nhost must perform immediate code audits. Custom implementations must be verified to explicitly extract and evaluate the verified or email_verified parameters from the upstream identity provider's API response. Relying solely on the presence of an email attribute is insufficient and will reintroduce the vulnerability.
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| Product | Affected Versions | Fixed Version |
|---|---|---|
github.com/nhost/nhost Nhost | < 0.0.0-20260417112436-ec8dab3f2cf4 | 0.0.0-20260417112436-ec8dab3f2cf4 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-287 |
| Attack Vector | Network |
| CVSS v4.0 | 9.3 |
| Impact | Complete Account Takeover |
| Exploit Status | Proof of Concept |
| Authentication Required | None |
Improper Authentication: Software does not prove or insufficiently proves that a claim to be a correct identity is valid.