When an application uses OAuth2::Client (typically via an OAuth2::AccessToken) and the configured authorization server returns a redirect whose Location header is a protocol-relative URI of the form //attacker.example/leak, OAuth2::Client#request resolves the redirect with response.response.env.url.merge(location). Per RFC 3986 §5.2, an input that starts with // is a network-path reference and replaces the authority of the base URL: URI("http://idp.trusted/userinfo").merge("//attacker.example/leak") returns http://attacker.example/leak. The recursive request(verb, full_location, req_opts) call then re-sends the request to the attacker host while preserving the Authorization: Bearer <access-token> header that OAuth2::AccessToken#configure_authentication! installed on req_opts[:headers] for the original request.
The result is a one-shot cross-origin credential disclosure: any 30x response from the IdP that an attacker can influence (a compromised endpoint, a tenant-controlled IdP in a multi-tenant deployment, or an open-redirect handler that does not normalise the Location it emits) can extract the bearer access token of the calling user.
oauth2 v2.0.21 and all prior versions back to and including v0.4.0.
OAuth2::Client#request since v2.0.0 via https://github.com/ruby-oauth/oauth2/commit/b944da54cd70487c06d7252b9e4e7948eae56e73arm64-darwin25. The behaviour of URI#merge for protocol-relative inputs is RFC-conformant and the same on every supported Ruby (≥ 2.x).faraday 2.14.2 + faraday-net_http 3.4.3 stack. The URI#merge call is in...2.0.22Exploitability
AV:NAC:LPR:NUI:NScope
S:CImpact
C:HI:NA:N8.6/CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:NInformation Disclosure
Input Validation