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 (admin intent: dev-app only sees dev-google identities)