Skip to content

RFC: Store registry tokens in the OS credential store by default#3981

Open
quinnjr wants to merge 2 commits into
rust-lang:masterfrom
quinnjr:keyring-credential-default
Open

RFC: Store registry tokens in the OS credential store by default#3981
quinnjr wants to merge 2 commits into
rust-lang:masterfrom
quinnjr:keyring-credential-default

Conversation

@quinnjr

@quinnjr quinnjr commented Jul 2, 2026

Copy link
Copy Markdown

This RFC proposes making Cargo store registry tokens in the operating system's credential store (Keychain, Credential Manager, Secret Service) by default instead of plaintext credentials.toml, using a single cargo:keyring built-in provider backed by the keyring crate.

Key points:

  • Default provider list becomes ["cargo:keyring", "cargo:token"] — keyring first, plaintext file as fallback so headless/CI environments are unaffected.
  • Existing plaintext tokens are migrated on any credential read (cargo login, cargo publish, cargo yank, cargo owner), not just on re-login, so the installed base actually moves.
  • CARGO_REGISTRY_TOKEN and --token keep precedence and are never migrated.
  • Builds on the credential-provider infrastructure from RFC 2730, stabilized in 1.74; this RFC only changes the default.

Rendered

quinnjr added 2 commits July 2, 2026 15:49
Cargo writes registry tokens to a plaintext credentials.toml by
default. Secure credential providers have existed since 1.74 but are
opt-in, so the default install remains the vulnerable one.

Propose a single cargo:keyring built-in provider (backed by the
keyring crate: Keychain, wincred, Secret Service) as the default,
with migration of existing plaintext tokens on any credential read
including cargo publish, and file fallback for headless/CI.
Comment on lines +45 to +52
# Rationale and alternatives
[rationale-and-alternatives]: #rationale-and-alternatives

The obvious alternative is the status quo: the secure providers exist, just turn them on yourself. But a security measure that requires discovering a config key does not protect the default install, and the default install is the threat model here. Defaults matter precisely because most users never change them, and that cuts both ways. Right now it cuts toward plaintext.

Migrating only on `cargo login` would be simpler, but it leaves every existing user in plaintext indefinitely, since a working token means there's no reason to ever run `login` again. Publish-time migration is what actually moves the installed base.

The Rust Maintainers could also keep the three separate per-platform providers and just change the default list per platform. That works, but it's three implementations to keep behaviorally in sync where one crate already does the job, and it leaves the BSDs out.

@zachs18 zachs18 Jul 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Another alternative could be to print a warning/notice when cargo publish (or another subcommand that requires registry authentication) is used interactively with a credentials.toml, prompting users to migrate manually (with the default for new cargo logins using the OS credential store). This wouldn't help much for users who publish infrequently, though.

To aid manual migration, maybe something like cargo login --bikeshed-migrate-credentials-to-keyring could be added that doesn't (re)login but just does the migration from credentials.toml to the OS key store.

View changes since the review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think a manual migration command would be useful whatever we do here, and also for testing.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I would agree for testing but I don't agree for the full practice.

Too many people, in my opinion, are going to just never do the migration if it's allowed to be manual. The purpose of the RFC is a forced stance on moving to a safety first approach to benefit users who are less savey about proper security procedures.

@teor2345 teor2345 Jul 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

To be clear, I think people should be able to manually migrate before they publish.
(In addition to automatic migration on publish.)

@ehuss ehuss added the T-cargo Relevant to the Cargo team, which will review and decide on the RFC. label Jul 3, 2026
# Prior art
[prior-art]: #prior-art

Cargo's own credential-process RFC ([RFC 2730](https://rust-lang.github.io/rfcs/2730-cargo-token-from-process.html)) and the 1.74 stabilization laid all the groundwork here. This RFC is really just about the default. Outside Rust: `docker-credential-helpers`, `gh auth`, and Python's `keyring` package all made the same move, and their experience (including the headless-fallback problem) is directly applicable.

@Eh2406 Eh2406 Jul 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You're absolutely right, The other languages and Tools Migrations would be extremely helpful in understanding where the pitfalls are. What did they do? What worked? What didn't? Where can I read more?

Are there other tools that did different migrations or made different decisions? If so what decisions did they do and why?

View changes since the review

@teor2345 teor2345 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There are some surprising security and availability risks here, which it would be good to mention in the drawbacks section, and mitigate if possible.

Overall I support this change though!

View changes since this review

# Drawbacks
[drawbacks]: #drawbacks

Keychain access can prompt the user, which is surprising the first time it happens mid-script, though the env-var and file fallbacks mean scripted environments never hit this in practice. Migrating during `publish` makes that first prompt more likely to appear in the middle of a workflow the user thinks of as non-interactive, which is why the notice needs to be loud about what just happened. It also adds the `keyring` crate (and its platform dependencies) to Cargo's dependency tree, which is real supply-chain surface for a security-sensitive code path and would need the usual vetting.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should there be a timeout for the initial transfer to the keychain, in case the user is running non-interactively, but with a GUI/terminal keychain prompt enabled?

Without a timeout, the build process will appear to freeze, and if the user can't see the reason, it will look like a bug. For example, the prompt could appear on a different (virtual/invisible) screen or terminal.

Similar unexpected prompts have led to denial of service issues, although this one isn't remotely triggerable.

Comment on lines +45 to +52
# Rationale and alternatives
[rationale-and-alternatives]: #rationale-and-alternatives

The obvious alternative is the status quo: the secure providers exist, just turn them on yourself. But a security measure that requires discovering a config key does not protect the default install, and the default install is the threat model here. Defaults matter precisely because most users never change them, and that cuts both ways. Right now it cuts toward plaintext.

Migrating only on `cargo login` would be simpler, but it leaves every existing user in plaintext indefinitely, since a working token means there's no reason to ever run `login` again. Publish-time migration is what actually moves the installed base.

The Rust Maintainers could also keep the three separate per-platform providers and just change the default list per platform. That works, but it's three implementations to keep behaviorally in sync where one crate already does the job, and it leaves the BSDs out.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think a manual migration command would be useful whatever we do here, and also for testing.

The existing three per-platform providers stay for a deprecation period so nobody's explicit config breaks, but they'd eventually become aliases for `cargo:keyring`.

# Drawbacks
[drawbacks]: #drawbacks

@teor2345 teor2345 Jul 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Another possible drawback is that the file no longer exists in the filesystem (or at least, it's not in the same place). This has multiple drawbacks:

  1. for backup/migration, if a user has carefully configured their system to backup or transfer everything they need, and they switch to a different device, the credential may be lost on the old machine
  2. for shared filesystems, the credential will only be on one machine
  3. for shared credential stores, the credential could be exposed to unexpected users

Since credentials can be re-created via GitHub, the first two issues seem manageable.
Are there any precautions we could take for the third issue?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actually, only crates.io credentials can be re-created via GitHub.

if this change applies to other registry credentials, then moving those credentials into personal keychains could potentially violate corporate asset or security policies.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

if the corporate registry mandates use of plaintext tokens they can specify it in the config:

# .cargo/config.toml

[registries.example]
index = 'https://example.com/index'
credential-provider = ['cargo:token']

also i don't think that a threat model that - (1) saving valuable corporate asset (the registry login token) into ~/.cargo/credentials.toml on employee's personal computer is fine but (2) being placed on their personal keychain is bad - make any sense at all. Either the corporate issue company computers so there's no "personal" keychain in the first place, or that the employee is totally trusted with the token so placing it anywhere secure is ok

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I don't think we need to worry about corporate access policies being an issue.

Prior art (Github CLI, pip/uv opt-in, etc) have already made the change and there isn't much covered engineering-news wise about lashing back from a corporate policy standpoint.

In my 5 last years, moves to secured credentials via company-owned password vaults and mandating things like 2FA, Passkeys on personal devices, etc, has been the norm, not the outlier.

@joshtriplett joshtriplett added the I-cargo-nominated Nominated for discussion during a cargo team meeting. label Jul 3, 2026
# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation

Rather than maintaining three separate built-in providers, Cargo's default provider would be a single `cargo:keyring` provider built on the `keyring` crate, which already abstracts over the macOS Security Framework, Windows Credential Manager, and Secret Service (with a kernel keyutils fallback on Linux, and Secret Service coverage on FreeBSD and OpenBSD). The default provider list becomes `["cargo:keyring", "cargo:token"]`: try the keyring, fall back to the file.

@Eh2406 Eh2406 Jul 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The cargo team rarely specifies exactly what dependencies we're going to use. I suspect we will be more comfortable merging this RFC if it directly specified the semantics; justifying that those semantics are possible to implement by referencing your recommended implementation.

Something like:

This RFC Described adding a combined Cross-platform built in provider under a common alias like cargo:keyring. This would have the semantics of using The system correct built in provider on any system. This can be implemented by wrapping the existing providers, or building a new provider based on the keyring crate.

If we do take your advice and use the keyring crate. We will also need to have a deep discussion about what infrastructure supports the ongoing maintenance of that crate. Who is on call for any security problems found? Will they still be around and available in three years or five years?

View changes since the review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

I-cargo-nominated Nominated for discussion during a cargo team meeting. T-cargo Relevant to the Cargo team, which will review and decide on the RFC.

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

7 participants