bazelbuild/bazel

Repository downloader drops netrc/`auth` credentials on same-host HTTP redirects

Open

#30,013 opened on Jun 25, 2026

View on GitHub
 (0 comments) (0 reactions) (0 assignees)Java (4,465 forks)batch import
P2help wantedteam-ExternalDepstype: bug

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:

  1. use_netrc() in tools/build_defs/repo/utils.bzl keys the auth dict by the exact request URL (keying).
  2. getAuthHeaders() in StarlarkBaseExternalContext.java turns that dict into an ImmutableMap<URI, …> keyed by the request URI.
  3. StaticCredentials.getRequestMetadata(uri) does an exact-key lookup with no host fallback: credentials.getOrDefault(uri, ImmutableMap.of()).
  4. HttpConnectorMultiplexer.getHeaderFunction() re-runs this lookup for every hop.
  5. 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 requests only strips Authorization when 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.

Contributor guide