diff --git a/Cargo.lock b/Cargo.lock
index 3c3c9895d7782..886e6c2fdac3e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5120,6 +5120,7 @@ dependencies = [
"tokio",
"tracing",
"url",
+ "uv-keyring",
"uv-once-map",
"uv-redacted",
"uv-small-str",
diff --git a/Cargo.toml b/Cargo.toml
index 41f8b5d651878..dd9f078bb9d45 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -43,6 +43,7 @@ uv-git-types = { path = "crates/uv-git-types" }
uv-globfilter = { path = "crates/uv-globfilter" }
uv-install-wheel = { path = "crates/uv-install-wheel", default-features = false }
uv-installer = { path = "crates/uv-installer" }
+uv-keyring = { path = "crates/uv-keyring" }
uv-macros = { path = "crates/uv-macros" }
uv-metadata = { path = "crates/uv-metadata" }
uv-normalize = { path = "crates/uv-normalize" }
diff --git a/crates/uv-auth/Cargo.toml b/crates/uv-auth/Cargo.toml
index cbe4d47874fe6..3af7a3f392b11 100644
--- a/crates/uv-auth/Cargo.toml
+++ b/crates/uv-auth/Cargo.toml
@@ -10,6 +10,7 @@ doctest = false
workspace = true
[dependencies]
+uv-keyring = { workspace = true, features = ["apple-native", "secret-service", "windows-native"] }
uv-once-map = { workspace = true }
uv-redacted = { workspace = true }
uv-small-str = { workspace = true }
diff --git a/crates/uv-auth/src/keyring.rs b/crates/uv-auth/src/keyring.rs
index 41b92114a37a4..7b21eda4650bf 100644
--- a/crates/uv-auth/src/keyring.rs
+++ b/crates/uv-auth/src/keyring.rs
@@ -1,11 +1,21 @@
-use std::{io::Write, process::Stdio};
+use rustc_hash::FxHashSet;
+use std::{
+ io::Write,
+ process::Stdio,
+ sync::{LazyLock, RwLock},
+};
use tokio::process::Command;
-use tracing::{instrument, trace, warn};
+use tracing::{debug, instrument, trace, warn};
use uv_redacted::DisplaySafeUrl;
use uv_warnings::warn_user_once;
use crate::credentials::Credentials;
+/// Keyring credentials that have been stored during an invocation of uv.
+static STORED_KEYRING_URLS: LazyLock = LazyLock::new(StoredKeyringUrls::new);
+/// Service name prefix for storing credentials in a keyring.
+static UV_SERVICE_PREFIX: &str = "uv-credentials:";
+
/// A backend for retrieving credentials from a keyring.
///
/// See pip's implementation for reference
@@ -17,13 +27,22 @@ pub struct KeyringProvider {
#[derive(Debug)]
pub(crate) enum KeyringProviderBackend {
- /// Use the `keyring` command to fetch credentials.
+ /// Use system keyring integration to fetch credentials.
+ Native,
+ /// Use the external `keyring` command to fetch credentials.
Subprocess,
#[cfg(test)]
Dummy(Vec<(String, &'static str, &'static str)>),
}
impl KeyringProvider {
+ /// Create a new [`KeyringProvider::Native`].
+ pub fn native() -> Self {
+ Self {
+ backend: KeyringProviderBackend::Native,
+ }
+ }
+
/// Create a new [`KeyringProvider::Subprocess`].
pub fn subprocess() -> Self {
Self {
@@ -31,6 +50,65 @@ impl KeyringProvider {
}
}
+ /// Store credentials for the given [`Url`] to the keyring if the
+ /// keyring provider backend is `Native`.
+ #[instrument(skip_all, fields(url = % url.to_string(), username))]
+ pub async fn store_if_native(&self, url: &DisplaySafeUrl, credentials: &Credentials) {
+ match &self.backend {
+ KeyringProviderBackend::Native => {
+ let Some(username) = credentials.username() else {
+ trace!(
+ "Unable to store credentials in keyring for {url} due to missing username"
+ );
+ return;
+ };
+ let Some(password) = credentials.password() else {
+ trace!(
+ "Unable to store credentials in keyring for {url} due to missing password"
+ );
+ return;
+ };
+
+ // Only store credentials if not already stored during this uv invocation.
+ if !STORED_KEYRING_URLS.contains(url) {
+ self.store_native(url.as_str(), username, password).await;
+ STORED_KEYRING_URLS.insert(url.clone());
+ }
+ }
+ KeyringProviderBackend::Subprocess => {
+ trace!("Storing credentials is not supported for `subprocess` keyring");
+ }
+ #[cfg(test)]
+ KeyringProviderBackend::Dummy(_) => {}
+ }
+ }
+
+ /// Store credentials to the system keyring for the given `service_name`/`username`
+ /// pair.
+ #[instrument(skip(self))]
+ async fn store_native(&self, service: &str, username: &str, password: &str) {
+ let prefixed_service = format!("{UV_SERVICE_PREFIX}{service}");
+ let entry = match uv_keyring::Entry::new(&prefixed_service, username) {
+ Ok(entry) => entry,
+ Err(err) => {
+ warn_user_once!(
+ "Unable to store credentials for {service} in the system keyring: {err}"
+ );
+ return;
+ }
+ };
+ match entry.set_password(password).await {
+ Ok(()) => {
+ debug!("Storing credentials for {service} in system keyring");
+ }
+ Err(err) => {
+ warn_user_once!(
+ "Unable to store credentials for {service} in the system keyring: {err}"
+ );
+ }
+ }
+ }
+
/// Fetch credentials for the given [`Url`] from the keyring.
///
/// Returns [`None`] if no password was found for the username or if any errors
@@ -55,6 +133,7 @@ impl KeyringProvider {
//
trace!("Checking keyring for URL {url}");
let mut credentials = match self.backend {
+ KeyringProviderBackend::Native => self.fetch_native(url.as_str(), username).await,
KeyringProviderBackend::Subprocess => {
self.fetch_subprocess(url.as_str(), username).await
}
@@ -72,6 +151,7 @@ impl KeyringProvider {
};
trace!("Checking keyring for host {host}");
credentials = match self.backend {
+ KeyringProviderBackend::Native => self.fetch_native(&host, username).await,
KeyringProviderBackend::Subprocess => self.fetch_subprocess(&host, username).await,
#[cfg(test)]
KeyringProviderBackend::Dummy(ref store) => {
@@ -175,6 +255,31 @@ impl KeyringProvider {
}
}
+ #[instrument(skip(self))]
+ async fn fetch_native(
+ &self,
+ service: &str,
+ username: Option<&str>,
+ ) -> Option<(String, String)> {
+ let prefixed_service = format!("{UV_SERVICE_PREFIX}{service}");
+ let username = username?;
+ if let Ok(entry) = uv_keyring::Entry::new(&prefixed_service, username) {
+ match entry.get_password().await {
+ Ok(password) => return Some((username.to_string(), password)),
+ Err(uv_keyring::Error::NoEntry) => {
+ debug!("No entry found in system keyring for {service}");
+ }
+ Err(err) => {
+ warn_user_once!(
+ "Unable to fetch credentials for {service} from system keyring: {}",
+ err
+ );
+ }
+ }
+ }
+ None
+ }
+
#[cfg(test)]
fn fetch_dummy(
store: &Vec<(String, &'static str, &'static str)>,
@@ -213,6 +318,23 @@ impl KeyringProvider {
}
}
+/// Keyring credentials that have been stored during an invocation of uv.
+struct StoredKeyringUrls(RwLock>);
+
+impl StoredKeyringUrls {
+ pub(crate) fn new() -> Self {
+ Self(RwLock::new(FxHashSet::default()))
+ }
+
+ pub(crate) fn contains(&self, url: &DisplaySafeUrl) -> bool {
+ self.0.read().unwrap().contains(url)
+ }
+
+ pub(crate) fn insert(&self, url: DisplaySafeUrl) -> bool {
+ self.0.write().unwrap().insert(url)
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/crates/uv-auth/src/middleware.rs b/crates/uv-auth/src/middleware.rs
index 28bbfb07acb73..0fd43c122bc02 100644
--- a/crates/uv-auth/src/middleware.rs
+++ b/crates/uv-auth/src/middleware.rs
@@ -217,7 +217,14 @@ impl Middleware for AuthMiddleware {
if credentials.password().is_some() {
trace!("Request for {url} is fully authenticated");
return self
- .complete_request(None, request, extensions, next, auth_policy)
+ .complete_request(
+ None,
+ request,
+ extensions,
+ next,
+ maybe_index_url,
+ auth_policy,
+ )
.await;
}
@@ -299,7 +306,14 @@ impl Middleware for AuthMiddleware {
trace!("Retrying request for {url} with credentials from cache {credentials:?}");
retry_request = credentials.authenticate(retry_request);
return self
- .complete_request(None, retry_request, extensions, next, auth_policy)
+ .complete_request(
+ None,
+ retry_request,
+ extensions,
+ next,
+ maybe_index_url,
+ auth_policy,
+ )
.await;
}
}
@@ -323,6 +337,7 @@ impl Middleware for AuthMiddleware {
retry_request,
extensions,
next,
+ maybe_index_url,
auth_policy,
)
.await;
@@ -333,7 +348,14 @@ impl Middleware for AuthMiddleware {
trace!("Retrying request for {url} with username from cache {credentials:?}");
retry_request = credentials.authenticate(retry_request);
return self
- .complete_request(None, retry_request, extensions, next, auth_policy)
+ .complete_request(
+ None,
+ retry_request,
+ extensions,
+ next,
+ maybe_index_url,
+ auth_policy,
+ )
.await;
}
}
@@ -358,6 +380,7 @@ impl AuthMiddleware {
request: Request,
extensions: &mut Extensions,
next: Next<'_>,
+ index_url: Option<&DisplaySafeUrl>,
auth_policy: AuthPolicy,
) -> reqwest_middleware::Result {
let Some(credentials) = credentials else {
@@ -375,6 +398,9 @@ impl AuthMiddleware {
.as_ref()
.is_ok_and(|response| response.error_for_status_ref().is_ok())
{
+ if let (Some(index_url), Some(keyring)) = (index_url, &self.keyring) {
+ keyring.store_if_native(index_url, &credentials).await;
+ }
trace!("Updating cached credentials for {url} to {credentials:?}");
self.cache().insert(&url, credentials);
}
@@ -399,7 +425,14 @@ impl AuthMiddleware {
if credentials.password().is_some() {
trace!("Request for {url} already contains username and password");
return self
- .complete_request(Some(credentials), request, extensions, next, auth_policy)
+ .complete_request(
+ Some(credentials),
+ request,
+ extensions,
+ next,
+ index_url,
+ auth_policy,
+ )
.await;
}
@@ -420,7 +453,14 @@ impl AuthMiddleware {
// Do not insert already-cached credentials
let credentials = None;
return self
- .complete_request(credentials, request, extensions, next, auth_policy)
+ .complete_request(
+ credentials,
+ request,
+ extensions,
+ next,
+ index_url,
+ auth_policy,
+ )
.await;
}
@@ -458,8 +498,15 @@ impl AuthMiddleware {
Some(credentials)
};
- self.complete_request(credentials, request, extensions, next, auth_policy)
- .await
+ self.complete_request(
+ credentials,
+ request,
+ extensions,
+ next,
+ index_url,
+ auth_policy,
+ )
+ .await
}
/// Fetch credentials for a URL.
diff --git a/crates/uv-configuration/src/authentication.rs b/crates/uv-configuration/src/authentication.rs
index a6773c81fa554..cc427130a1d61 100644
--- a/crates/uv-configuration/src/authentication.rs
+++ b/crates/uv-configuration/src/authentication.rs
@@ -9,6 +9,8 @@ pub enum KeyringProviderType {
/// Do not use keyring for credential lookup.
#[default]
Disabled,
+ /// Use the system keyring for credential lookup.
+ Native,
/// Use the `keyring` command for credential lookup.
Subprocess,
// /// Not yet implemented
@@ -22,6 +24,7 @@ impl KeyringProviderType {
pub fn to_provider(&self) -> Option {
match self {
Self::Disabled => None,
+ Self::Native => Some(KeyringProvider::native()),
Self::Subprocess => Some(KeyringProvider::subprocess()),
}
}
diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml
index 501763ad2c840..11b95eb196feb 100644
--- a/crates/uv/Cargo.toml
+++ b/crates/uv/Cargo.toml
@@ -149,6 +149,7 @@ ignored = [
[features]
default = ["performance", "uv-distribution/static", "default-tests"]
+keyring-tests = []
# Use better memory allocators, etc.
performance = ["performance-memory-allocator"]
performance-memory-allocator = ["dep:uv-performance-memory-allocator"]
diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs
index f627a5961fe43..be103de52ea20 100644
--- a/crates/uv/tests/it/edit.rs
+++ b/crates/uv/tests/it/edit.rs
@@ -12555,6 +12555,75 @@ fn add_auth_policy_never_without_credentials() -> Result<()> {
Ok(())
}
+#[cfg(feature = "keyring-tests")]
+#[test]
+fn add_package_persist_system_keyring_credentials() -> Result<()> {
+ let context = TestContext::new("3.12");
+
+ // Configure `pyproject.toml` with native keyring provider.
+ let pyproject_toml = context.temp_dir.child("pyproject.toml");
+ pyproject_toml.write_str(indoc! { r#"
+ [project]
+ name = "foo"
+ version = "1.0.0"
+ requires-python = ">=3.11, <4"
+ dependencies = []
+
+ [tool.uv]
+ keyring-provider = "native"
+ "#
+ })?;
+
+ // Try to add a package without credentials.
+ uv_snapshot!(context.add().arg("anyio").arg("--default-index").arg("https://public@pypi-proxy.fly.dev/basic-auth/simple"), @r"
+ success: false
+ exit_code: 1
+ ----- stdout -----
+
+ ----- stderr -----
+ × No solution found when resolving dependencies:
+ ╰─▶ Because anyio was not found in the package registry and your project depends on anyio, we can conclude that your project's requirements are unsatisfiable.
+
+ hint: An index URL (https://pypi-proxy.fly.dev/basic-auth/simple) could not be queried due to a lack of valid authentication credentials (401 Unauthorized).
+ help: If you want to add the package regardless of the failed resolution, provide the `--frozen` flag to skip locking and syncing.
+ "
+ );
+
+ // Add a different package with credentials. This should persist
+ // the credentials to the system keyring.
+ uv_snapshot!(context.add().arg("iniconfig==2.0.0").arg("--default-index").arg("https://public:heron@pypi-proxy.fly.dev/basic-auth/simple"), @r"
+ success: true
+ exit_code: 0
+ ----- stdout -----
+
+ ----- stderr -----
+ Resolved 2 packages in [TIME]
+ Prepared 1 package in [TIME]
+ Installed 1 package in [TIME]
+ + iniconfig==2.0.0
+ "
+ );
+
+ // Try to add the original package without credentials again. This should use
+ // credentials storied in the system keyring.
+ uv_snapshot!(context.add().arg("anyio").arg("--default-index").arg("https://public@pypi-proxy.fly.dev/basic-auth/simple"), @r"
+ success: true
+ exit_code: 0
+ ----- stdout -----
+
+ ----- stderr -----
+ Resolved 5 packages in [TIME]
+ Prepared 3 packages in [TIME]
+ Installed 3 packages in [TIME]
+ + anyio==4.3.0
+ + idna==3.6
+ + sniffio==1.3.1
+ "
+ );
+
+ Ok(())
+}
+
/// If uv receives a 302 redirect to a cross-origin server, it should not forward
/// credentials. In the absence of a netrc entry for the new location,
/// it should fail.
diff --git a/docs/concepts/authentication.md b/docs/concepts/authentication.md
index fe5314b85d444..379f0dec2098d 100644
--- a/docs/concepts/authentication.md
+++ b/docs/concepts/authentication.md
@@ -72,7 +72,7 @@ Authentication can come from the following sources, in order of precedence:
- The URL, e.g., `https://:@/...`
- A [`.netrc`](https://everything.curl.dev/usingcurl/netrc) configuration file
-- A [keyring](https://github.com/jaraco/keyring) provider (requires opt-in)
+- A keyring provider (requires opt-in)
If authentication is found for a single index URL or net location (scheme, host, and port), it will
be cached for the duration of the command and used for other queries to that index or net location.
@@ -81,8 +81,15 @@ Authentication is not cached across invocations of uv.
`.netrc` authentication is enabled by default, and will respect the `NETRC` environment variable if
defined, falling back to `~/.netrc` if not.
-To enable keyring-based authentication, pass the `--keyring-provider subprocess` command-line
-argument to uv, or set `UV_KEYRING_PROVIDER=subprocess`.
+To enable keyring-based authentication, pass a `--keyring-provider` command-line argument to uv, or
+set the `UV_KEYRING_PROVIDER` env var. uv supports two keyring provider options:
+
+- `--keyring provider subprocess` instructs uv to use the
+ [Python keyring plugin](https://github.com/jaraco/keyring).
+- `--keyring provider native` instructs uv to use the system keyring (Keychain Services on macOS,
+ Secret Service on Linux, and the Windows Credential Manager on Windows). Currently, this is used
+ to automatically persist credentials for an index after successful authentication. If the username
+ is provided for the index, uv will automatically fetch these credentials on future invocations.
Authentication may be used for hosts specified in the following contexts:
diff --git a/docs/reference/cli.md b/docs/reference/cli.md
index b24860531208f..5e419bb69d5da 100644
--- a/docs/reference/cli.md
+++ b/docs/reference/cli.md
@@ -149,6 +149,7 @@ uv run [OPTIONS] [COMMAND]
May also be set with the UV_KEYRING_PROVIDER environment variable.
Possible values:
disabled: Do not use keyring for credential lookup
+native: Use the system keyring for credential lookup
subprocess: Use the keyring command for credential lookup
--link-mode link-modeThe method to use when installing packages from the global cache.
Defaults to clone (also known as Copy-on-Write) on macOS, and hardlink on Linux and Windows.
@@ -508,6 +509,7 @@ uv add [OPTIONS] >
May also be set with the UV_KEYRING_PROVIDER environment variable.
Possible values:
disabled: Do not use keyring for credential lookup
+native: Use the system keyring for credential lookup
subprocess: Use the keyring command for credential lookup
--link-mode link-modeThe method to use when installing packages from the global cache.
Defaults to clone (also known as Copy-on-Write) on macOS, and hardlink on Linux and Windows.
@@ -706,6 +708,7 @@ uv remove [OPTIONS] ...
May also be set with the UV_KEYRING_PROVIDER environment variable.
Possible values:
disabled: Do not use keyring for credential lookup
+native: Use the system keyring for credential lookup
subprocess: Use the keyring command for credential lookup
--link-mode link-modeThe method to use when installing packages from the global cache.
Defaults to clone (also known as Copy-on-Write) on macOS, and hardlink on Linux and Windows.
@@ -888,6 +891,7 @@ uv version [OPTIONS] [VALUE]
May also be set with the UV_KEYRING_PROVIDER environment variable.
Possible values:
disabled: Do not use keyring for credential lookup
+native: Use the system keyring for credential lookup
subprocess: Use the keyring command for credential lookup
--link-mode link-modeThe method to use when installing packages from the global cache.
Defaults to clone (also known as Copy-on-Write) on macOS, and hardlink on Linux and Windows.
@@ -1086,6 +1090,7 @@ uv sync [OPTIONS]
May also be set with the UV_KEYRING_PROVIDER environment variable.
Possible values:
disabled: Do not use keyring for credential lookup
+native: Use the system keyring for credential lookup
subprocess: Use the keyring command for credential lookup
--link-mode link-modeThe method to use when installing packages from the global cache.
Defaults to clone (also known as Copy-on-Write) on macOS, and hardlink on Linux and Windows.
@@ -1328,6 +1333,7 @@ uv lock [OPTIONS]
May also be set with the UV_KEYRING_PROVIDER environment variable.
Possible values:
disabled: Do not use keyring for credential lookup
+native: Use the system keyring for credential lookup
subprocess: Use the keyring command for credential lookup
--link-mode link-modeThe method to use when installing packages from the global cache.
This option is only used when building source distributions.
@@ -1507,6 +1513,7 @@ uv export [OPTIONS]
May also be set with the UV_KEYRING_PROVIDER environment variable.
Possible values:
disabled: Do not use keyring for credential lookup
+native: Use the system keyring for credential lookup
subprocess: Use the keyring command for credential lookup
--link-mode link-modeThe method to use when installing packages from the global cache.
This option is only used when building source distributions.
@@ -1699,6 +1706,7 @@ uv tree [OPTIONS]
May also be set with the UV_KEYRING_PROVIDER environment variable.
Possible values:
disabled: Do not use keyring for credential lookup
+native: Use the system keyring for credential lookup
subprocess: Use the keyring command for credential lookup
--link-mode link-modeThe method to use when installing packages from the global cache.
This option is only used when building source distributions.
@@ -2040,6 +2048,7 @@ uv tool run [OPTIONS] [COMMAND]
May also be set with the UV_KEYRING_PROVIDER environment variable.
Possible values:
disabled: Do not use keyring for credential lookup
+native: Use the system keyring for credential lookup
subprocess: Use the keyring command for credential lookup
--link-mode link-modeThe method to use when installing packages from the global cache.
Defaults to clone (also known as Copy-on-Write) on macOS, and hardlink on Linux and Windows.
@@ -2215,6 +2224,7 @@ uv tool install [OPTIONS]
May also be set with the UV_KEYRING_PROVIDER environment variable.
Possible values:
disabled: Do not use keyring for credential lookup
+native: Use the system keyring for credential lookup
subprocess: Use the keyring command for credential lookup
--link-mode link-modeThe method to use when installing packages from the global cache.
Defaults to clone (also known as Copy-on-Write) on macOS, and hardlink on Linux and Windows.
@@ -2382,6 +2392,7 @@ uv tool upgrade [OPTIONS] ...
May also be set with the UV_KEYRING_PROVIDER environment variable.
Possible values:
disabled: Do not use keyring for credential lookup
+native: Use the system keyring for credential lookup
subprocess: Use the keyring command for credential lookup
--link-mode link-modeThe method to use when installing packages from the global cache.
Defaults to clone (also known as Copy-on-Write) on macOS, and hardlink on Linux and Windows.
@@ -3556,6 +3567,7 @@ uv pip compile [OPTIONS] >
May also be set with the UV_KEYRING_PROVIDER environment variable.
Possible values:
disabled: Do not use keyring for credential lookup
+native: Use the system keyring for credential lookup
subprocess: Use the keyring command for credential lookup
--link-mode link-modeThe method to use when installing packages from the global cache.
This option is only used when building source distributions.
@@ -3850,6 +3862,7 @@ uv pip sync [OPTIONS] ...
May also be set with the UV_KEYRING_PROVIDER environment variable.
Possible values:
disabled: Do not use keyring for credential lookup
+native: Use the system keyring for credential lookup
subprocess: Use the keyring command for credential lookup
--link-mode link-modeThe method to use when installing packages from the global cache.
Defaults to clone (also known as Copy-on-Write) on macOS, and hardlink on Linux and Windows.
@@ -4122,6 +4135,7 @@ uv pip install [OPTIONS] |--editable May also be set with the UV_KEYRING_PROVIDER environment variable.
Possible values:
disabled: Do not use keyring for credential lookup
+native: Use the system keyring for credential lookup
subprocess: Use the keyring command for credential lookup
--link-mode link-modeThe method to use when installing packages from the global cache.
Defaults to clone (also known as Copy-on-Write) on macOS, and hardlink on Linux and Windows.
@@ -4367,6 +4381,7 @@ uv pip uninstall [OPTIONS] >
May also be set with the UV_KEYRING_PROVIDER environment variable.
Possible values:
disabled: Do not use keyring for credential lookup
+native: Use the system keyring for credential lookup
subprocess: Use the keyring command for credential lookup
--managed-pythonRequire use of uv-managed Python versions.
By default, uv prefers using Python versions it manages. However, it will use system Python versions if a uv-managed Python is not installed. This option disables use of system Python versions.
@@ -4545,6 +4560,7 @@ uv pip list [OPTIONS]
May also be set with the UV_KEYRING_PROVIDER environment variable.
Possible values:
disabled: Do not use keyring for credential lookup
+native: Use the system keyring for credential lookup
subprocess: Use the keyring command for credential lookup
--managed-pythonRequire use of uv-managed Python versions.
By default, uv prefers using Python versions it manages. However, it will use system Python versions if a uv-managed Python is not installed. This option disables use of system Python versions.
@@ -4720,6 +4736,7 @@ uv pip tree [OPTIONS]
May also be set with the UV_KEYRING_PROVIDER environment variable.
Possible values:
disabled: Do not use keyring for credential lookup
+native: Use the system keyring for credential lookup
subprocess: Use the keyring command for credential lookup
--managed-pythonRequire use of uv-managed Python versions.
By default, uv prefers using Python versions it manages. However, it will use system Python versions if a uv-managed Python is not installed. This option disables use of system Python versions.
@@ -4912,6 +4929,7 @@ uv venv [OPTIONS] [PATH]
May also be set with the UV_KEYRING_PROVIDER environment variable.
Possible values:
disabled: Do not use keyring for credential lookup
+native: Use the system keyring for credential lookup
subprocess: Use the keyring command for credential lookup
--link-mode link-modeThe method to use when installing packages from the global cache.
This option is only used for installing seed packages.
@@ -5068,6 +5086,7 @@ uv build [OPTIONS] [SRC]
May also be set with the UV_KEYRING_PROVIDER environment variable.
Possible values:
disabled: Do not use keyring for credential lookup
+native: Use the system keyring for credential lookup
subprocess: Use the keyring command for credential lookup
--link-mode link-modeThe method to use when installing packages from the global cache.
This option is only used when building source distributions.
@@ -5220,6 +5239,7 @@ uv publish --publish-url https://upload.pypi.org/legacy/ --check-url https://pyp
May also be set with the UV_KEYRING_PROVIDER environment variable.
Possible values:
disabled: Do not use keyring for credential lookup
+native: Use the system keyring for credential lookup
subprocess: Use the keyring command for credential lookup
--managed-pythonRequire use of uv-managed Python versions.
By default, uv prefers using Python versions it manages. However, it will use system Python versions if a uv-managed Python is not installed. This option disables use of system Python versions.
diff --git a/uv.schema.json b/uv.schema.json
index 6deddd4be8442..710c28167b5a2 100644
--- a/uv.schema.json
+++ b/uv.schema.json
@@ -1144,6 +1144,11 @@
"type": "string",
"const": "disabled"
},
+ {
+ "description": "Use the system keyring for credential lookup.",
+ "type": "string",
+ "const": "native"
+ },
{
"description": "Use the `keyring` command for credential lookup.",
"type": "string",