CVE-2026-91039 (GCVE-0-2026-91039)
Vulnerability from cvelistv5 – Published: 2026-09-17 15:19 – Updated: 2026-09-17 19:40
VLAI
EPSS
VEX
Title
dynamic_oidc identities are not namespaced by connection in ash_authentication, allowing cross-connection account takeover
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.
Severity
SSVC
Exploitation: none
Automatable: no
Technical Impact: total
CISA Coordinator · CISA-ADP (v2.0.3)
Decision recorded 2026-09-17 19:40 UTC
CWE
- CWE-290 - Authentication Bypass by Spoofing
Assigner
References
5 references
| URL | Tags |
|---|---|
| https://github.com/team-alembic/ash_authenticatio… | relatedvendor-advisory |
| https://cna.erlef.org/cves/CVE-2026-91039.html | related |
| https://osv.dev/vulnerability/EEF-CVE-2026-91039 | related |
| https://github.com/team-alembic/ash_authenticatio… | related |
| https://github.com/team-alembic/ash_authenticatio… | patch |
Impacted products
2 products
| Vendor | Product | Version | CPE status | |
|---|---|---|---|---|
| team-alembic | ash_authentication |
Affected:
5.0.0-rc.10 , < 5.0.0-rc.14
(semver)
cpe:2.3:a:team-alembic:ash_authentication:*:*:*:*:*:*:*:* |
||
| team-alembic | ash_authentication |
Affected:
64530644f9b37ebb76ca14aeb83a77597a0034b7 , < 73ad16e452670bbf843550a13361bd41e72ad964
(git)
cpe:2.3:a:team-alembic:ash_authentication:*:*:*:*:*:*:*:* |
{
"containers": {
"adp": [
{
"metrics": [
{
"other": {
"content": {
"id": "CVE-2026-91039",
"options": [
{
"Exploitation": "none"
},
{
"Automatable": "no"
},
{
"Technical Impact": "total"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-09-17T19:40:12.687953Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-09-17T19:40:32.068Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"collectionURL": "https://repo.hex.pm",
"cpes": [
"cpe:2.3:a:team-alembic:ash_authentication:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"packageName": "ash_authentication",
"packageURL": "pkg:hex/ash_authentication",
"product": "ash_authentication",
"repo": "https://github.com/team-alembic/ash_authentication",
"vendor": "team-alembic",
"versions": [
{
"lessThan": "5.0.0-rc.14",
"status": "affected",
"version": "5.0.0-rc.10",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:team-alembic:ash_authentication:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"packageName": "team-alembic/ash_authentication",
"packageURL": "pkg:github/team-alembic/ash_authentication",
"product": "ash_authentication",
"repo": "https://github.com/team-alembic/ash_authentication",
"vendor": "team-alembic",
"versions": [
{
"lessThan": "73ad16e452670bbf843550a13361bd41e72ad964",
"status": "affected",
"version": "64530644f9b37ebb76ca14aeb83a77597a0034b7",
"versionType": "git"
}
]
}
],
"configurations": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eOnly deployments running a \u003ccode\u003edynamic_oidc\u003c/code\u003e strategy with more than one identity-provider connection are exposed, since a single connection has no second namespace to collide with.\u003c/p\u003e\n\u003cp\u003eTwo partial mitigations exist and neither is sufficient. A \u003ccode\u003econfirmation\u003c/code\u003e 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.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "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.\n\nTwo 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."
}
],
"value": "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.\n\nTwo 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."
}
],
"cpeApplicability": [
{
"nodes": [
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:team-alembic:ash_authentication:*:*:*:*:*:*:*:*",
"versionEndExcluding": "5.0.0-rc.14",
"versionStartIncluding": "5.0.0-rc.10",
"vulnerable": true
}
],
"negate": false,
"operator": "OR"
}
],
"operator": "AND"
}
],
"credits": [
{
"lang": "en",
"type": "reporter",
"value": "Jace"
},
{
"lang": "en",
"type": "coordinator",
"value": "Jonatan M\u00e4nnchen / EEF"
},
{
"lang": "en",
"type": "reporter",
"value": "manus-pi"
},
{
"lang": "en",
"type": "remediation developer",
"value": "James Harton"
}
],
"dateAssigned": "2026-09-17T05:25:04.000Z",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eAuthentication Bypass by Spoofing vulnerability in team-alembic ash_authentication allows an attacker who operates one identity-provider connection of a \u003ccode\u003edynamic_oidc\u003c/code\u003e strategy to be signed in as a local user established through a different connection.\u003c/p\u003e\n\u003cp\u003eThe strategy is meant to keep each connection in its own identity namespace by writing every \u003ccode\u003eUserIdentity\u003c/code\u003e row\u0027s \u003ccode\u003estrategy\u003c/code\u003e field as \u003ccode\u003e\"\u0026lt;name\u0026gt;/\u0026lt;connection_id\u0026gt;\"\u003c/code\u003e, but that namespacing never takes effect. \u003ccode\u003e__connection_id__\u003c/code\u003e is populated only on the ephemeral runtime struct built per request in \u003ccode\u003edynamic_oidc/plug.ex\u003c/code\u003e, and \u003ccode\u003eDynamicOidc.IdentityChange.change/3\u003c/code\u003e re-fetches the strategy from the compile-time DSL through \u003ccode\u003eInfo.strategy_for_action\u003c/code\u003e, yielding the persisted struct whose \u003ccode\u003e__connection_id__\u003c/code\u003e is its \u003ccode\u003edefstruct\u003c/code\u003e default of \u003ccode\u003enil\u003c/code\u003e. \u003ccode\u003eOAuth2.identity_strategy_name/1\u003c/code\u003e therefore falls back to the bare strategy name for both the identity write and the reads in \u003ccode\u003eoauth2/user_resolver.ex\u003c/code\u003e and \u003ccode\u003eoauth2/sign_in_preparation.ex\u003c/code\u003e. Since the identity resource\u0027s unique key is \u003ccode\u003e(uid, strategy)\u003c/code\u003e, one row exists per \u003ccode\u003esub\u003c/code\u003e across every connection, and the identity-match branch runs before any email check. Neither strategy handles \u003ccode\u003eiss\u003c/code\u003e, so nothing else distinguishes the issuers: OpenID Connect Core section 5.7 makes \u003ccode\u003esub\u003c/code\u003e unique only within an issuer, so two connections numbering subjects independently share one subject space.\u003c/p\u003e\n\u003cp\u003eThis issue affects ash_authentication: from 5.0.0-rc.10 before 5.0.0-rc.14.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "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.\n\nThe strategy is meant to keep each connection in its own identity namespace by writing every `UserIdentity` row\u0027s `strategy` field as `\"\u003cname\u003e/\u003cconnection_id\u003e\"`, 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\u0027s 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.\n\nThis issue affects ash_authentication: from 5.0.0-rc.10 before 5.0.0-rc.14."
}
],
"value": "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.\n\nThe strategy is meant to keep each connection in its own identity namespace by writing every UserIdentity row\u0027s strategy field as \"\u003cname\u003e/\u003cconnection_id\u003e\", 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\u0027s 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.\n\nThis issue affects ash_authentication: from 5.0.0-rc.10 before 5.0.0-rc.14."
}
],
"impacts": [
{
"capecId": "CAPEC-21",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eAn attacker who can obtain or choose their \u003ccode\u003esub\u003c/code\u003e on any connection served by a \u003ccode\u003edynamic_oidc\u003c/code\u003e 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\u0027s own identity provider.\u003c/p\u003e\n\u003cp\u003eThe same collision also occurs with no attacker at all: two honest providers numbering subjects sequentially produce overlapping \u003ccode\u003esub\u003c/code\u003e values and silently merge distinct users, so this may already have happened in affected deployments.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "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\u0027s own identity provider.\n\nThe 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."
}
],
"value": "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\u0027s own identity provider.\n\nThe 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."
}
]
}
],
"metrics": [
{
"cvssV4_0": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "PRESENT",
"attackVector": "NETWORK",
"baseScore": 9.1,
"baseSeverity": "CRITICAL",
"privilegesRequired": "NONE",
"providerUrgency": "NOT_DEFINED",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "NONE",
"subIntegrityImpact": "NONE",
"userInteraction": "NONE",
"valueDensity": "NOT_DEFINED",
"vectorString": "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",
"version": "4.0",
"vulnAvailabilityImpact": "NONE",
"vulnConfidentialityImpact": "HIGH",
"vulnIntegrityImpact": "HIGH",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"format": "CVSS",
"scenarios": [
{
"lang": "en",
"value": "GENERAL"
}
]
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-290",
"description": "CWE-290 Authentication Bypass by Spoofing",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-09-17T15:19:15.994Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"name": "GitHub Advisory",
"tags": [
"related",
"vendor-advisory"
],
"url": "https://github.com/team-alembic/ash_authentication/security/advisories/GHSA-73j9-m294-fvv9"
},
{
"name": "EEF CNA record for CVE-2026-91039",
"tags": [
"related"
],
"url": "https://cna.erlef.org/cves/CVE-2026-91039.html"
},
{
"name": "OSV record EEF-CVE-2026-91039",
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-91039"
},
{
"name": "Introducing commit 6453064 in team-alembic/ash_authentication",
"tags": [
"related"
],
"url": "https://github.com/team-alembic/ash_authentication/commit/64530644f9b37ebb76ca14aeb83a77597a0034b7"
},
{
"name": "Fix commit 73ad16e in team-alembic/ash_authentication",
"tags": [
"patch"
],
"url": "https://github.com/team-alembic/ash_authentication/commit/73ad16e452670bbf843550a13361bd41e72ad964"
}
],
"solutions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eUpgrading is not sufficient on its own, for two reasons.\u003c/p\u003e\n\u003cp\u003eNamespacing changes the value the identity lookup keys on, so \u003ccode\u003eUserIdentity\u003c/code\u003e rows written before the fix, all holding the bare strategy name, no longer match. Each row\u0027s \u003ccode\u003estrategy\u003c/code\u003e must be relinked in place to \u003ccode\u003e\"\u0026lt;name\u0026gt;/\u0026lt;connection_id\u0026gt;\"\u003c/code\u003e. Deleting the rows does not work: the block is the account match that follows, refused under the default \u003ccode\u003eon_untrusted_email_match :reject\u003c/code\u003e 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 \u003ccode\u003e(uid, strategy)\u003c/code\u003e index.\u003c/p\u003e\n\u003cp\u003eThe 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\u0027s 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\u0027s set of \u003ccode\u003esub\u003c/code\u003e values from its identity provider and intersecting them. Any \u003ccode\u003esub\u003c/code\u003e present in more than one set identifies an account that may have been merged.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Upgrading is not sufficient on its own, for two reasons.\n\nNamespacing 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\u0027s `strategy` must be relinked in place to `\"\u003cname\u003e/\u003cconnection_id\u003e\"`. 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.\n\nThe 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\u0027s 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\u0027s 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."
}
],
"value": "Upgrading is not sufficient on its own, for two reasons.\n\nNamespacing 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\u0027s strategy must be relinked in place to \"\u003cname\u003e/\u003cconnection_id\u003e\". 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.\n\nThe 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\u0027s 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\u0027s 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."
}
],
"source": {
"discovery": "EXTERNAL"
},
"title": "dynamic_oidc identities are not namespaced by connection in ash_authentication, allowing cross-connection account takeover"
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-91039",
"datePublished": "2026-09-17T15:19:15.994Z",
"dateReserved": "2026-09-15T15:30:01.883Z",
"dateUpdated": "2026-09-17T19:40:32.068Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2",
"vulnerability-lookup:meta": {
"epss": {
"cve": "CVE-2026-91039",
"date": "2026-09-18",
"epss": "0.00403",
"percentile": "0.3438"
},
"nvd": {
"cve": {
"affected": [
{
"affectedData": [
{
"collectionURL": "https://repo.hex.pm",
"cpes": [
"cpe:2.3:a:team-alembic:ash_authentication:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"packageName": "ash_authentication",
"packageURL": "pkg:hex/ash_authentication",
"product": "ash_authentication",
"repo": "https://github.com/team-alembic/ash_authentication",
"vendor": "team-alembic",
"versions": [
{
"lessThan": "5.0.0-rc.14",
"status": "affected",
"version": "5.0.0-rc.10",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:team-alembic:ash_authentication:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"packageName": "team-alembic/ash_authentication",
"packageURL": "pkg:github/team-alembic/ash_authentication",
"product": "ash_authentication",
"repo": "https://github.com/team-alembic/ash_authentication",
"vendor": "team-alembic",
"versions": [
{
"lessThan": "73ad16e452670bbf843550a13361bd41e72ad964",
"status": "affected",
"version": "64530644f9b37ebb76ca14aeb83a77597a0034b7",
"versionType": "git"
}
]
}
],
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db"
}
],
"cveTags": [],
"descriptions": [
{
"lang": "en",
"value": "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.\n\nThe strategy is meant to keep each connection in its own identity namespace by writing every UserIdentity row\u0027s strategy field as \"\u003cname\u003e/\u003cconnection_id\u003e\", 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\u0027s 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.\n\nThis issue affects ash_authentication: from 5.0.0-rc.10 before 5.0.0-rc.14."
}
],
"id": "CVE-2026-91039",
"lastModified": "2026-09-17T20:18:52.740",
"metrics": {
"cvssMetricV40": [
{
"cvssData": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "PRESENT",
"attackVector": "NETWORK",
"availabilityRequirement": "NOT_DEFINED",
"baseScore": 9.1,
"baseSeverity": "CRITICAL",
"confidentialityRequirement": "NOT_DEFINED",
"exploitMaturity": "NOT_DEFINED",
"integrityRequirement": "NOT_DEFINED",
"modifiedAttackComplexity": "NOT_DEFINED",
"modifiedAttackRequirements": "NOT_DEFINED",
"modifiedAttackVector": "NOT_DEFINED",
"modifiedPrivilegesRequired": "NOT_DEFINED",
"modifiedSubAvailabilityImpact": "NOT_DEFINED",
"modifiedSubConfidentialityImpact": "NOT_DEFINED",
"modifiedSubIntegrityImpact": "NOT_DEFINED",
"modifiedUserInteraction": "NOT_DEFINED",
"modifiedVulnAvailabilityImpact": "NOT_DEFINED",
"modifiedVulnConfidentialityImpact": "NOT_DEFINED",
"modifiedVulnIntegrityImpact": "NOT_DEFINED",
"privilegesRequired": "NONE",
"providerUrgency": "NOT_DEFINED",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "NONE",
"subIntegrityImpact": "NONE",
"userInteraction": "NONE",
"valueDensity": "NOT_DEFINED",
"vectorString": "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/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
"version": "4.0",
"vulnAvailabilityImpact": "NONE",
"vulnConfidentialityImpact": "HIGH",
"vulnIntegrityImpact": "HIGH",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"type": "Secondary"
}
],
"ssvcV203": [
{
"source": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"ssvcData": {
"id": "CVE-2026-91039",
"options": [
{
"exploitation": "none"
},
{
"automatable": "no"
},
{
"technicalImpact": "total"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-09-17T19:40:12.687953Z",
"version": "2.0.3"
}
}
]
},
"published": "2026-09-17T16:18:28.700",
"references": [
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://cna.erlef.org/cves/CVE-2026-91039.html"
},
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://github.com/team-alembic/ash_authentication/commit/64530644f9b37ebb76ca14aeb83a77597a0034b7"
},
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://github.com/team-alembic/ash_authentication/commit/73ad16e452670bbf843550a13361bd41e72ad964"
},
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://github.com/team-alembic/ash_authentication/security/advisories/GHSA-73j9-m294-fvv9"
},
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-91039"
}
],
"sourceIdentifier": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"vulnStatus": "Received",
"weaknesses": [
{
"description": [
{
"lang": "en",
"value": "CWE-290"
}
],
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"type": "Secondary"
}
]
}
},
"vulnrichment": {
"containers": {
"adp": [
{
"metrics": [
{
"other": {
"content": {
"id": "CVE-2026-91039",
"options": [
{
"Exploitation": "none"
},
{
"Automatable": "no"
},
{
"Technical Impact": "total"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-09-17T19:40:12.687953Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-09-17T19:40:19.334Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"collectionURL": "https://repo.hex.pm",
"cpes": [
"cpe:2.3:a:team-alembic:ash_authentication:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"packageName": "ash_authentication",
"packageURL": "pkg:hex/ash_authentication",
"product": "ash_authentication",
"repo": "https://github.com/team-alembic/ash_authentication",
"vendor": "team-alembic",
"versions": [
{
"lessThan": "5.0.0-rc.14",
"status": "affected",
"version": "5.0.0-rc.10",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:team-alembic:ash_authentication:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"packageName": "team-alembic/ash_authentication",
"packageURL": "pkg:github/team-alembic/ash_authentication",
"product": "ash_authentication",
"repo": "https://github.com/team-alembic/ash_authentication",
"vendor": "team-alembic",
"versions": [
{
"lessThan": "73ad16e452670bbf843550a13361bd41e72ad964",
"status": "affected",
"version": "64530644f9b37ebb76ca14aeb83a77597a0034b7",
"versionType": "git"
}
]
}
],
"configurations": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eOnly deployments running a \u003ccode\u003edynamic_oidc\u003c/code\u003e strategy with more than one identity-provider connection are exposed, since a single connection has no second namespace to collide with.\u003c/p\u003e\n\u003cp\u003eTwo partial mitigations exist and neither is sufficient. A \u003ccode\u003econfirmation\u003c/code\u003e 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.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "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.\n\nTwo 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."
}
],
"value": "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.\n\nTwo 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."
}
],
"cpeApplicability": [
{
"nodes": [
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:team-alembic:ash_authentication:*:*:*:*:*:*:*:*",
"versionEndExcluding": "5.0.0-rc.14",
"versionStartIncluding": "5.0.0-rc.10",
"vulnerable": true
}
],
"negate": false,
"operator": "OR"
}
],
"operator": "AND"
}
],
"credits": [
{
"lang": "en",
"type": "reporter",
"value": "Jace"
},
{
"lang": "en",
"type": "coordinator",
"value": "Jonatan M\u00e4nnchen / EEF"
},
{
"lang": "en",
"type": "reporter",
"value": "manus-pi"
},
{
"lang": "en",
"type": "remediation developer",
"value": "James Harton"
}
],
"dateAssigned": "2026-09-17T05:25:04.000Z",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eAuthentication Bypass by Spoofing vulnerability in team-alembic ash_authentication allows an attacker who operates one identity-provider connection of a \u003ccode\u003edynamic_oidc\u003c/code\u003e strategy to be signed in as a local user established through a different connection.\u003c/p\u003e\n\u003cp\u003eThe strategy is meant to keep each connection in its own identity namespace by writing every \u003ccode\u003eUserIdentity\u003c/code\u003e row\u0027s \u003ccode\u003estrategy\u003c/code\u003e field as \u003ccode\u003e\"\u0026lt;name\u0026gt;/\u0026lt;connection_id\u0026gt;\"\u003c/code\u003e, but that namespacing never takes effect. \u003ccode\u003e__connection_id__\u003c/code\u003e is populated only on the ephemeral runtime struct built per request in \u003ccode\u003edynamic_oidc/plug.ex\u003c/code\u003e, and \u003ccode\u003eDynamicOidc.IdentityChange.change/3\u003c/code\u003e re-fetches the strategy from the compile-time DSL through \u003ccode\u003eInfo.strategy_for_action\u003c/code\u003e, yielding the persisted struct whose \u003ccode\u003e__connection_id__\u003c/code\u003e is its \u003ccode\u003edefstruct\u003c/code\u003e default of \u003ccode\u003enil\u003c/code\u003e. \u003ccode\u003eOAuth2.identity_strategy_name/1\u003c/code\u003e therefore falls back to the bare strategy name for both the identity write and the reads in \u003ccode\u003eoauth2/user_resolver.ex\u003c/code\u003e and \u003ccode\u003eoauth2/sign_in_preparation.ex\u003c/code\u003e. Since the identity resource\u0027s unique key is \u003ccode\u003e(uid, strategy)\u003c/code\u003e, one row exists per \u003ccode\u003esub\u003c/code\u003e across every connection, and the identity-match branch runs before any email check. Neither strategy handles \u003ccode\u003eiss\u003c/code\u003e, so nothing else distinguishes the issuers: OpenID Connect Core section 5.7 makes \u003ccode\u003esub\u003c/code\u003e unique only within an issuer, so two connections numbering subjects independently share one subject space.\u003c/p\u003e\n\u003cp\u003eThis issue affects ash_authentication: from 5.0.0-rc.10 before 5.0.0-rc.14.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "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.\n\nThe strategy is meant to keep each connection in its own identity namespace by writing every `UserIdentity` row\u0027s `strategy` field as `\"\u003cname\u003e/\u003cconnection_id\u003e\"`, 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\u0027s 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.\n\nThis issue affects ash_authentication: from 5.0.0-rc.10 before 5.0.0-rc.14."
}
],
"value": "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.\n\nThe strategy is meant to keep each connection in its own identity namespace by writing every UserIdentity row\u0027s strategy field as \"\u003cname\u003e/\u003cconnection_id\u003e\", 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\u0027s 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.\n\nThis issue affects ash_authentication: from 5.0.0-rc.10 before 5.0.0-rc.14."
}
],
"impacts": [
{
"capecId": "CAPEC-21",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eAn attacker who can obtain or choose their \u003ccode\u003esub\u003c/code\u003e on any connection served by a \u003ccode\u003edynamic_oidc\u003c/code\u003e 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\u0027s own identity provider.\u003c/p\u003e\n\u003cp\u003eThe same collision also occurs with no attacker at all: two honest providers numbering subjects sequentially produce overlapping \u003ccode\u003esub\u003c/code\u003e values and silently merge distinct users, so this may already have happened in affected deployments.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "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\u0027s own identity provider.\n\nThe 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."
}
],
"value": "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\u0027s own identity provider.\n\nThe 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."
}
]
}
],
"metrics": [
{
"cvssV4_0": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "PRESENT",
"attackVector": "NETWORK",
"baseScore": 9.1,
"baseSeverity": "CRITICAL",
"privilegesRequired": "NONE",
"providerUrgency": "NOT_DEFINED",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "NONE",
"subIntegrityImpact": "NONE",
"userInteraction": "NONE",
"valueDensity": "NOT_DEFINED",
"vectorString": "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",
"version": "4.0",
"vulnAvailabilityImpact": "NONE",
"vulnConfidentialityImpact": "HIGH",
"vulnIntegrityImpact": "HIGH",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"format": "CVSS",
"scenarios": [
{
"lang": "en",
"value": "GENERAL"
}
]
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-290",
"description": "CWE-290 Authentication Bypass by Spoofing",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-09-17T15:19:15.994Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"name": "GitHub Advisory",
"tags": [
"related",
"vendor-advisory"
],
"url": "https://github.com/team-alembic/ash_authentication/security/advisories/GHSA-73j9-m294-fvv9"
},
{
"name": "EEF CNA record for CVE-2026-91039",
"tags": [
"related"
],
"url": "https://cna.erlef.org/cves/CVE-2026-91039.html"
},
{
"name": "OSV record EEF-CVE-2026-91039",
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-91039"
},
{
"name": "Introducing commit 6453064 in team-alembic/ash_authentication",
"tags": [
"related"
],
"url": "https://github.com/team-alembic/ash_authentication/commit/64530644f9b37ebb76ca14aeb83a77597a0034b7"
},
{
"name": "Fix commit 73ad16e in team-alembic/ash_authentication",
"tags": [
"patch"
],
"url": "https://github.com/team-alembic/ash_authentication/commit/73ad16e452670bbf843550a13361bd41e72ad964"
}
],
"solutions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eUpgrading is not sufficient on its own, for two reasons.\u003c/p\u003e\n\u003cp\u003eNamespacing changes the value the identity lookup keys on, so \u003ccode\u003eUserIdentity\u003c/code\u003e rows written before the fix, all holding the bare strategy name, no longer match. Each row\u0027s \u003ccode\u003estrategy\u003c/code\u003e must be relinked in place to \u003ccode\u003e\"\u0026lt;name\u0026gt;/\u0026lt;connection_id\u0026gt;\"\u003c/code\u003e. Deleting the rows does not work: the block is the account match that follows, refused under the default \u003ccode\u003eon_untrusted_email_match :reject\u003c/code\u003e 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 \u003ccode\u003e(uid, strategy)\u003c/code\u003e index.\u003c/p\u003e\n\u003cp\u003eThe 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\u0027s 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\u0027s set of \u003ccode\u003esub\u003c/code\u003e values from its identity provider and intersecting them. Any \u003ccode\u003esub\u003c/code\u003e present in more than one set identifies an account that may have been merged.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Upgrading is not sufficient on its own, for two reasons.\n\nNamespacing 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\u0027s `strategy` must be relinked in place to `\"\u003cname\u003e/\u003cconnection_id\u003e\"`. 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.\n\nThe 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\u0027s 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\u0027s 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."
}
],
"value": "Upgrading is not sufficient on its own, for two reasons.\n\nNamespacing 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\u0027s strategy must be relinked in place to \"\u003cname\u003e/\u003cconnection_id\u003e\". 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.\n\nThe 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\u0027s 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\u0027s 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."
}
],
"source": {
"discovery": "EXTERNAL"
},
"title": "dynamic_oidc identities are not namespaced by connection in ash_authentication, allowing cross-connection account takeover"
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-91039",
"datePublished": "2026-09-17T15:19:15.994Z",
"dateReserved": "2026-09-15T15:30:01.883Z",
"dateUpdated": "2026-09-17T19:40:32.068Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
}
}
Loading…
Loading…
Experimental. This forecast is provided for visualization only and may change without notice. Do not use it for operational decisions.
Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Loading…
The MITRE ATT&CK techniques below are AI-generated suggestions, inferred from the description of the
vulnerability by the CIRCL/vulnerability-attack-technique-classification-roberta-base
model, served locally by ML-Gateway.
They have not been verified by an analyst and are provided for guidance only.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Loading…
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.
Loading…