EEF-CVE-2026-91039
dynamic_oidc identities are not namespaced by connection in ash_authentication, allowing cross-connection account takeover
Quick fix
EEF-CVE-2026-91039 — ash_authentication: upgrade to the fixed version with the command below.
mix deps.update ash_authenticationDetails
## 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.
Are you affected?
Enter the version of the package you're using.
Affected packages
5.0.0-rc.10Fixed in: 5.0.0-rc.14mix deps.update ash_authenticationReferences
- https://github.com/team-alembic/ash_authentication/security/advisories/GHSA-73j9-m294-fvv9[ADVISORY]
- https://cna.erlef.org/cves/CVE-2026-91039.html[WEB]
- https://github.com/team-alembic/ash_authentication/commit/64530644f9b37ebb76ca14aeb83a77597a0034b7[WEB]
- https://github.com/team-alembic/ash_authentication/commit/73ad16e452670bbf843550a13361bd41e72ad964[FIX]
- https://hex.pm/packages/ash_authentication[PACKAGE]