Released in v2.0.26 and v2.1.0, 25 Oct 2026 Two upstream fixes in the oauth2 gem
oauth2 for Ruby: secrets followed redirects and leaked into errors
Our scanner flagged two places where oauth2, the Ruby OAuth 2.0 client gem that omniauth-oauth2 builds on, with over 280 million downloads on RubyGems, could hand secrets to the wrong place. We opened a pull request for each, with a fix and regression tests. Maintainer Peter Boling (pboling) merged both on 25 Oct 2026, hardened them the same evening and shipped them in v2.0.26 and v2.1.0. The 2.1.0 changelog credits both to us.
1. A cross-origin redirect kept the request body
OAuth2::Client#request follows 301, 302, 303 and 307 redirects. When the redirect pointed to a different origin, sanitize_redirect_options removed the Authorization header and nothing else:
def sanitize_redirect_options(req_opts, current_location, next_location)
return req_opts unless cross_origin_redirect?(current_location, next_location)
headers = req_opts[:headers]
return req_opts unless headers && headers.any? { |key, _value| authorization_header?(key) }
safe_opts = req_opts.dup
safe_headers = headers.dup
safe_headers.delete_if { |key, _value| authorization_header?(key) }
safe_opts[:headers] = safe_headers
safe_opts
end
Except for a 303, which switches to GET and drops the body, the request body and query parameters went to the new host unchanged. On a token request that body holds the one-time authorization code or a refresh token, and with auth_scheme: :request_body also the client_id and client_secret. So if a token endpoint, or a proxy in front of it, answered with a 307 to another host, the client replayed the code and the client secret there.
Our pull request #775 also drops :body and :params, under both symbol and string keys, on a cross-origin redirect and keeps them on a same-origin one. Four new specs failed on the old code, one of them a POST body with credentials arriving at the other host, and passed with the fix; the full suite of 601 examples stayed green.
2. A token-less response was dumped into the error message
When a token response contains neither an access token nor a refresh token and raise_errors is on (the default), OAuth2::AccessToken#initialize raises OAuth2::Error with the description "Options are: #{opts.inspect}". At that point opts still holds the rest of the provider’s response, such as an OpenID Connect id_token or a token under an alternative key, so the full values ended up in exception messages, logs and error trackers.
Our pull request #776 replaces the values of sensitive keys with [FILTERED] before the options are inspected, with specs showing the secrets no longer appear in the message. Peter Boling replied on it:
“This is pretty good.”
He added that a new release of his auth-sanitizer gem would offer a helper to simplify our hand-written redaction, and published it in auth-sanitizer #27.
What the maintainer added
Minutes after merging, Peter Boling opened pull request #777, which:
- switches the error-message redaction to
Auth::Sanitizer.sanitizefromauth-sanitizer0.2.5; - extends the default filtered keys with
password,client_assertion,device_code,subject_token,actor_tokenand camel-case token names; - strips the
Authorizationheader whether the headers sit under a symbol or a string key.
Version 2.1.0 also makes allow_cross_origin_redirects default to false, so the client now refuses a cross-origin redirect before replaying anything; an app that opts back in still gets the stripped request from our fix.
Timeline
- October 2026. A scan of the oauth2 tree at commit
9e2c294flagged both issues; triage rated the redirect leak high and the error-message leak medium, and failing specs reproduced both. - 24 Oct. We opened pull request #775 for the redirect leak.
- 25 Oct. We opened pull request #776 for the error message. Peter Boling merged both that evening, followed up with #777, and released v2.0.26 and v2.1.0, both of which contain the fixes.
How to read this
The redirect leak needs the token endpoint, or something in front of it, to send a cross-origin redirect: a compromised or misconfigured server or proxy, not an arbitrary outsider. An attacker who fully controls the token endpoint already sees the request, so the fix matters for a redirect from a server the app otherwise trusts. The error-message leak only reaches logs when a provider returns a response without a token. Both are hardening of a widely used library rather than remotely exploitable holes. Findings are leads for people, not verdicts.