Repository downloader drops netrc/`auth` credentials on same-host HTTP redirects
#30,013 opened on Jun 25, 2026
Repository metrics
- Stars
- (25,384 stars)
- PR merge metrics
- (Avg merge 22d 20h) (77 merged PRs in 30d)
Description
Description of the bug:
When http_file/http_archive downloads a URL that responds with a redirect to another path on the same host, Bazel does not send the credentials (from ~/.netrc or the rule's auth attr) on the redirected request. The follow-up request goes out unauthenticated and fails (e.g. 401). This commonly surfaces with artifact servers/CDNs that normalize a URL and 30x-redirect to a canonical path that still requires auth.
Root cause
Credentials are flattened into an exact-URI map and looked up per redirect hop, so a
redirect to any URI other than the original matches nothing:
use_netrc()intools/build_defs/repo/utils.bzlkeys theauthdict by the exact request URL (keying).getAuthHeaders()inStarlarkBaseExternalContext.javaturns that dict into anImmutableMap<URI, …>keyed by the requestURI.StaticCredentials.getRequestMetadata(uri)does an exact-key lookup with no host fallback:credentials.getOrDefault(uri, ImmutableMap.of()).HttpConnectorMultiplexer.getHeaderFunction()re-runs this lookup for every hop.HttpConnector.connect()follows the redirect (url = HttpUtils.getLocation(connection)) and re-invokes the header function with the new target URI.
On the redirect, the new target URI is not a key in the map, so getOrDefault returns empty
and no Authorization header is added to the follow-up request.
Which category does this issue belong to?
No response
What's the simplest, easiest way to reproduce this bug? Please provide a minimal example if possible.
server.py (a host that redirects /start → /protected, where /protected requires Basic auth):
from http.server import BaseHTTPRequestHandler, HTTPServer
class H(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/start":
self.send_response(307); self.send_header("Location", "/protected"); self.end_headers()
elif self.path == "/protected" and self.headers.get("Authorization"):
body=b"ok"; self.send_response(200); self.send_header("Content-Length", str(len(body))); self.end_headers(); self.wfile.write(body)
else:
self.send_response(401); self.end_headers()
def log_message(self,*a): pass
HTTPServer(("127.0.0.1",8077),H).serve_forever()
netrc:
machine localhost login u password p
MODULE.bazel:
http_file = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")
http_file(
name = "x",
url = "http://localhost:8077/start",
netrc = "netrc",
sha256 = "2689367b205c16ce32ed4200942b8b8b1e262dfc70d9bc9fbc77c49699a4f1df", # "ok"
)
python3 server.py &
bazel build @x//file
Expected: download succeeds (creds re-applied to the same-host redirect target → 200).
Actual: GET returned 401 Unauthorized. The server log shows /start arrives with Authorization, but /protected (the redirect target) arrives without it.
Which operating system are you running Bazel on?
Linux
What is the output of bazel info release?
9.x
If bazel info release returns development version or (@non-git), tell us how you built Bazel.
No response
What's the output of git remote get-url origin; git rev-parse HEAD ?
If this is a regression, please try to identify the Bazel commit where the bug was introduced with bazelisk --bisect.
No response
Have you found anything relevant by searching the web?
Stripping Authorization on a cross-host redirect is standard (security). The difference is on same-host redirects:
- curl
--netrc/ wget re-derive credentials from netrc per host on every hop → same-host redirect stays authenticated. - Python
requestsonly stripsAuthorizationwhen the host changes; same-host redirects keep it. - Bazel keys by exact URI and re-derives nothing, so even a same-host, same-scheme redirect to a different path loses the credentials.
Any other information, logs, or outputs that you want to share?
Suggested direction: Re-apply the netrc-derived credentials by host (at least for same-host, same-scheme redirects), matching curl/wget — Bazel already has host-based netrcCreds available but converts them to an exact-URI entry for the original URL before downloading.