Skip to content

Authenticate index requests to private registries#968

Open
sunsided wants to merge 3 commits into
killercup:masterfrom
sunsided:feature/auth
Open

Authenticate index requests to private registries#968
sunsided wants to merge 3 commits into
killercup:masterfrom
sunsided:feature/auth

Conversation

@sunsided

@sunsided sunsided commented Jul 13, 2026

Copy link
Copy Markdown

Summary

cargo upgrade now sends the registry authentication token when querying a private sparse-registry index, so workspaces depending on an auth-required = true registry can be upgraded instead of failing with 401 Unauthorized.

Why

Reported in #931: cargo upgrade --incompatible fails with status code '401 Unauthorized': the request was not authorized against a private registry, while cargo search / build / test succeed with the same credentials, and downgrading to 0.13.1 avoids it.

The index request was always unauthenticated: RemoteIndex::krate builds it via tame-index's make_remote_request, which by design attaches no credentials, and cargo-edit never read or sent a token. Before 0.13.2 the resulting 401 was silently swallowed and the dependency skipped; #925 made index errors propagate, turning that long-standing gap into a hard failure that aborts the whole run.

Key changes

  • registry_token() in src/registry.rs resolves a registry's token from CARGO_REGISTRIES_<NAME>_TOKEN, then token under [registries.<name>] in credentials / config files, walking .cargo/ from the manifest up to $CARGO_HOME.
  • RemoteIndex carries the token and attaches it verbatim as the Authorization header on the index request, matching cargo (no Bearer prefix is injected).
  • The token is threaded through IndexCache to the single call site in cargo upgrade.
  • The default / crates.io index is left unauthenticated, so a crates.io publish token is never leaked to the CDN-fronted sparse index.
  • Regression coverage: token-resolution precedence, plus a local HTTP server asserting the Authorization header is present with a token and absent without.

Known gap: credential providers

Only static tokens are handled. The CARGO_REGISTRIES_<NAME>_TOKEN env var and a plaintext token in credentials.toml / config.toml. Cargo credential providers (registries.<name>.credential-provider, [registry] global-credential-providers, e.g. cargo:token-from-stdout, cargo:libsecret, or any credential-process) are not invoked. Users who keep their token in a provider rather than a plaintext token will still hit the 401. Matching cargo's full credential-provider resolution is left as a follow-up.

Review instructions

Start with registry_token() in src/registry.rs, resolution order and the crates.io opt-out. Then RemoteIndex::krate in src/index.rs, where the header is attached right after the tame-index request is converted to a reqwest request. The src/bin/upgrade/upgrade.rs change is just wiring the token into the existing index.krate call. Worth scrutiny: the token precedence (env over file, credentials over config, nearest dir first) and the decision to send the token verbatim.

Closes #931

`cargo upgrade` aborted with `status code '401 Unauthorized': the
request was not authorized` whenever a workspace depended on a private
registry with `auth-required = true`.

`RemoteIndex::krate` builds the sparse-index request via tame-index's
`make_remote_request`, which intentionally attaches no credentials, and
cargo-edit never read or sent a registry token. The request therefore
went out unauthenticated and the registry rejected it with 401. This was
always broken for auth-required registries; before 0.13.2 the error was
silently swallowed (the dependency was skipped), but killercup#925 made index
errors propagate, so the 401 now aborts the whole run.

Resolve the registry token (`CARGO_REGISTRIES_<NAME>_TOKEN`, then `token`
under `[registries.<name>]` in credentials/config, walking `.cargo/` up
to `$CARGO_HOME`) and attach it verbatim as the `Authorization` header,
matching cargo. The crates.io index is left unauthenticated so its
publish token is not leaked to the CDN-fronted sparse index.

Fixes killercup#931
@sunsided sunsided changed the title fix(upgrade): Authenticate index requests to private registries Authenticate index requests to private registries Jul 13, 2026
@sunsided sunsided marked this pull request as ready for review July 13, 2026 17:32
Copilot AI review requested due to automatic review settings July 13, 2026 17:32

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR updates cargo upgrade to authenticate sparse index requests for private registries by resolving a registry token (matching Cargo’s precedence rules) and attaching it as the Authorization header on the index HTTP request.

Changes:

  • Added registry_token() to resolve per-registry auth tokens from env/config/credentials files while explicitly opting out for the default (crates.io) registry.
  • Threaded an optional auth value through IndexCache/RemoteIndex and attach it as the Authorization header for remote sparse index requests.
  • Wired token lookup into cargo upgrade, and added regression tests plus a changelog entry.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/registry.rs Adds token resolution logic (registry_token) and unit tests for precedence/behavior.
src/lib.rs Re-exports registry_token for use by binaries.
src/index.rs Threads optional auth into remote index access and attaches Authorization header; adds request-capture tests.
src/bin/upgrade/upgrade.rs Wires token resolution into the existing index lookup call in cargo upgrade.
CHANGELOG.md Documents the fix in Unreleased notes.
Comments suppressed due to low confidence (1)

src/index.rs:93

  • IndexCache is keyed only by registry Url, but the RemoteIndex stores auth as state. If the same Url is queried with different auth values in one run (e.g. default crates.io with None, then a named registry/source replacement mapping to the same Url with Some(token), or two names sharing an index Url), the cached RemoteIndex will keep the first auth value. This can either omit credentials (causing 401) or send a token on requests that were meant to be unauthenticated (credential leakage). Consider updating the cached RemoteIndex's auth on every access (or include auth in the cache key).
        if !self.index.contains_key(registry) {
            let index = AnyIndex::open(registry, self.certs_source, auth)?;
            let index = AnyIndexCache::new(index);
            self.index.insert(registry.clone(), index);
        }
        Ok(self.index.get_mut(registry).unwrap())
    }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/registry.rs Outdated
sunsided and others added 2 commits July 14, 2026 08:54
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cargo upgrade returns 401 consistently even after changing registry token

2 participants