GHSA-73j9-m294-fvv9 — ash_authentication
Fix: team-alembic/ash_authentication@73ad16eGHSA-73j9-m294-fvv9 is a CWE-290 vulnerability in ash_authentication. A fix is available for ash_authentication — see the affected versions and patch details below.
dynamic_oidc identities are not namespaced by connection in ash_authentication, allowing cross-connection account takeover
Exploitation Status
No confirmed exploitation observed yet
- A successful exploit gives an attacker total control of the affected component, not partial access.
- CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.
Exploitation and automatability from CISA’s SSVC triage for GHSA-73j9-m294-fvv9.
Real-World Exposure
ash_authenticationReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects Hex packages — download data is not available via public APIs for these ecosystems.
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 Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 💧Hex | ash_authentication | ≥ 5.0.0-rc.10&&< 5.0.0-rc.14 | 5.0.0-rc.14mix deps.update ash_authentication |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for ash_authentication, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update ash_authentication to 5.0.0-rc.14 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-73j9-m294-fvv9 is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like GHSA-73j9-m294-fvv9 can be triaged on real exposure rather than presence alone.
Tailored to GHSA-73j9-m294-fvv9. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is GHSA-73j9-m294-fvv9 in your dependencies?
O3 Security finds GHSA-73j9-m294-fvv9 across Hex dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.