Skip to content

security: Reject cross-host redirects to prevent Authorization leak#4171

Open
mohammadmseet-hue wants to merge 1 commit intogoogle:masterfrom
mohammadmseet-hue:fix/redirect-auth-leak
Open

security: Reject cross-host redirects to prevent Authorization leak#4171
mohammadmseet-hue wants to merge 1 commit intogoogle:masterfrom
mohammadmseet-hue:fix/redirect-auth-leak

Conversation

@mohammadmseet-hue
Copy link
Copy Markdown
Contributor

Summary

roundTripWithOptionalFollowRedirect and bareDoUntilFound manually follow HTTP 301 redirects by reading the Location header and issuing a new request through the same *http.Client. Because the auth transport installed by WithAuthToken is attached to that client, any redirect target receives the user's Authorization: Bearer <token> header.

Go's net/http.Client strips Authorization on cross-domain redirects by default (golang/go#35104). The custom redirect handling here bypasses that protection: a compromised GitHub Enterprise Server, an on-path attacker on an HTTP GHE link, or any attacker who can influence an API response's Location header can point the client at an attacker-controlled host and capture the Bearer token.

This PR validates that the redirect target's host matches c.BaseURL.Host before following the redirect. Relative locations still resolve against BaseURL and are followed normally; cross-host targets are rejected with a descriptive error.

Affected code paths

Both redirect helpers preserve the auth transport across the follow:

  • roundTripWithOptionalFollowRedirectgithub/github.go (used by GetArchiveLink, GetBranch, DownloadArtifact, GetWorkflowJobLogs, GetWorkflowRunLogs, GetWorkflowRunAttemptLogs).
  • bareDoUntilFoundgithub/github.go (used by the same endpoints when RateLimitRedirectionalEndpoints=true).

What the fix does

  • Adds Client.checkRedirectHost(location string) error, which resolves a Location value (absolute or relative) against c.BaseURL and returns an error if the resulting host differs from c.BaseURL.Host.
  • roundTripWithOptionalFollowRedirect calls checkRedirectHost before recursing into the new URL.
  • bareDoUntilFound performs the same host comparison before calling req.Clone(ctx) (since Clone preserves Authorization headers).

Same-host redirects — the normal case for GitHub's rate-limit redirection flow — continue to work unchanged.

Reproducer

A minimal reproducer with a fake "GitHub API" returning a 301 to an evil server captures the Bearer token on v84.0.0:

=== PoC: roundTripWithOptionalFollowRedirect ===
[Evil Server] Captured Authorization: Bearer ghp_SUPER_SECRET_12345
CONFIRMED: Token leaked: Bearer ghp_SUPER_SECRET_12345

With this patch applied, the same PoC returns refusing to follow cross-host redirect from "127.0.0.1:<api>" to "127.0.0.1:<evil>" and no request is issued to the evil server.

Tests

  • TestBareDoUntilFound_RejectsCrossHostRedirect — 301 with cross-host Location is rejected.
  • TestRoundTripWithOptionalFollowRedirect_RejectsCrossHostRedirect — same, for the other code path.
  • TestRoundTripWithOptionalFollowRedirect_AllowsSameHostRedirect — same-host 301 is still followed (regression guard).
  • Existing TestBareDoUntilFound_redirectLoop and TestBareDoUntilFound_UnexpectedRedirection continue to pass.

Notes on scope

This PR only addresses the two 301-follow code paths. Two related patterns in which the library uses attacker-controlled URLs from API JSON bodies through the auth transport — DownloadContents/DownloadContentsWithMeta (via download_url) and UploadReleaseAssetFromRelease (via upload_url) — have the same underlying risk but are out of scope here to keep this change focused and easy to review. I'm happy to send a follow-up PR for those.

The client manually follows HTTP 301 redirects in two places by extracting
the Location header (or the RedirectionError.Location field) and recursively
issuing a new request through the client's auth transport. Because the new
request reuses the same transport, any Authorization header configured via
WithAuthToken is also sent to the redirect target.

Go's net/http.Client strips Authorization headers on cross-domain redirects
by default (see golang/go#35104), but the custom
redirect handling in roundTripWithOptionalFollowRedirect and bareDoUntilFound
bypasses that protection. A compromised GitHub Enterprise Server, a MitM on
an HTTP GHE link, or any API response that can inject a Location header can
therefore redirect the client to an attacker-controlled host and capture the
user's Bearer token.

This change validates that the redirect target's host matches the client's
configured BaseURL.Host before following the redirect. Same-host redirects
(the normal rate-limit-redirection case) are unaffected; cross-host targets
are rejected with a descriptive error. Relative Location values continue to
resolve against BaseURL as before.

Tests cover both the cross-host rejection and the same-host follow path in
roundTripWithOptionalFollowRedirect, plus cross-host rejection in
bareDoUntilFound.
@gmlewis gmlewis changed the title security: reject cross-host redirects to prevent Authorization leak security: Reject cross-host redirects to prevent Authorization leak Apr 19, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 19, 2026

Codecov Report

❌ Patch coverage is 71.42857% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.69%. Comparing base (971b607) to head (40e627b).

Files with missing lines Patch % Lines
github/github.go 71.42% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #4171      +/-   ##
==========================================
- Coverage   93.70%   93.69%   -0.02%     
==========================================
  Files         210      210              
  Lines       19692    19706      +14     
==========================================
+ Hits        18453    18463      +10     
- Misses       1045     1047       +2     
- Partials      194      196       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@gmlewis gmlewis added the NeedsReview PR is awaiting a review before merging. label Apr 19, 2026
Copy link
Copy Markdown
Collaborator

@gmlewis gmlewis left a comment

Choose a reason for hiding this comment

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

Thank you, @mohammadmseet-hue!
LGTM.
Awaiting second LGTM+Approval from any other contributor to this repo before merging.

cc: @stevehipwell - @alexandear - @zyfy29 - @Not-Dhananjay-Mishra - @munlicode

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

NeedsReview PR is awaiting a review before merging.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants