Product or component
Hex CLI
Summary
When a user is authenticated to hex.pm (mix hex.user auth), Hex sends that hex.pm OAuth token — as Authorization: Bearer <token> — to requests for custom repositories, even ones with no configured auth_key that rely on ~/.netrc for HTTP Basic auth. This has two effects:
- The hex.pm Bearer token is sent to a server it means nothing to, which rejects it.
- Because an
Authorization header is now present, Hex.HTTP.add_basic_auth_via_netrc/2 no-ops, so the repo's ~/.netrc credentials are never applied.
Net result: mix deps.get (and mix hex.repo add --fetch-public-key) fail with 401 against a custom repo that authenticates fine outside Hex. mix hex.user deauth fixes it immediately.
Regression introduced in 2.5.0 (see root cause). Works on 2.4.2.
What happened?
401.
What did you expect?
netrc credentials are used for myrepo; fetch succeeds.
Steps to reproduce
- Add a custom repo whose server requires HTTP Basic auth, with credentials only in
~/.netrc:
machine myrepo.example.com
login someuser
password somepass
mix hex.repo add myrepo https://myrepo.example.com --public-key ./key.pem
mix hex.user auth
mix deps.get for a project depending on a package from myrepo.
Environment / context
- Hex 2.5.1, Elixir 1.19.5 / Erlang-OTP 28
- Custom repo behind a CDN requiring HTTP Basic auth; credentials only in
~/.netrc; added with no auth_key.
Logs / screenshots
No response
Additional context
Root cause
In 2.5.0, Hex.Repo.get_package/3 began wrapping the hex_core call in Hex.Auth.with_repo/3. Verbatim:
# 2.4.2 and earlier — no auth wrapper
def get_package(repo, package, etag) do
repo_config = get_repo(repo)
config = build_hex_core_config(repo_config, repo, etag)
:mix_hex_repo.get_package(config, package)
end
# 2.5.0+ — wrapped
def get_package(repo, package, etag) do
repo_config = get_repo(repo)
config = build_hex_core_config(repo_config, repo, etag)
Hex.Auth.with_repo(config, &:mix_hex_repo.get_package(&1, package), optional: true)
end
Hex.Auth.with_repo → :mix_hex_cli_auth.with_repo uses Hex.Auth.get_oauth_tokens/0, which reads the global token (Hex.State.get(:oauth_token), set by mix hex.user auth) and attaches it as Authorization: Bearer <token> to the request — regardless of the target repo. The repo in question has auth_key: nil and oauth_exchange: false, so it should not be receiving hexpm credentials.
The netrc step only runs when no Authorization header is present (lib/hex/http.ex):
defp add_basic_auth_via_netrc(%{"authorization" => _} = headers, _url), do: headers
So the Bearer header both fails auth and blocks the netrc fallback.
Evidence
Same custom-repo URL, same session:
| Condition |
Result |
Hex.HTTP.request with no authorization header |
200 (netrc applied) |
Hex.HTTP.request with an authorization header |
401 (netrc skipped) |
Real get_package while logged into hex.pm |
401 |
Real get_package after mix hex.user deauth |
200 |
Product or component
Hex CLI
Summary
When a user is authenticated to hex.pm (
mix hex.user auth), Hex sends that hex.pm OAuth token — asAuthorization: Bearer <token>— to requests for custom repositories, even ones with no configuredauth_keythat rely on~/.netrcfor HTTP Basic auth. This has two effects:Authorizationheader is now present,Hex.HTTP.add_basic_auth_via_netrc/2no-ops, so the repo's~/.netrccredentials are never applied.Net result:
mix deps.get(andmix hex.repo add --fetch-public-key) fail with 401 against a custom repo that authenticates fine outside Hex.mix hex.user deauthfixes it immediately.Regression introduced in 2.5.0 (see root cause). Works on 2.4.2.
What happened?
401.What did you expect?
netrc credentials are used for
myrepo; fetch succeeds.Steps to reproduce
~/.netrc:mix hex.user authmix deps.getfor a project depending on a package frommyrepo.Environment / context
~/.netrc; added with noauth_key.Logs / screenshots
No response
Additional context
Root cause
In 2.5.0,
Hex.Repo.get_package/3began wrapping the hex_core call inHex.Auth.with_repo/3. Verbatim:Hex.Auth.with_repo→:mix_hex_cli_auth.with_repousesHex.Auth.get_oauth_tokens/0, which reads the global token (Hex.State.get(:oauth_token), set bymix hex.user auth) and attaches it asAuthorization: Bearer <token>to the request — regardless of the target repo. The repo in question hasauth_key: nilandoauth_exchange: false, so it should not be receiving hexpm credentials.The netrc step only runs when no
Authorizationheader is present (lib/hex/http.ex):So the Bearer header both fails auth and blocks the netrc fallback.
Evidence
Same custom-repo URL, same session:
Hex.HTTP.requestwith noauthorizationheaderHex.HTTP.requestwith anauthorizationheaderget_packagewhile logged into hex.pmget_packageaftermix hex.user deauth