GHSA-7QJX-GP9H-65QJ

Vulnerability from github – Published: 2026-06-09 21:59 – Updated: 2026-06-09 21:59
VLAI
Summary
Dex: Token-exchange endpoint is missing AllowedConnectors enforcement
Details

Summary

server/handlers.go::handleTokenExchange (lines 1804-1893) does not call isConnectorAllowed(client.AllowedConnectors, connID) before issuing tokens, while sibling handlers do. This is a per-client connector ACL gap on the token-exchange endpoint; the redirect-flow paths enforce the same field correctly.

Affected code path

handleTokenExchange reads connector_id from the request body at server/handlers.go:1822. Validators called between read and token issuance:

  • s.getConnector(ctx, connID) at line 1836 - confirms connector exists
  • GrantTypeAllowed(conn.GrantTypes, grantTypeTokenExchange) at line 1842 - confirms connector permits this grant
  • (missing) isConnectorAllowed(client.AllowedConnectors, connID) - never called

Tokens are issued at lines 1887 / 1889, bound to client.ID carrying claims derived from connID.

Sibling handlers DO enforce the check:

  • server/handlers.go::handleConnectorLogin:377 - calls isConnectorAllowed, returns HTTP 403 "Connector not allowed for this client." (line 380).
  • server/oauth2.go::parseAuthorizationRequest:535 - same enforcement for the authorization-code flow.

The doc-string at storage/storage.go:192-194 reads:

AllowedConnectors is a list of connector IDs that the client is allowed to use for authentication. If empty, all connectors are allowed.

The phrasing is unconditional - a permission ACL, not a UX filter.

Impact (concrete scenario)

  • Connector corp-okta - high-trust, gates production access
  • Connector dev-google - low-trust, internal Gmail
  • Client dev-app configured with allowedConnectors: ["dev-google"] (admin intent: dev-app only sees dev-google identities)
  • dev-apps client secret leaks (CI artifact, env file, breached service-account secret store)

Without the bug, the leaked secret would only allow the attacker to mint tokens via dev-google - blast radius bounded by what any dev-google user can already do.

With the bug, an attacker holding their own legitimate corp-okta ID token sends:

POST /token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&client_id=dev-app
&client_secret=<leaked>
&connector_id=corp-okta
&subject_token=<attackers own corp-okta id token>
&subject_token_type=urn:ietf:params:oauth:token-type:id_token
&scope=openid+groups

Dex returns an ID token signed by Dex, aud=dev-app, carrying the attackers corp-okta groups. Downstream services trusting tokens issued for dev-app see the attacker as a corp-okta user - a combination the admins policy explicitly forbade.

Severity (self-assessed)

CVSS 3.1 vector: AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:N -> 8.7 HIGH.

The PR:H precondition is a real reduction (requires leaked confidential client_secret PLUS attacker holding a subject_token from a forbidden connector that has token-exchange enabled). Defer to your scoring - HIGH and MEDIUM are both defensible.

Affected version

master only - not yet in any released tag. Latest release v2.45.1 (2026-03-03) predates PR #4610 (commit f80a89d, 2026-03-11) which introduced AllowedConnectors. Production deployments on stable releases are NOT affected; deployments pulling from master / nightly images are. A fix can be merged ahead of the next release without an embargo for past versions.

Precedent / lineage

  • PR #4610 (commit f80a89d, 2026-03-11) - added the AllowedConnectors field, isConnectorAllowed, filterConnectors, and the redirect-flow check sites (handleConnectorLogin:377, parseAuthorizationRequest:535). Did not modify handleTokenExchange.
  • PR #4619 (commit 7777773, 2026-03-11, same author, one day earlier) - added GrantTypeAllowed(conn.GrantTypes, grantTypeTokenExchange) to handleTokenExchange. Added a connector-side grant-type gate but did not add the symmetric client-side connector ACL.

Suggested fix

Insert isConnectorAllowed(client.AllowedConnectors, connID) between the existing getConnector / GrantTypeAllowed checks and the connector cast at line 1847, returning HTTP 403 via the token-endpoint error helper. Mirror the existing patterns at handlers.go:377-380 and oauth2.go:535. One-block addition.

Verification methodology

Two-stage verification per IRIS / XBOW pattern (LLM-assisted research with non-LLM verifier as last stage):

  1. Code-mechanics - independent cold-read of server/handlers.go, server/oauth2.go, storage/storage.go confirmed the missing check at handleTokenExchange and the present checks at the two siblings; cross-checked diffs of PR #4610 (f80a89d) and PR #4619 (7777773).
  2. External grounding - cross-checked docs/configuration/customization, docs/guides/token-exchange/, RFC 8693 (which defers per-client policy to implementations), .github/SECURITY.md, GHSA dashboard, huntr.com, and existing issues including #3546 (different mechanism: connector-level disable list, orthogonal to this finding). No prior public report of this gap was found.

semgrep (p/golang + p/security-audit) on server/ returned no ERROR-severity findings - the static tool cannot detect missing-validator gaps; evidence rests on file:line grep + sibling-handler comparison above.

Reporter

Matteo Panzeri (GitHub: @matte1782, contact: matteo1782@gmail.com). Please credit as Matteo Panzeri if a CVE is requested.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/dexidp/dex"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.0.0-20260303131938-204dbb2e3ff7"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-285"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-06-09T21:59:33Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "## Summary\n\n`server/handlers.go::handleTokenExchange` (lines 1804-1893) does not call `isConnectorAllowed(client.AllowedConnectors, connID)` before issuing tokens, while sibling handlers do. This is a per-client connector ACL gap on the token-exchange endpoint; the redirect-flow paths enforce the same field correctly.\n\n## Affected code path\n\n`handleTokenExchange` reads `connector_id` from the request body at `server/handlers.go:1822`. Validators called between read and token issuance:\n\n- `s.getConnector(ctx, connID)` at line 1836 - confirms connector exists\n- `GrantTypeAllowed(conn.GrantTypes, grantTypeTokenExchange)` at line 1842 - confirms connector permits this grant\n- **(missing)** `isConnectorAllowed(client.AllowedConnectors, connID)` - never called\n\nTokens are issued at lines 1887 / 1889, bound to `client.ID` carrying claims derived from `connID`.\n\nSibling handlers DO enforce the check:\n\n- `server/handlers.go::handleConnectorLogin:377` - calls `isConnectorAllowed`, returns HTTP 403 \"Connector not allowed for this client.\" (line 380).\n- `server/oauth2.go::parseAuthorizationRequest:535` - same enforcement for the authorization-code flow.\n\nThe doc-string at `storage/storage.go:192-194` reads:\n\n\u003e *AllowedConnectors is a list of connector IDs that the client is allowed to use for authentication. If empty, all connectors are allowed.*\n\nThe phrasing is unconditional - a permission ACL, not a UX filter.\n\n## Impact (concrete scenario)\n\n- Connector `corp-okta` - high-trust, gates production access\n- Connector `dev-google` - low-trust, internal Gmail\n- Client `dev-app` configured with `allowedConnectors: [\"dev-google\"]` (admin intent: dev-app only sees dev-google identities)\n- `dev-app`s client secret leaks (CI artifact, env file, breached service-account secret store)\n\nWithout the bug, the leaked secret would only allow the attacker to mint tokens via `dev-google` - blast radius bounded by what any dev-google user can already do.\n\nWith the bug, an attacker holding their own legitimate `corp-okta` ID token sends:\n\n```\nPOST /token\nContent-Type: application/x-www-form-urlencoded\n\ngrant_type=urn:ietf:params:oauth:grant-type:token-exchange\n\u0026client_id=dev-app\n\u0026client_secret=\u003cleaked\u003e\n\u0026connector_id=corp-okta\n\u0026subject_token=\u003cattackers own corp-okta id token\u003e\n\u0026subject_token_type=urn:ietf:params:oauth:token-type:id_token\n\u0026scope=openid+groups\n```\n\nDex returns an ID token signed by Dex, `aud=dev-app`, carrying the attackers `corp-okta` groups. Downstream services trusting tokens issued for `dev-app` see the attacker as a `corp-okta` user - a combination the admins policy explicitly forbade.\n\n## Severity (self-assessed)\n\nCVSS 3.1 vector: `AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:N` -\u003e **8.7 HIGH**.\n\nThe `PR:H` precondition is a real reduction (requires leaked confidential client_secret PLUS attacker holding a `subject_token` from a forbidden connector that has token-exchange enabled). Defer to your scoring - HIGH and MEDIUM are both defensible.\n\n## Affected version\n\n`master` only - **not yet in any released tag**. Latest release `v2.45.1` (2026-03-03) predates PR #4610 (commit `f80a89d`, 2026-03-11) which introduced `AllowedConnectors`. Production deployments on stable releases are NOT affected; deployments pulling from master / nightly images are. A fix can be merged ahead of the next release without an embargo for past versions.\n\n## Precedent / lineage\n\n- PR #4610 (commit `f80a89d`, 2026-03-11) - added the `AllowedConnectors` field, `isConnectorAllowed`, `filterConnectors`, and the redirect-flow check sites (`handleConnectorLogin:377`, `parseAuthorizationRequest:535`). Did not modify `handleTokenExchange`.\n- PR #4619 (commit `7777773`, 2026-03-11, same author, one day earlier) - added `GrantTypeAllowed(conn.GrantTypes, grantTypeTokenExchange)` to `handleTokenExchange`. Added a connector-side grant-type gate but did not add the symmetric client-side connector ACL.\n\n## Suggested fix\n\nInsert `isConnectorAllowed(client.AllowedConnectors, connID)` between the existing `getConnector` / `GrantTypeAllowed` checks and the connector cast at line 1847, returning HTTP 403 via the token-endpoint error helper. Mirror the existing patterns at `handlers.go:377-380` and `oauth2.go:535`. One-block addition.\n\n## Verification methodology\n\nTwo-stage verification per IRIS / XBOW pattern (LLM-assisted research with non-LLM verifier as last stage):\n\n1. **Code-mechanics** - independent cold-read of `server/handlers.go`, `server/oauth2.go`, `storage/storage.go` confirmed the missing check at `handleTokenExchange` and the present checks at the two siblings; cross-checked diffs of PR #4610 (`f80a89d`) and PR #4619 (`7777773`).\n2. **External grounding** - cross-checked `docs/configuration/customization`, `docs/guides/token-exchange/`, RFC 8693 (which defers per-client policy to implementations), `.github/SECURITY.md`, GHSA dashboard, huntr.com, and existing issues including #3546 (different mechanism: connector-level disable list, orthogonal to this finding). No prior public report of this gap was found.\n\n`semgrep` (`p/golang` + `p/security-audit`) on `server/` returned no ERROR-severity findings - the static tool cannot detect missing-validator gaps; evidence rests on file:line grep + sibling-handler comparison above.\n\n## Reporter\n\nMatteo Panzeri (GitHub: @matte1782, contact: matteo1782@gmail.com). Please credit as **Matteo Panzeri** if a CVE is requested.",
  "id": "GHSA-7qjx-gp9h-65qj",
  "modified": "2026-06-09T21:59:33Z",
  "published": "2026-06-09T21:59:33Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/dexidp/dex/security/advisories/GHSA-7qjx-gp9h-65qj"
    },
    {
      "type": "WEB",
      "url": "https://github.com/dexidp/dex/commit/204dbb2e3ff7692af3b7ca4362b1ee46fb43c227"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/dexidp/dex"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Dex: Token-exchange endpoint is missing AllowedConnectors enforcement"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

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…

Detection rules are retrieved from Rulezet.

Loading…

Loading…