Skip to content

EEF-CVE-2026-91039 CVE-2026-91039 GHSA-73j9-m294-fvv9

dynamic_oidc identities are not namespaced by connection in ash_authentication, allowing cross-connection account takeover

Not rated yet Hex
Severity
0.0 / 10
Unknown · CVSS 4.0
Exploit chance (EPSS)
0.66%
Chance of exploitation in the next 30 days. Higher than 51.71% of scored vulnerabilities.
Known exploited
No
Not in CISA's catalog
Weakness
CWE-290 Authentication Bypass by Spoofing
Fix
5.0.0-rc.14
Upgrade ash_authentication to this version or later

Description

Summary

Authentication Bypass by Spoofing vulnerability in team-alembic ash_authentication allows an attacker who operates one identity-provider connection of a dynamic_oidc strategy to be signed in as a local user established through a different connection.

The strategy is meant to keep each connection in its own identity namespace by writing every UserIdentity row's strategy field as "<name>/<connection_id>", but that namespacing never takes effect. __connection_id__ is populated only on the ephemeral runtime struct built per request in dynamic_oidc/plug.ex, and DynamicOidc.IdentityChange.change/3 re-fetches the strategy from the compile-time DSL through Info.strategy_for_action, yielding the persisted struct whose __connection_id__ is its defstruct default of nil. OAuth2.identity_strategy_name/1 therefore falls back to the bare strategy name for both the identity write and the reads in oauth2/user_resolver.ex and oauth2/sign_in_preparation.ex. Since the identity resource's unique key is (uid, strategy), one row exists per sub across every connection, and the identity-match branch runs before any email check. Neither strategy handles iss, so nothing else distinguishes the issuers: OpenID Connect Core section 5.7 makes sub unique only within an issuer, so two connections numbering subjects independently share one subject space.

This issue affects ash_authentication: from 5.0.0-rc.10 before 5.0.0-rc.14.

Impact

An attacker who can obtain or choose their sub on any connection served by a dynamic_oidc strategy is authenticated as the local user established through a different connection, with no interaction from the victim and no pre-existing account on the application. Token, nonce and audience validation all pass legitimately, because they only prove the assertion came from the attacker's own identity provider.

The same collision also occurs with no attacker at all: two honest providers numbering subjects sequentially produce overlapping sub values and silently merge distinct users, so this may already have happened in affected deployments.

Configurations

Only deployments running a dynamic_oidc strategy with more than one identity-provider connection are exposed, since a single connection has no second namespace to collide with.

Two partial mitigations exist and neither is sufficient. A confirmation add-on incidentally blocks the takeover only while the victim is unconfirmed, leaving confirmed victims and confirmation-free applications exposed. Ash multitenancy isolates only a one-connection-per-tenant topology, so non-multitenant deployments and tenants holding several connections get no isolation; connection namespacing is orthogonal to tenancy.

Solutions

Upgrading is not sufficient on its own, for two reasons.

Namespacing changes the value the identity lookup keys on, so UserIdentity rows written before the fix, all holding the bare strategy name, no longer match. Each row's strategy must be relinked in place to "<name>/<connection_id>". Deleting the rows does not work: the block is the account match that follows, refused under the default on_untrusted_email_match :reject whether the row exists or not, and deleting also discards the stored refresh token. Where a deployment has one connection the mapping is unambiguous and the update is mechanical, but it must run in the same deployment as the upgrade and before users sign in, or a newly written namespaced row can collide with a surviving bare row on the (uid, strategy) index.

The fix also cannot separate accounts already merged, and the merge is not detectable from the database, because a collision never produced two rows to compare: the second user's identity was never created and the surviving row looks legitimate. Deployments running more than one connection must audit out of band, by exporting each connection's set of sub values from its identity provider and intersecting them. Any sub present in more than one set identifies an account that may have been merged.

Affected versions

Hex

PackageVulnerableFixed in
ash_authentication
>= 5.0.0-rc.10, < 5.0.0-rc.14
5.0.0-rc.14

How to fix

Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.

ash_authentication Hex → 5.0.0-rc.14

mix
{:ash_authentication, "~> 5.0.0-rc.14"} # then: mix deps.update ash_authentication

Severity breakdown

CVSS 4.0 0.0

CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N

Attack vector
Network
Attack complexity
Low
Attack requirements
Present
Privileges required
None
User interaction
None
Confidentiality (vulnerable system)
High
Integrity (vulnerable system)
High
Availability (vulnerable system)
None
Confidentiality (subsequent systems)
None
Integrity (subsequent systems)
None
Availability (subsequent systems)
None

Weakness

CWE-290: Authentication Bypass by Spoofing

This attack-focused weakness is caused by incorrectly implemented authentication schemes that are subject to spoofing attacks.

References

Timeline

  1. Published
  2. Last updated by the source
  3. Latest EPSS score
AdvisorySeverityWhy it's related
EEF-CVE-2026-86688
Session id is not renewed on authentication in ash_authentication, allowing session fixation
Not rated yet Also affects ash_authentication
EEF-CVE-2026-76949
Remember-me sign-in guard reads a session key that is never written in ash_authentication, allowing session replacement
Not rated yet Also affects ash_authentication
EEF-CVE-2026-88952
OAuth2 sign-in attached to an existing account without an email comparison in AshAuthentication
Not rated yet Also affects ash_authentication
EEF-CVE-2026-85500
`require_confirmed_with` is not enforced on the action and fails open on an unreadable attribute in AshAuthentication
Not rated yet Also affects ash_authentication
EEF-CVE-2026-86533
Revoked session accepted because the session jti is never checked in AshAuthentication and AshAuthentication Phoenix
Not rated yet Also affects ash_authentication
EEF-CVE-2026-81632
Single-use sign-in token placed in a redirect query string in AshAuthenticationPhoenix
Not rated yet Also affects ash_authentication
EEF-CVE-2026-80218
Sign-in token minted for one resource accepted by another in AshAuthentication
Not rated yet Also affects ash_authentication
EEF-CVE-2026-78223
Token revocation record built from unverified JWT claims in AshAuthentication
Not rated yet Also affects ash_authentication

Credits: Jace, James Harton, Jonatan Männchen / EEF, manus-pi

Is this in your code?

MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.

Check your repos with MergeHarbor