# Dex: Token-exchange endpoint is missing AllowedConnectors enforcement

Dex's token-exchange handler skipped the per-client AllowedConnectors ACL, letting a client with a leaked secret mint tokens for a forbidden high-trust connector.

- Payout: Research
- Severity: high
- Program: Dex
- Found by: matte1782
- Disclosed: 2026-06-09
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-7qjx-gp9h-65qj
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-7qjx-gp9h-65qj
- Tags: area:web domain:oss domain:web impact:integrity impact:privesc kind:research platform:ghsa sev:high tech:go tech:oauth technique:incomplete-fix technique:source-review vuln:access-control vuln:auth-bypass

**Target:** OAuth2 token endpoint (/token) token-exchange grant handler
**Root cause:** handleTokenExchange validates connector existence and grant-type but never calls isConnectorAllowed(client.AllowedConnectors, connID), unlike sibling redirect-flow handlers, so the per-client connector ACL is unenforced on token exchange.
**Steps:**
1. Identify client `dev-app` with `allowedConnectors: ["dev-google"]` and obtain its leaked client_secret.
2. Obtain attacker's own valid ID token from the forbidden connector `corp-okta`.
3. POST to `/token` with `grant_type=urn:ietf:params:oauth:grant-type:token-exchange`, `client_id=dev-app`, `client_secret=<leaked>`, `connector_id=corp-okta`, `subject_token=<corp-okta id token>`, `subject_token_type=urn:ietf:params:oauth:token-type:id_token`, `scope=openid+groups`.
4. Receive Dex-signed ID token with `aud=dev-app` carrying corp-okta groups.
**How found:** Code-reading of handleTokenExchange against sibling handlers: noticed getConnector and GrantTypeAllowed checks present but isConnectorAllowed absent, while handleConnectorLogin:377 and parseAuthorizationRequest:535 enforce it; cross-checked PR #4610 (added ACL) and PR #4619 (added grant-type gate) diffs to confirm the gap.
**Exploitation:** Attacker with a leaked confidential client_secret and their own token from a forbidden connector exchanges it at /token; Dex issues a token bound to the client but with claims from the disallowed connector, which downstream services trusting dev-app accept as a corp-okta identity.
**Escalation:** Cross-connector trust confusion: low-trust client's token audience combined with high-trust connector claims bypasses admin's explicit connector restriction, granting production access.
**Insight:** When a new per-client ACL field is introduced, grep every handler that reads the gated identifier (here connector_id) and diff against the reference enforcement site; grant-type gates and existence checks are not substitutes for the ACL, and static analyzers miss missing-validator gaps.

## Proof of concept (copied exactly from the original report)

**Token-exchange request bypassing AllowedConnectors**

```
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
```

