Skip to content

fix(auth/httptransport): prevent bearer token leakage on cross-host redirects#14547

Open
evilgensec wants to merge 2 commits into
googleapis:mainfrom
evilgensec:fix/auth-transport-cross-host-redirect
Open

fix(auth/httptransport): prevent bearer token leakage on cross-host redirects#14547
evilgensec wants to merge 2 commits into
googleapis:mainfrom
evilgensec:fix/auth-transport-cross-host-redirect

Conversation

@evilgensec
Copy link
Copy Markdown

Problem

authTransport.RoundTrip unconditionally injects the Authorization header on every call, including the intermediate calls http.Client makes when following redirects.

Go's http.Client strips the Authorization header before building a cross-host redirect request (per the standard library's shouldCopyHeaderOnRedirect logic), but authTransport immediately re-adds the bearer token — forwarding it to every host in the redirect chain.

Reproduction sketch

// Two test servers on different ports/hosts.
// victim records whether Authorization header was present.
victim := httptest.NewServer(...)
redirector := httptest.NewServer(func(w, r) {
    http.Redirect(w, r, victim.URL, http.StatusFound)
})

client, _ := httptransport.NewClient(&httptransport.Options{Credentials: creds})
client.Get(redirector.URL)
// bearer token is present in victim's received request — credential leak

Any application using NewClient or AddAuthorizationMiddleware that follows a cross-host redirect (a webhook caller, an API proxy, or code that accepts user-provided URLs) will forward its OAuth bearer token to the redirect destination. An attacker who controls a redirect via an open redirect on a Google API endpoint or a user-controlled URL can steal the token.

Note: the same class of bug is being fixed upstream in golang.org/x/oauth2 (PR #804); this is an independent fix for the cloud.google.com/go/auth transport layer.

Fix

Check req.Response in RoundTrip. When req.Response is non-nil, the call is a redirect. If the current URL's host differs from the previous hop's host, skip auth injection and delegate directly to the base transport.

if prev := req.Response; prev != nil && prev.Request != nil {
    if prev.Request.URL.Host != req.URL.Host {
        reqBodyClosed = true
        return t.base.RoundTrip(req.Clone(req.Context()))
    }
}

Same-host redirects continue to carry the bearer token as before.

Tests

  • TestAuthTransport_CrossHostRedirectDoesNotLeakToken: verifies the token is NOT forwarded to a different-host redirect destination.
  • TestAuthTransport_SameHostRedirectIsAuthorized: verifies same-host redirects remain fully authorized.

@evilgensec evilgensec requested review from a team as code owners May 7, 2026 06:36
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces security enhancements to prevent bearer token leakage during cross-host redirects in the authTransport and adds comprehensive tests for these scenarios. Additionally, it limits the size of error bodies read in the storage client to 64 KiB to prevent potential out-of-memory issues. Feedback was provided to use case-insensitive host comparison and to remove an unnecessary request clone in the redirect handling logic.

Comment thread auth/httptransport/transport.go
…edirects

authTransport.RoundTrip unconditionally injects the Authorization header on
every call, including intermediate calls http.Client makes when following
redirects. Go's http.Client strips Authorization before building a
cross-host redirect request, but authTransport immediately re-adds the
token, forwarding it to every host in the redirect chain.

Any code using NewClient or AddAuthorizationMiddleware that follows a
cross-host redirect (e.g. a webhook caller, an API proxy, or code that
accepts user-supplied URLs) leaks its OAuth bearer token to the redirect
destination. An attacker who controls a redirect via an open redirect on
a Google API endpoint or a user-controlled URL can steal the token.

Fix: check req.Response in RoundTrip. When req.Response is non-nil, this
call is a redirect. If the current URL host differs from the previous
hop's host, skip auth injection and delegate directly to the base
transport.

Adds two tests: one verifying tokens are not forwarded to a different
host, and one verifying same-host redirects continue to be authorized.
@evilgensec evilgensec force-pushed the fix/auth-transport-cross-host-redirect branch from 16e548c to 0ee17f0 Compare May 7, 2026 15:25
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant