Skip to content

Netrc authentication for private wheel indexes broken by SessionAuth #1462

Open

Description

@Allda

Description

Requests to a private wheel cache return HTTP 401 despite valid Basic authentication credentials in netrc.

The regression was introduced by commit cae6a49fca4dcdcf53f5fd308753ee408aac2db1 — feat(auth): add URL-dispatched session auth for GitHub and GitLab.

Confirmed in a builder container running Fromager 0.101.0.

Observed behavior

During a wheel build:

WARNING banks-2.5.0: network error checking wheel cache for 2.5.0
at https://packages.redhat.com/lightwell/python/validated/simple/:
401 Client Error: Unauthorized for url:
https://packages.redhat.com/lightwell/python/validated/simple/banks/

The builder generates a netrc file and passes its path through NETRC. The same credentials successfully authenticate using a standard Requests session.

Minimal reproduction

Provide valid credentials for a private index through either ~/.netrc or a file referenced by NETRC:

machine packages.redhat.com login YOUR_USERNAME password YOUR_PASSWORD

Set file permissions to 0600, then run this in an environment with Fromager installed:

NETRC=/path/to/private.netrc python - <<'PYTHON'
import requests
from fromager.request_session import session

url = (
    "https://packages.redhat.com/"
    "lightwell/python/validated/simple/banks/"
)

for name, client in [
    ("requests", requests.Session()),
    ("fromager", session),
]:
    response = client.get(url, timeout=30, allow_redirects=False)
    print(
        name,
        response.status_code,
        "auth sent:",
        "Authorization" in response.request.headers,
    )
    response.close()
PYTHON

Observed output:

requests 200 auth sent: True
fromager 401 auth sent: False

This requires access to the private index; another index requiring netrc Basic authentication can be substituted.

Cause

The commit installs a custom authentication handler:

s.auth = auth

Requests performs its automatic netrc lookup only when neither request-specific nor session authentication is configured:

if self.trust_env and not auth and not self.auth:
    auth = get_netrc_auth(url)

Setting session.auth therefore suppresses automatic netrc lookup. SessionAuth handles registered GitHub/GitLab hosts, but supplies no authentication for unregistered hosts such as the private wheel server.

Verification

Using the same builder image, input package, and credentials:

Authentication implementation Result
Original Fromager 0.101.0 HTTP 401; no Authorization header
Only request_session.py restored from before cae6a49 HTTP 200; wheel CLI passes cache lookup
Proposed netrc fallback HTTP 200; wheel CLI passes cache lookup

Both home-directory and explicit NETRC locations reproduce the regression in request-preparation tests. Restoring the earlier module succeeds while retaining the builder’s temporary netrc location.

Expected behavior and proposed fix

For hosts without a registered authentication callback, use matching netrc credentials as HTTP Basic authentication.

A proposed fix is available in PR #1461, with regression coverage for both netrc locations, host matching, GitHub/GitLab authentication, and explicit credentials.

Activity

  1. tiran commented on Nov 9, 2026

    @tiran
    Collaborator

    Summary

    In #1173 I changed requests' session auth to no longer parse netrc by default. Instead trusted hosts must register a callback that returns auth headers.

    Options

    1. Keep the current behavior and document what users need to do to enable netrc parsing for their hosts
    2. Add a DEFAULT callback feature that is called when no host-specific callback is registered. Register a default callback that restores netrc parsing for all hosts. This allows users to control and override callback behavior.
    3. Modify SessionAuth.get() to always fall back to get_netrc_auth
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions