Add support for --trusted-host (#6591)

## Summary

This PR revives https://github.com/astral-sh/uv/pull/4944, which I think
was a good start towards adding `--trusted-host`. Last night, I tried to
add `--trusted-host` with a custom verifier, but we had to vendor a lot
of `reqwest` code and I eventually hit some private APIs. I'm not
confident that I can implement it correctly with that mechanism, and
since this is security, correctness is the priority.

So, instead, we now use two clients and multiplex between them.

Closes https://github.com/astral-sh/uv/issues/1339.

## Test Plan

Created self-signed certificate, and ran `python3 -m http.server --bind
127.0.0.1 4443 --directory . --certfile cert.pem --keyfile key.pem` from
the packse index directory.

Verified that `cargo run pip install
transitive-yanked-and-unyanked-dependency-a-0abad3b6 --index-url
https://127.0.0.1:8443/simple-html` failed with:

```
error: Request failed after 3 retries
  Caused by: error sending request for url (https://127.0.0.1:8443/simple-html/transitive-yanked-and-unyanked-dependency-a-0abad3b6/)
  Caused by: client error (Connect)
  Caused by: invalid peer certificate: Other(OtherError(CaUsedAsEndEntity))
```

Verified that `cargo run pip install
transitive-yanked-and-unyanked-dependency-a-0abad3b6 --index-url
'https://127.0.0.1:8443/simple-html' --trusted-host '127.0.0.1:8443'`
failed with the expected error (invalid resolution) and made valid
requests.

Verified that `cargo run pip install
transitive-yanked-and-unyanked-dependency-a-0abad3b6 --index-url
'https://127.0.0.1:8443/simple-html' --trusted-host '127.0.0.2' -n` also
failed.
This commit is contained in:
Charlie Marsh 2024-08-27 09:36:50 -04:00 committed by GitHub
parent ce749591de
commit d86075fc1e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 808 additions and 125 deletions

2
Cargo.lock generated
View file

@ -4774,7 +4774,9 @@ dependencies = [
"schemars",
"serde",
"serde_json",
"thiserror",
"tracing",
"url",
"uv-auth",
"uv-cache",
"uv-normalize",

View file

@ -39,9 +39,6 @@ pub struct PipCompileCompatArgs {
#[clap(long, hide = true)]
client_cert: Option<String>,
#[clap(long, hide = true)]
trusted_host: Option<String>,
#[clap(long, hide = true)]
emit_trusted_host: bool,
@ -118,15 +115,9 @@ impl CompatArgs for PipCompileCompatArgs {
));
}
if self.trusted_host.is_some() {
return Err(anyhow!(
"pip-compile's `--trusted-host` is unsupported (uv always requires HTTPS)"
));
}
if self.emit_trusted_host {
return Err(anyhow!(
"pip-compile's `--emit-trusted-host` is unsupported (uv always requires HTTPS)"
"pip-compile's `--emit-trusted-host` is unsupported"
));
}
@ -209,9 +200,6 @@ pub struct PipSyncCompatArgs {
#[clap(short, long, hide = true)]
ask: bool,
#[clap(long, hide = true)]
trusted_host: Option<String>,
#[clap(long, hide = true)]
python_executable: Option<String>,
@ -265,12 +253,6 @@ impl CompatArgs for PipSyncCompatArgs {
));
}
if self.trusted_host.is_some() {
return Err(anyhow!(
"pip-sync's `--trusted-host` is unsupported (uv always requires HTTPS)"
));
}
if self.config.is_some() {
return Err(anyhow!(
"pip-sync's `--config` is unsupported (uv does not use a configuration file)"

View file

@ -6,13 +6,13 @@ use std::str::FromStr;
use anyhow::{anyhow, Result};
use clap::builder::styling::Style;
use clap::{Args, Parser, Subcommand};
use distribution_types::{FlatIndexLocation, IndexUrl};
use pep508_rs::Requirement;
use pypi_types::VerbatimParsedUrl;
use uv_cache::CacheArgs;
use uv_configuration::{
ConfigSettingEntry, IndexStrategy, KeyringProviderType, PackageNameSpecifier, TargetTriple,
TrustedHost,
};
use uv_normalize::{ExtraName, PackageName};
use uv_python::{PythonDownloads, PythonPreference, PythonVersion};
@ -678,6 +678,18 @@ fn parse_index_url(input: &str) -> Result<Maybe<IndexUrl>, String> {
}
}
/// Parse a string into an [`Url`], mapping the empty string to `None`.
fn parse_insecure_host(input: &str) -> Result<Maybe<TrustedHost>, String> {
if input.is_empty() {
Ok(Maybe::None)
} else {
match TrustedHost::from_str(input) {
Ok(host) => Ok(Maybe::Some(host)),
Err(err) => Err(err.to_string()),
}
}
}
/// Parse a string into a [`PathBuf`]. The string can represent a file, either as a path or a
/// `file://` URL.
fn parse_file_path(input: &str) -> Result<PathBuf, String> {
@ -1559,6 +1571,25 @@ pub struct PipUninstallArgs {
#[arg(long, value_enum, env = "UV_KEYRING_PROVIDER")]
pub keyring_provider: Option<KeyringProviderType>,
/// Allow insecure connections to a host.
///
/// Can be provided multiple times.
///
/// Expects to receive either a hostname (e.g., `localhost`), a host-port pair (e.g.,
/// `localhost:8080`), or a URL (e.g., `https://localhost`).
///
/// WARNING: Hosts included in this list will not be verified against the system's certificate
/// store. Only use `--allow-insecure-host` in a secure network with verified sources, as it
/// bypasses SSL verification and could expose you to MITM attacks.
#[arg(
long,
alias = "trusted-host",
env = "UV_INSECURE_HOST",
value_delimiter = ' ',
value_parser = parse_insecure_host,
)]
pub allow_insecure_host: Option<Vec<Maybe<TrustedHost>>>,
/// Use the system Python to uninstall packages.
///
/// By default, uv uninstalls from the virtual environment in the current working directory or
@ -1985,6 +2016,25 @@ pub struct VenvArgs {
#[arg(long, value_enum, env = "UV_KEYRING_PROVIDER")]
pub keyring_provider: Option<KeyringProviderType>,
/// Allow insecure connections to a host.
///
/// Can be provided multiple times.
///
/// Expects to receive either a hostname (e.g., `localhost`), a host-port pair (e.g.,
/// `localhost:8080`), or a URL (e.g., `https://localhost`).
///
/// WARNING: Hosts included in this list will not be verified against the system's certificate
/// store. Only use `--allow-insecure-host` in a secure network with verified sources, as it
/// bypasses SSL verification and could expose you to MITM attacks.
#[arg(
long,
alias = "trusted-host",
env = "UV_INSECURE_HOST",
value_delimiter = ' ',
value_parser = parse_insecure_host,
)]
pub allow_insecure_host: Option<Vec<Maybe<TrustedHost>>>,
/// Limit candidate packages to those that were uploaded prior to the given date.
///
/// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and local dates in the same
@ -3321,6 +3371,26 @@ pub struct InstallerArgs {
)]
pub keyring_provider: Option<KeyringProviderType>,
/// Allow insecure connections to a host.
///
/// Can be provided multiple times.
///
/// Expects to receive either a hostname (e.g., `localhost`), a host-port pair (e.g.,
/// `localhost:8080`), or a URL (e.g., `https://localhost`).
///
/// WARNING: Hosts included in this list will not be verified against the system's certificate
/// store. Only use `--allow-insecure-host` in a secure network with verified sources, as it
/// bypasses SSL verification and could expose you to MITM attacks.
#[arg(
long,
alias = "trusted-host",
env = "UV_INSECURE_HOST",
value_delimiter = ' ',
value_parser = parse_insecure_host,
help_heading = "Index options"
)]
pub allow_insecure_host: Option<Vec<Maybe<TrustedHost>>>,
/// Settings to pass to the PEP 517 build backend, specified as `KEY=VALUE` pairs.
#[arg(
long,
@ -3463,6 +3533,26 @@ pub struct ResolverArgs {
)]
pub keyring_provider: Option<KeyringProviderType>,
/// Allow insecure connections to a host.
///
/// Can be provided multiple times.
///
/// Expects to receive either a hostname (e.g., `localhost`), a host-port pair (e.g.,
/// `localhost:8080`), or a URL (e.g., `https://localhost`).
///
/// WARNING: Hosts included in this list will not be verified against the system's certificate
/// store. Only use `--allow-insecure-host` in a secure network with verified sources, as it
/// bypasses SSL verification and could expose you to MITM attacks.
#[arg(
long,
alias = "trusted-host",
env = "UV_INSECURE_HOST",
value_delimiter = ' ',
value_parser = parse_insecure_host,
help_heading = "Index options"
)]
pub allow_insecure_host: Option<Vec<Maybe<TrustedHost>>>,
/// The strategy to use when selecting between the different compatible versions for a given
/// package requirement.
///
@ -3635,6 +3725,26 @@ pub struct ResolverInstallerArgs {
)]
pub keyring_provider: Option<KeyringProviderType>,
/// Allow insecure connections to a host.
///
/// Can be provided multiple times.
///
/// Expects to receive either a hostname (e.g., `localhost`), a host-port pair (e.g.,
/// `localhost:8080`), or a URL (e.g., `https://localhost`).
///
/// WARNING: Hosts included in this list will not be verified against the system's certificate
/// store. Only use `--allow-insecure-host` in a secure network with verified sources, as it
/// bypasses SSL verification and could expose you to MITM attacks.
#[arg(
long,
alias = "trusted-host",
env = "UV_INSECURE_HOST",
value_delimiter = ' ',
value_parser = parse_insecure_host,
help_heading = "Index options"
)]
pub allow_insecure_host: Option<Vec<Maybe<TrustedHost>>>,
/// The strategy to use when selecting between the different compatible versions for a given
/// package requirement.
///

View file

@ -38,6 +38,7 @@ impl From<ResolverArgs> for PipOptions {
upgrade_package,
index_strategy,
keyring_provider,
allow_insecure_host,
resolution,
prerelease,
pre,
@ -55,6 +56,12 @@ impl From<ResolverArgs> for PipOptions {
upgrade_package: Some(upgrade_package),
index_strategy,
keyring_provider,
allow_insecure_host: allow_insecure_host.map(|allow_insecure_host| {
allow_insecure_host
.into_iter()
.filter_map(Maybe::into_option)
.collect()
}),
resolution,
prerelease: if pre {
Some(PrereleaseMode::Allow)
@ -82,6 +89,7 @@ impl From<InstallerArgs> for PipOptions {
reinstall_package,
index_strategy,
keyring_provider,
allow_insecure_host,
config_setting,
no_build_isolation,
build_isolation,
@ -97,6 +105,12 @@ impl From<InstallerArgs> for PipOptions {
reinstall_package: Some(reinstall_package),
index_strategy,
keyring_provider,
allow_insecure_host: allow_insecure_host.map(|allow_insecure_host| {
allow_insecure_host
.into_iter()
.filter_map(Maybe::into_option)
.collect()
}),
config_settings: config_setting
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
no_build_isolation: flag(no_build_isolation, build_isolation),
@ -121,6 +135,7 @@ impl From<ResolverInstallerArgs> for PipOptions {
reinstall_package,
index_strategy,
keyring_provider,
allow_insecure_host,
resolution,
prerelease,
pre,
@ -142,6 +157,12 @@ impl From<ResolverInstallerArgs> for PipOptions {
reinstall_package: Some(reinstall_package),
index_strategy,
keyring_provider,
allow_insecure_host: allow_insecure_host.map(|allow_insecure_host| {
allow_insecure_host
.into_iter()
.filter_map(Maybe::into_option)
.collect()
}),
resolution,
prerelease: if pre {
Some(PrereleaseMode::Allow)
@ -194,6 +215,7 @@ pub fn resolver_options(resolver_args: ResolverArgs, build_args: BuildArgs) -> R
upgrade_package,
index_strategy,
keyring_provider,
allow_insecure_host,
resolution,
prerelease,
pre,
@ -233,6 +255,12 @@ pub fn resolver_options(resolver_args: ResolverArgs, build_args: BuildArgs) -> R
upgrade_package: Some(upgrade_package),
index_strategy,
keyring_provider,
allow_insecure_host: allow_insecure_host.map(|allow_insecure_host| {
allow_insecure_host
.into_iter()
.filter_map(Maybe::into_option)
.collect()
}),
resolution,
prerelease: if pre {
Some(PrereleaseMode::Allow)
@ -268,6 +296,7 @@ pub fn resolver_installer_options(
reinstall_package,
index_strategy,
keyring_provider,
allow_insecure_host,
resolution,
prerelease,
pre,
@ -319,6 +348,12 @@ pub fn resolver_installer_options(
},
index_strategy,
keyring_provider,
allow_insecure_host: allow_insecure_host.map(|allow_insecure_host| {
allow_insecure_host
.into_iter()
.filter_map(Maybe::into_option)
.collect()
}),
resolution,
prerelease: if pre {
Some(PrereleaseMode::Allow)

View file

@ -1,10 +1,11 @@
use std::error::Error;
use std::fmt::Debug;
use std::ops::Deref;
use std::path::Path;
use std::{env, iter};
use itertools::Itertools;
use pep508_rs::MarkerEnvironment;
use platform_tags::Platform;
use reqwest::{Client, ClientBuilder, Response};
use reqwest_middleware::ClientWithMiddleware;
use reqwest_retry::policies::ExponentialBackoff;
@ -12,11 +13,9 @@ use reqwest_retry::{
DefaultRetryableStrategy, RetryTransientMiddleware, Retryable, RetryableStrategy,
};
use tracing::debug;
use pep508_rs::MarkerEnvironment;
use platform_tags::Platform;
use url::Url;
use uv_auth::AuthMiddleware;
use uv_configuration::KeyringProviderType;
use uv_configuration::{KeyringProviderType, TrustedHost};
use uv_fs::Simplified;
use uv_version::version;
use uv_warnings::warn_user_once;
@ -30,6 +29,7 @@ use crate::Connectivity;
#[derive(Debug, Clone)]
pub struct BaseClientBuilder<'a> {
keyring: KeyringProviderType,
allow_insecure_host: Vec<TrustedHost>,
native_tls: bool,
retries: u32,
pub connectivity: Connectivity,
@ -48,6 +48,7 @@ impl BaseClientBuilder<'_> {
pub fn new() -> Self {
Self {
keyring: KeyringProviderType::default(),
allow_insecure_host: vec![],
native_tls: false,
connectivity: Connectivity::Online,
retries: 3,
@ -65,6 +66,12 @@ impl<'a> BaseClientBuilder<'a> {
self
}
#[must_use]
pub fn allow_insecure_host(mut self, allow_insecure_host: Vec<TrustedHost>) -> Self {
self.allow_insecure_host = allow_insecure_host;
self
}
#[must_use]
pub fn connectivity(mut self, connectivity: Connectivity) -> Self {
self.connectivity = connectivity;
@ -117,6 +124,18 @@ impl<'a> BaseClientBuilder<'a> {
}
}
// Check for the presence of an `SSL_CERT_FILE`.
let ssl_cert_file_exists = env::var_os("SSL_CERT_FILE").is_some_and(|path| {
let path_exists = Path::new(&path).exists();
if !path_exists {
warn_user_once!(
"Ignoring invalid `SSL_CERT_FILE`. File does not exist: {}.",
path.simplified_display().cyan()
);
}
path_exists
});
// Timeout options, matching https://doc.rust-lang.org/nightly/cargo/reference/config.html#httptimeout
// `UV_REQUEST_TIMEOUT` is provided for backwards compatibility with v0.1.6
let default_timeout = 30;
@ -134,54 +153,83 @@ impl<'a> BaseClientBuilder<'a> {
.unwrap_or(default_timeout);
debug!("Using request timeout of {timeout}s");
// Initialize the base client.
let client = self.client.clone().unwrap_or_else(|| {
// Check for the presence of an `SSL_CERT_FILE`.
let ssl_cert_file_exists = env::var_os("SSL_CERT_FILE").is_some_and(|path| {
let path_exists = Path::new(&path).exists();
if !path_exists {
warn_user_once!(
"Ignoring invalid `SSL_CERT_FILE`. File does not exist: {}.",
path.simplified_display().cyan()
);
// Create a secure client that validates certificates.
let client = self.create_client(
&user_agent_string,
timeout,
ssl_cert_file_exists,
Security::Secure,
);
// Create an insecure client that accepts invalid certificates.
let dangerous_client = self.create_client(
&user_agent_string,
timeout,
ssl_cert_file_exists,
Security::Insecure,
);
// Wrap in any relevant middleware and handle connectivity.
let client = self.apply_middleware(client);
let dangerous_client = self.apply_middleware(dangerous_client);
BaseClient {
connectivity: self.connectivity,
allow_insecure_host: self.allow_insecure_host.clone(),
client,
dangerous_client,
timeout,
}
}
fn create_client(
&self,
user_agent: &str,
timeout: u64,
ssl_cert_file_exists: bool,
security: Security,
) -> Client {
// Configure the builder.
let client_builder = ClientBuilder::new()
.user_agent(user_agent)
.pool_max_idle_per_host(20)
.read_timeout(std::time::Duration::from_secs(timeout))
.tls_built_in_root_certs(false);
// If necessary, accept invalid certificates.
let client_builder = match security {
Security::Secure => client_builder,
Security::Insecure => client_builder.danger_accept_invalid_certs(true),
};
let client_builder = if self.native_tls || ssl_cert_file_exists {
client_builder.tls_built_in_native_certs(true)
} else {
client_builder.tls_built_in_webpki_certs(true)
};
// Configure mTLS.
let client_builder = if let Some(ssl_client_cert) = env::var_os("SSL_CLIENT_CERT") {
match read_identity(&ssl_client_cert) {
Ok(identity) => client_builder.identity(identity),
Err(err) => {
warn_user_once!("Ignoring invalid `SSL_CLIENT_CERT`: {err}");
client_builder
}
path_exists
});
}
} else {
client_builder
};
// Configure the builder.
let client_core = ClientBuilder::new()
.user_agent(user_agent_string)
.pool_max_idle_per_host(20)
.read_timeout(std::time::Duration::from_secs(timeout))
.tls_built_in_root_certs(false);
client_builder
.build()
.expect("Failed to build HTTP client.")
}
// Configure TLS.
let client_core = if self.native_tls || ssl_cert_file_exists {
client_core.tls_built_in_native_certs(true)
} else {
client_core.tls_built_in_webpki_certs(true)
};
// Configure mTLS.
let client_core = if let Some(ssl_client_cert) = env::var_os("SSL_CLIENT_CERT") {
match read_identity(&ssl_client_cert) {
Ok(identity) => client_core.identity(identity),
Err(err) => {
warn_user_once!("Ignoring invalid `SSL_CLIENT_CERT`: {err}");
client_core
}
}
} else {
client_core
};
client_core.build().expect("Failed to build HTTP client")
});
// Wrap in any relevant middleware.
let client = match self.connectivity {
fn apply_middleware(&self, client: Client) -> ClientWithMiddleware {
match self.connectivity {
Connectivity::Online => {
let client = reqwest_middleware::ClientBuilder::new(client.clone());
let client = reqwest_middleware::ClientBuilder::new(client);
// Initialize the retry strategy.
let retry_policy =
@ -198,15 +246,9 @@ impl<'a> BaseClientBuilder<'a> {
client.build()
}
Connectivity::Offline => reqwest_middleware::ClientBuilder::new(client.clone())
Connectivity::Offline => reqwest_middleware::ClientBuilder::new(client)
.with(OfflineMiddleware)
.build(),
};
BaseClient {
connectivity: self.connectivity,
client,
timeout,
}
}
}
@ -214,20 +256,45 @@ impl<'a> BaseClientBuilder<'a> {
/// A base client for HTTP requests
#[derive(Debug, Clone)]
pub struct BaseClient {
/// The underlying HTTP client.
/// The underlying HTTP client that enforces valid certificates.
client: ClientWithMiddleware,
/// The underlying HTTP client that accepts invalid certificates.
dangerous_client: ClientWithMiddleware,
/// The connectivity mode to use.
connectivity: Connectivity,
/// Configured client timeout, in seconds.
timeout: u64,
/// Hosts that are trusted to use the insecure client.
allow_insecure_host: Vec<TrustedHost>,
}
#[derive(Debug, Clone, Copy)]
enum Security {
/// The client should use secure settings, i.e., valid certificates.
Secure,
/// The client should use insecure settings, i.e., skip certificate validation.
Insecure,
}
impl BaseClient {
/// The underlying [`ClientWithMiddleware`].
/// The underlying [`ClientWithMiddleware`] for secure requests.
pub fn client(&self) -> ClientWithMiddleware {
self.client.clone()
}
/// Selects the appropriate client based on the host's trustworthiness.
pub fn for_host(&self, url: &Url) -> &ClientWithMiddleware {
if self
.allow_insecure_host
.iter()
.any(|allow_insecure_host| allow_insecure_host.matches(url))
{
&self.dangerous_client
} else {
&self.client
}
}
/// The configured client timeout, in seconds.
pub fn timeout(&self) -> u64 {
self.timeout
@ -239,16 +306,6 @@ impl BaseClient {
}
}
// To avoid excessively verbose call chains, as the [`BaseClient`] is often nested within other client types.
impl Deref for BaseClient {
type Target = ClientWithMiddleware;
/// Deference to the underlying [`ClientWithMiddleware`].
fn deref(&self) -> &Self::Target {
&self.client
}
}
/// Extends [`DefaultRetryableStrategy`], to log transient request failures and additional retry cases.
struct UvRetryableStrategy;

View file

@ -165,9 +165,9 @@ impl CachedClient {
Self(client)
}
/// The base client
pub fn uncached(&self) -> BaseClient {
self.0.clone()
/// The underlying [`BaseClient`] without caching.
pub fn uncached(&self) -> &BaseClient {
&self.0
}
/// Make a cached request with a custom response transformation
@ -460,6 +460,7 @@ impl CachedClient {
debug!("Sending revalidation request for: {url}");
let response = self
.0
.for_host(req.url())
.execute(req)
.instrument(info_span!("revalidation_request", url = url.as_str()))
.await
@ -499,6 +500,7 @@ impl CachedClient {
let cache_policy_builder = CachePolicyBuilder::new(&req);
let response = self
.0
.for_host(req.url())
.execute(req)
.await
.map_err(ErrorKind::from)?

View file

@ -154,7 +154,7 @@ impl<'a> FlatIndexClient<'a> {
let flat_index_request = self
.client
.uncached_client()
.uncached_client(url)
.get(url.clone())
.header("Accept-Encoding", "gzip")
.header("Accept", "text/html")

View file

@ -7,6 +7,7 @@ use async_http_range_reader::AsyncHttpRangeReader;
use futures::{FutureExt, TryStreamExt};
use http::HeaderMap;
use reqwest::{Client, Response, StatusCode};
use reqwest_middleware::ClientWithMiddleware;
use serde::{Deserialize, Serialize};
use tokio::io::AsyncReadExt;
use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt};
@ -21,11 +22,11 @@ use pep508_rs::MarkerEnvironment;
use platform_tags::Platform;
use pypi_types::{Metadata23, SimpleJson};
use uv_cache::{Cache, CacheBucket, CacheEntry, WheelCache};
use uv_configuration::IndexStrategy;
use uv_configuration::KeyringProviderType;
use uv_configuration::{IndexStrategy, TrustedHost};
use uv_normalize::PackageName;
use crate::base_client::{BaseClient, BaseClientBuilder};
use crate::base_client::BaseClientBuilder;
use crate::cached_client::CacheControl;
use crate::html::SimpleHtml;
use crate::remote_metadata::wheel_metadata_from_remote_zip;
@ -71,6 +72,14 @@ impl<'a> RegistryClientBuilder<'a> {
self
}
#[must_use]
pub fn allow_insecure_host(mut self, allow_insecure_host: Vec<TrustedHost>) -> Self {
self.base_client_builder = self
.base_client_builder
.allow_insecure_host(allow_insecure_host);
self
}
#[must_use]
pub fn connectivity(mut self, connectivity: Connectivity) -> Self {
self.base_client_builder = self.base_client_builder.connectivity(connectivity);
@ -171,8 +180,8 @@ impl RegistryClient {
}
/// Return the [`BaseClient`] used by this client.
pub fn uncached_client(&self) -> BaseClient {
self.client.uncached()
pub fn uncached_client(&self, url: &Url) -> &ClientWithMiddleware {
self.client.uncached().for_host(url)
}
/// Return the [`Connectivity`] mode used by this client.
@ -298,7 +307,7 @@ impl RegistryClient {
cache_control: CacheControl,
) -> Result<OwnedArchive<SimpleMetadata>, Error> {
let simple_request = self
.uncached_client()
.uncached_client(url)
.get(url.clone())
.header("Accept-Encoding", "gzip")
.header("Accept", MediaType::accepts())
@ -512,7 +521,7 @@ impl RegistryClient {
})
};
let req = self
.uncached_client()
.uncached_client(&url)
.get(url.clone())
.build()
.map_err(ErrorKind::from)?;
@ -551,7 +560,7 @@ impl RegistryClient {
};
let req = self
.uncached_client()
.uncached_client(url)
.head(url.clone())
.header(
"accept-encoding",
@ -571,7 +580,7 @@ impl RegistryClient {
let read_metadata_range_request = |response: Response| {
async {
let mut reader = AsyncHttpRangeReader::from_head_response(
self.uncached_client().client(),
self.uncached_client(url).clone(),
response,
url.clone(),
headers,
@ -619,7 +628,7 @@ impl RegistryClient {
// Create a request to stream the file.
let req = self
.uncached_client()
.uncached_client(url)
.get(url.clone())
.header(
// `reqwest` defaults to accepting compressed responses.

View file

@ -56,6 +56,7 @@ async fn test_user_agent_has_version() -> Result<()> {
let res = client
.cached_client()
.uncached()
.client()
.get(format!("http://{addr}"))
.send()
.await?;
@ -151,6 +152,7 @@ async fn test_user_agent_has_linehaul() -> Result<()> {
let res = client
.cached_client()
.uncached()
.client()
.get(format!("http://{addr}"))
.send()
.await?;

View file

@ -28,7 +28,9 @@ rustc-hash = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
url = { workspace = true }
[dev-dependencies]
anyhow = { workspace = true }

View file

@ -12,6 +12,7 @@ pub use package_options::*;
pub use preview::*;
pub use sources::*;
pub use target_triple::*;
pub use trusted_host::*;
mod authentication;
mod build_options;
@ -27,3 +28,4 @@ mod package_options;
mod preview;
mod sources;
mod target_triple;
mod trusted_host;

View file

@ -0,0 +1,137 @@
use serde::{Deserialize, Serialize};
use url::Url;
/// A trusted host, which could be a host or a host-port pair.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TrustedHost {
scheme: Option<String>,
host: String,
port: Option<u16>,
}
impl TrustedHost {
/// Returns `true` if the [`Url`] matches this trusted host.
pub fn matches(&self, url: &Url) -> bool {
if self
.scheme
.as_ref()
.is_some_and(|scheme| scheme != url.scheme())
{
return false;
}
if self.port.is_some_and(|port| url.port() != Some(port)) {
return false;
}
if Some(self.host.as_ref()) != url.host_str() {
return false;
}
true
}
}
#[derive(Debug, thiserror::Error)]
pub enum TrustedHostError {
#[error("missing host for `--trusted-host`: `{0}`")]
MissingHost(String),
#[error("invalid port for `--trusted-host`: `{0}`")]
InvalidPort(String),
}
impl std::str::FromStr for TrustedHost {
type Err = TrustedHostError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
// Detect scheme.
let (scheme, s) = if let Some(s) = s.strip_prefix("https://") {
(Some("https".to_string()), s)
} else if let Some(s) = s.strip_prefix("http://") {
(Some("http".to_string()), s)
} else {
(None, s)
};
let mut parts = s.splitn(2, ':');
// Detect host.
let host = parts
.next()
.and_then(|host| host.split('/').next())
.map(ToString::to_string)
.ok_or_else(|| TrustedHostError::MissingHost(s.to_string()))?;
// Detect port.
let port = parts
.next()
.map(str::parse)
.transpose()
.map_err(|_| TrustedHostError::InvalidPort(s.to_string()))?;
Ok(Self { scheme, host, port })
}
}
#[cfg(feature = "schemars")]
impl schemars::JsonSchema for TrustedHost {
fn schema_name() -> String {
"TrustedHost".to_string()
}
fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
schemars::schema::SchemaObject {
instance_type: Some(schemars::schema::InstanceType::String.into()),
metadata: Some(Box::new(schemars::schema::Metadata {
description: Some("A host or host-port pair.".to_string()),
..schemars::schema::Metadata::default()
})),
..schemars::schema::SchemaObject::default()
}
.into()
}
}
#[cfg(test)]
mod tests {
#[test]
fn parse() {
assert_eq!(
"example.com".parse::<super::TrustedHost>().unwrap(),
super::TrustedHost {
scheme: None,
host: "example.com".to_string(),
port: None
}
);
assert_eq!(
"example.com:8080".parse::<super::TrustedHost>().unwrap(),
super::TrustedHost {
scheme: None,
host: "example.com".to_string(),
port: Some(8080)
}
);
assert_eq!(
"https://example.com".parse::<super::TrustedHost>().unwrap(),
super::TrustedHost {
scheme: Some("https".to_string()),
host: "example.com".to_string(),
port: None
}
);
assert_eq!(
"https://example.com/hello/world"
.parse::<super::TrustedHost>()
.unwrap(),
super::TrustedHost {
scheme: Some("https".to_string()),
host: "example.com".to_string(),
port: None
}
);
}
}

View file

@ -834,7 +834,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
fn request(&self, url: Url) -> Result<reqwest::Request, reqwest::Error> {
self.client
.unmanaged
.uncached_client()
.uncached_client(&url)
.get(url)
.header(
// `reqwest` defaults to accepting compressed responses.

View file

@ -1118,7 +1118,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
.git()
.fetch(
resource.git,
client.unmanaged.uncached_client().client(),
client.unmanaged.uncached_client(resource.url).clone(),
self.build_context.cache().bucket(CacheBucket::Git),
self.reporter.clone().map(Facade::from),
)
@ -1188,7 +1188,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
.git()
.fetch(
resource.git,
client.unmanaged.uncached_client().client(),
client.unmanaged.uncached_client(resource.url).clone(),
self.build_context.cache().bucket(CacheBucket::Git),
self.reporter.clone().map(Facade::from),
)
@ -1589,7 +1589,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
/// Returns a GET [`reqwest::Request`] for the given URL.
fn request(url: Url, client: &RegistryClient) -> Result<reqwest::Request, reqwest::Error> {
client
.uncached_client()
.uncached_client(&url)
.get(url)
.header(
// `reqwest` defaults to accepting compressed responses.

View file

@ -432,7 +432,7 @@ impl ManagedPythonDownload {
let filename = url.path_segments().unwrap().last().unwrap();
let ext = SourceDistExtension::from_path(filename)
.map_err(|err| Error::MissingExtension(url.to_string(), err))?;
let response = client.get(url.clone()).send().await?;
let response = client.client().get(url.clone()).send().await?;
// Ensure the request was successful.
response.error_for_status_ref()?;

View file

@ -8,6 +8,7 @@ use pep508_rs::Requirement;
use pypi_types::{SupportedEnvironments, VerbatimParsedUrl};
use uv_configuration::{
ConfigSettings, IndexStrategy, KeyringProviderType, PackageNameSpecifier, TargetTriple,
TrustedHost,
};
use uv_macros::{CombineOptions, OptionsMetadata};
use uv_normalize::{ExtraName, PackageName};
@ -213,6 +214,7 @@ pub struct InstallerOptions {
pub find_links: Option<Vec<FlatIndexLocation>>,
pub index_strategy: Option<IndexStrategy>,
pub keyring_provider: Option<KeyringProviderType>,
pub allow_insecure_host: Option<Vec<TrustedHost>>,
pub config_settings: Option<ConfigSettings>,
pub exclude_newer: Option<ExcludeNewer>,
pub link_mode: Option<LinkMode>,
@ -239,6 +241,7 @@ pub struct ResolverOptions {
pub find_links: Option<Vec<FlatIndexLocation>>,
pub index_strategy: Option<IndexStrategy>,
pub keyring_provider: Option<KeyringProviderType>,
pub allow_insecure_host: Option<Vec<TrustedHost>>,
pub resolution: Option<ResolutionMode>,
pub prerelease: Option<PrereleaseMode>,
pub config_settings: Option<ConfigSettings>,
@ -350,6 +353,22 @@ pub struct ResolverInstallerOptions {
"#
)]
pub keyring_provider: Option<KeyringProviderType>,
/// Allow insecure connections to host.
///
/// Expects to receive either a hostname (e.g., `localhost`), a host-port pair (e.g.,
/// `localhost:8080`), or a URL (e.g., `https://localhost`).
///
/// WARNING: Hosts included in this list will not be verified against the system's certificate
/// store. Only use `--allow-insecure-host` in a secure network with verified sources, as it
/// bypasses SSL verification and could expose you to MITM attacks.
#[option(
default = "[]",
value_type = "list[str]",
example = r#"
allow-insecure-host = ["localhost:8080"]
"#
)]
pub allow_insecure_host: Option<Vec<TrustedHost>>,
/// The strategy to use when selecting between the different compatible versions for a given
/// package requirement.
///
@ -721,6 +740,22 @@ pub struct PipOptions {
"#
)]
pub keyring_provider: Option<KeyringProviderType>,
/// Allow insecure connections to host.
///
/// Expects to receive either a hostname (e.g., `localhost`), a host-port pair (e.g.,
/// `localhost:8080`), or a URL (e.g., `https://localhost`).
///
/// WARNING: Hosts included in this list will not be verified against the system's certificate
/// store. Only use `--allow-insecure-host` in a secure network with verified sources, as it
/// bypasses SSL verification and could expose you to MITM attacks.
#[option(
default = "[]",
value_type = "list[str]",
example = r#"
allow-insecure-host = ["localhost:8080"]
"#
)]
pub allow_insecure_host: Option<Vec<TrustedHost>>,
/// Don't build source distributions.
///
/// When enabled, resolving will not run arbitrary Python code. The cached wheels of
@ -1208,6 +1243,7 @@ impl From<ResolverInstallerOptions> for ResolverOptions {
find_links: value.find_links,
index_strategy: value.index_strategy,
keyring_provider: value.keyring_provider,
allow_insecure_host: value.allow_insecure_host,
resolution: value.resolution,
prerelease: value.prerelease,
config_settings: value.config_settings,
@ -1235,6 +1271,7 @@ impl From<ResolverInstallerOptions> for InstallerOptions {
find_links: value.find_links,
index_strategy: value.index_strategy,
keyring_provider: value.keyring_provider,
allow_insecure_host: value.allow_insecure_host,
config_settings: value.config_settings,
exclude_newer: value.exclude_newer,
link_mode: value.link_mode,
@ -1267,6 +1304,7 @@ pub struct ToolOptions {
pub find_links: Option<Vec<FlatIndexLocation>>,
pub index_strategy: Option<IndexStrategy>,
pub keyring_provider: Option<KeyringProviderType>,
pub allow_insecure_host: Option<Vec<TrustedHost>>,
pub resolution: Option<ResolutionMode>,
pub prerelease: Option<PrereleaseMode>,
pub config_settings: Option<ConfigSettings>,
@ -1291,6 +1329,7 @@ impl From<ResolverInstallerOptions> for ToolOptions {
find_links: value.find_links,
index_strategy: value.index_strategy,
keyring_provider: value.keyring_provider,
allow_insecure_host: value.allow_insecure_host,
resolution: value.resolution,
prerelease: value.prerelease,
config_settings: value.config_settings,
@ -1317,6 +1356,7 @@ impl From<ToolOptions> for ResolverInstallerOptions {
find_links: value.find_links,
index_strategy: value.index_strategy,
keyring_provider: value.keyring_provider,
allow_insecure_host: value.allow_insecure_host,
resolution: value.resolution,
prerelease: value.prerelease,
config_settings: value.config_settings,

View file

@ -17,7 +17,7 @@ use uv_cache::Cache;
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
BuildOptions, Concurrency, ConfigSettings, ExtrasSpecification, IndexStrategy, NoBinary,
NoBuild, Reinstall, SourceStrategy, Upgrade,
NoBuild, Reinstall, SourceStrategy, TrustedHost, Upgrade,
};
use uv_configuration::{KeyringProviderType, TargetTriple};
use uv_dispatch::BuildDispatch;
@ -75,6 +75,7 @@ pub(crate) async fn pip_compile(
index_locations: IndexLocations,
index_strategy: IndexStrategy,
keyring_provider: KeyringProviderType,
allow_insecure_host: Vec<TrustedHost>,
config_settings: ConfigSettings,
connectivity: Connectivity,
no_build_isolation: bool,
@ -107,7 +108,8 @@ pub(crate) async fn pip_compile(
let client_builder = BaseClientBuilder::new()
.connectivity(connectivity)
.native_tls(native_tls)
.keyring(keyring_provider);
.keyring(keyring_provider)
.allow_insecure_host(allow_insecure_host);
// Read all requirements from the provided sources.
let RequirementsSpecification {

View file

@ -3,18 +3,18 @@ use std::fmt::Write;
use anstream::eprint;
use itertools::Itertools;
use owo_colors::OwoColorize;
use pep508_rs::PackageName;
use tracing::{debug, enabled, Level};
use distribution_types::{IndexLocations, Resolution, UnresolvedRequirementSpecification};
use install_wheel_rs::linker::LinkMode;
use pep508_rs::PackageName;
use pypi_types::Requirement;
use uv_auth::store_credentials_from_url;
use uv_cache::Cache;
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
BuildOptions, Concurrency, ConfigSettings, ExtrasSpecification, HashCheckingMode,
IndexStrategy, Reinstall, SourceStrategy, Upgrade,
IndexStrategy, Reinstall, SourceStrategy, TrustedHost, Upgrade,
};
use uv_configuration::{KeyringProviderType, TargetTriple};
use uv_dispatch::BuildDispatch;
@ -53,6 +53,7 @@ pub(crate) async fn pip_install(
index_locations: IndexLocations,
index_strategy: IndexStrategy,
keyring_provider: KeyringProviderType,
allow_insecure_host: Vec<TrustedHost>,
reinstall: Reinstall,
link_mode: LinkMode,
compile: bool,
@ -83,7 +84,8 @@ pub(crate) async fn pip_install(
let client_builder = BaseClientBuilder::new()
.connectivity(connectivity)
.native_tls(native_tls)
.keyring(keyring_provider);
.keyring(keyring_provider)
.allow_insecure_host(allow_insecure_host);
// Read all requirements from the provided sources.
let RequirementsSpecification {

View file

@ -3,17 +3,17 @@ use std::fmt::Write;
use anstream::eprint;
use anyhow::Result;
use owo_colors::OwoColorize;
use pep508_rs::PackageName;
use tracing::debug;
use distribution_types::{IndexLocations, Resolution};
use install_wheel_rs::linker::LinkMode;
use pep508_rs::PackageName;
use uv_auth::store_credentials_from_url;
use uv_cache::Cache;
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
BuildOptions, Concurrency, ConfigSettings, ExtrasSpecification, HashCheckingMode,
IndexStrategy, Reinstall, SourceStrategy, Upgrade,
IndexStrategy, Reinstall, SourceStrategy, TrustedHost, Upgrade,
};
use uv_configuration::{KeyringProviderType, TargetTriple};
use uv_dispatch::BuildDispatch;
@ -48,6 +48,7 @@ pub(crate) async fn pip_sync(
index_locations: IndexLocations,
index_strategy: IndexStrategy,
keyring_provider: KeyringProviderType,
allow_insecure_host: Vec<TrustedHost>,
allow_empty_requirements: bool,
connectivity: Connectivity,
config_settings: &ConfigSettings,
@ -73,7 +74,8 @@ pub(crate) async fn pip_sync(
let client_builder = BaseClientBuilder::new()
.connectivity(connectivity)
.native_tls(native_tls)
.keyring(keyring_provider);
.keyring(keyring_provider)
.allow_insecure_host(allow_insecure_host);
// Initialize a few defaults.
let overrides = &[];

View file

@ -11,7 +11,7 @@ use pypi_types::Requirement;
use pypi_types::VerbatimParsedUrl;
use uv_cache::Cache;
use uv_client::{BaseClientBuilder, Connectivity};
use uv_configuration::KeyringProviderType;
use uv_configuration::{KeyringProviderType, TrustedHost};
use uv_fs::Simplified;
use uv_python::EnvironmentPreference;
use uv_python::PythonRequest;
@ -33,6 +33,7 @@ pub(crate) async fn pip_uninstall(
connectivity: Connectivity,
native_tls: bool,
keyring_provider: KeyringProviderType,
allow_insecure_host: Vec<TrustedHost>,
printer: Printer,
) -> Result<ExitStatus> {
let start = std::time::Instant::now();
@ -40,7 +41,8 @@ pub(crate) async fn pip_uninstall(
let client_builder = BaseClientBuilder::new()
.connectivity(connectivity)
.native_tls(native_tls)
.keyring(keyring_provider);
.keyring(keyring_provider)
.allow_insecure_host(allow_insecure_host);
// Read all requirements from the provided sources.
let spec = RequirementsSpecification::from_simple_sources(sources, &client_builder).await?;

View file

@ -234,6 +234,7 @@ async fn do_lock(
index_locations,
index_strategy,
keyring_provider,
allow_insecure_host,
resolution,
prerelease,
config_setting,
@ -342,6 +343,7 @@ async fn do_lock(
.index_urls(index_locations.index_urls())
.index_strategy(index_strategy)
.keyring(keyring_provider)
.allow_insecure_host(allow_insecure_host.to_vec())
.markers(interpreter.markers())
.platform(interpreter.platform())
.build();

View file

@ -410,6 +410,7 @@ pub(crate) async fn resolve_names(
index_locations,
index_strategy,
keyring_provider,
allow_insecure_host,
resolution: _,
prerelease: _,
config_setting,
@ -436,6 +437,7 @@ pub(crate) async fn resolve_names(
.index_urls(index_locations.index_urls())
.index_strategy(*index_strategy)
.keyring(*keyring_provider)
.allow_insecure_host(allow_insecure_host.clone())
.markers(interpreter.markers())
.platform(interpreter.platform())
.build();
@ -510,6 +512,7 @@ pub(crate) async fn resolve_environment<'a>(
index_locations,
index_strategy,
keyring_provider,
allow_insecure_host,
resolution,
prerelease,
config_setting,
@ -549,6 +552,7 @@ pub(crate) async fn resolve_environment<'a>(
.index_urls(index_locations.index_urls())
.index_strategy(index_strategy)
.keyring(keyring_provider)
.allow_insecure_host(allow_insecure_host.to_vec())
.markers(interpreter.markers())
.platform(interpreter.platform())
.build();
@ -660,6 +664,7 @@ pub(crate) async fn sync_environment(
index_locations,
index_strategy,
keyring_provider,
allow_insecure_host,
config_setting,
no_build_isolation,
no_build_isolation_package,
@ -690,6 +695,7 @@ pub(crate) async fn sync_environment(
.index_urls(index_locations.index_urls())
.index_strategy(index_strategy)
.keyring(keyring_provider)
.allow_insecure_host(allow_insecure_host.to_vec())
.markers(interpreter.markers())
.platform(interpreter.platform())
.build();
@ -804,6 +810,7 @@ pub(crate) async fn update_environment(
index_locations,
index_strategy,
keyring_provider,
allow_insecure_host,
resolution,
prerelease,
config_setting,
@ -871,6 +878,7 @@ pub(crate) async fn update_environment(
.index_urls(index_locations.index_urls())
.index_strategy(*index_strategy)
.keyring(*keyring_provider)
.allow_insecure_host(allow_insecure_host.clone())
.markers(interpreter.markers())
.platform(interpreter.platform())
.build();

View file

@ -143,6 +143,7 @@ pub(super) async fn do_sync(
index_locations,
index_strategy,
keyring_provider,
allow_insecure_host,
config_setting,
no_build_isolation,
no_build_isolation_package,
@ -209,6 +210,7 @@ pub(super) async fn do_sync(
.index_urls(index_locations.index_urls())
.index_strategy(index_strategy)
.keyring(keyring_provider)
.allow_insecure_host(allow_insecure_host.to_vec())
.markers(venv.interpreter().markers())
.platform(venv.interpreter().platform())
.build();

View file

@ -17,7 +17,7 @@ use uv_cache::Cache;
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
BuildOptions, Concurrency, ConfigSettings, IndexStrategy, KeyringProviderType, NoBinary,
NoBuild, SourceStrategy,
NoBuild, SourceStrategy, TrustedHost,
};
use uv_dispatch::BuildDispatch;
use uv_fs::{Simplified, CWD};
@ -49,6 +49,7 @@ pub(crate) async fn venv(
index_locations: &IndexLocations,
index_strategy: IndexStrategy,
keyring_provider: KeyringProviderType,
allow_insecure_host: Vec<TrustedHost>,
prompt: uv_virtualenv::Prompt,
system_site_packages: bool,
connectivity: Connectivity,
@ -69,6 +70,7 @@ pub(crate) async fn venv(
index_locations,
index_strategy,
keyring_provider,
allow_insecure_host,
prompt,
system_site_packages,
connectivity,
@ -122,6 +124,7 @@ async fn venv_impl(
index_locations: &IndexLocations,
index_strategy: IndexStrategy,
keyring_provider: KeyringProviderType,
allow_insecure_host: Vec<TrustedHost>,
prompt: uv_virtualenv::Prompt,
system_site_packages: bool,
connectivity: Connectivity,
@ -251,6 +254,7 @@ async fn venv_impl(
.index_urls(index_locations.index_urls())
.index_strategy(index_strategy)
.keyring(keyring_provider)
.allow_insecure_host(allow_insecure_host)
.markers(interpreter.markers())
.platform(interpreter.platform())
.build();

View file

@ -325,6 +325,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
args.settings.index_locations,
args.settings.index_strategy,
args.settings.keyring_provider,
args.settings.allow_insecure_host,
args.settings.config_setting,
globals.connectivity,
args.settings.no_build_isolation,
@ -391,6 +392,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
args.settings.index_locations,
args.settings.index_strategy,
args.settings.keyring_provider,
args.settings.allow_insecure_host,
args.settings.allow_empty_requirements,
globals.connectivity,
&args.settings.config_setting,
@ -474,6 +476,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
args.settings.index_locations,
args.settings.index_strategy,
args.settings.keyring_provider,
args.settings.allow_insecure_host,
args.settings.reinstall,
args.settings.link_mode,
args.settings.compile_bytecode,
@ -532,6 +535,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
globals.connectivity,
globals.native_tls,
args.settings.keyring_provider,
args.settings.allow_insecure_host,
printer,
)
.await
@ -692,6 +696,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
&args.settings.index_locations,
args.settings.index_strategy,
args.settings.keyring_provider,
args.settings.allow_insecure_host,
uv_virtualenv::Prompt::from_args(prompt),
args.system_site_packages,
globals.connectivity,

View file

@ -24,7 +24,7 @@ use uv_client::Connectivity;
use uv_configuration::{
BuildOptions, Concurrency, ConfigSettings, ExtrasSpecification, HashCheckingMode,
IndexStrategy, InstallOptions, KeyringProviderType, NoBinary, NoBuild, PreviewMode, Reinstall,
SourceStrategy, TargetTriple, Upgrade,
SourceStrategy, TargetTriple, TrustedHost, Upgrade,
};
use uv_normalize::PackageName;
use uv_python::{Prefix, PythonDownloads, PythonPreference, PythonVersion, Target};
@ -1308,6 +1308,7 @@ impl PipUninstallSettings {
requirement,
python,
keyring_provider,
allow_insecure_host,
system,
no_system,
break_system_packages,
@ -1328,6 +1329,12 @@ impl PipUninstallSettings {
target,
prefix,
keyring_provider,
allow_insecure_host: allow_insecure_host.map(|allow_insecure_host| {
allow_insecure_host
.into_iter()
.filter_map(Maybe::into_option)
.collect()
}),
..PipOptions::default()
},
filesystem,
@ -1558,6 +1565,7 @@ impl VenvSettings {
index_args,
index_strategy,
keyring_provider,
allow_insecure_host,
exclude_newer,
link_mode,
compat_args: _,
@ -1576,6 +1584,12 @@ impl VenvSettings {
system: flag(system, no_system),
index_strategy,
keyring_provider,
allow_insecure_host: allow_insecure_host.map(|allow_insecure_host| {
allow_insecure_host
.into_iter()
.filter_map(Maybe::into_option)
.collect()
}),
exclude_newer,
link_mode,
..PipOptions::from(index_args)
@ -1595,6 +1609,7 @@ pub(crate) struct InstallerSettingsRef<'a> {
pub(crate) index_locations: &'a IndexLocations,
pub(crate) index_strategy: IndexStrategy,
pub(crate) keyring_provider: KeyringProviderType,
pub(crate) allow_insecure_host: &'a [TrustedHost],
pub(crate) config_setting: &'a ConfigSettings,
pub(crate) no_build_isolation: bool,
pub(crate) no_build_isolation_package: &'a [PackageName],
@ -1616,6 +1631,7 @@ pub(crate) struct ResolverSettings {
pub(crate) index_locations: IndexLocations,
pub(crate) index_strategy: IndexStrategy,
pub(crate) keyring_provider: KeyringProviderType,
pub(crate) allow_insecure_host: Vec<TrustedHost>,
pub(crate) resolution: ResolutionMode,
pub(crate) prerelease: PrereleaseMode,
pub(crate) config_setting: ConfigSettings,
@ -1633,6 +1649,7 @@ pub(crate) struct ResolverSettingsRef<'a> {
pub(crate) index_locations: &'a IndexLocations,
pub(crate) index_strategy: IndexStrategy,
pub(crate) keyring_provider: KeyringProviderType,
pub(crate) allow_insecure_host: &'a [TrustedHost],
pub(crate) resolution: ResolutionMode,
pub(crate) prerelease: PrereleaseMode,
pub(crate) config_setting: &'a ConfigSettings,
@ -1663,6 +1680,7 @@ impl ResolverSettings {
index_locations: &self.index_locations,
index_strategy: self.index_strategy,
keyring_provider: self.keyring_provider,
allow_insecure_host: &self.allow_insecure_host,
resolution: self.resolution,
prerelease: self.prerelease,
config_setting: &self.config_setting,
@ -1690,6 +1708,7 @@ impl From<ResolverOptions> for ResolverSettings {
prerelease: value.prerelease.unwrap_or_default(),
index_strategy: value.index_strategy.unwrap_or_default(),
keyring_provider: value.keyring_provider.unwrap_or_default(),
allow_insecure_host: value.allow_insecure_host.unwrap_or_default(),
config_setting: value.config_settings.unwrap_or_default(),
no_build_isolation: value.no_build_isolation.unwrap_or_default(),
no_build_isolation_package: value.no_build_isolation_package.unwrap_or_default(),
@ -1718,6 +1737,7 @@ pub(crate) struct ResolverInstallerSettingsRef<'a> {
pub(crate) index_locations: &'a IndexLocations,
pub(crate) index_strategy: IndexStrategy,
pub(crate) keyring_provider: KeyringProviderType,
pub(crate) allow_insecure_host: &'a [TrustedHost],
pub(crate) resolution: ResolutionMode,
pub(crate) prerelease: PrereleaseMode,
pub(crate) config_setting: &'a ConfigSettings,
@ -1744,6 +1764,7 @@ pub(crate) struct ResolverInstallerSettings {
pub(crate) index_locations: IndexLocations,
pub(crate) index_strategy: IndexStrategy,
pub(crate) keyring_provider: KeyringProviderType,
pub(crate) allow_insecure_host: Vec<TrustedHost>,
pub(crate) resolution: ResolutionMode,
pub(crate) prerelease: PrereleaseMode,
pub(crate) config_setting: ConfigSettings,
@ -1779,6 +1800,7 @@ impl ResolverInstallerSettings {
index_locations: &self.index_locations,
index_strategy: self.index_strategy,
keyring_provider: self.keyring_provider,
allow_insecure_host: &self.allow_insecure_host,
resolution: self.resolution,
prerelease: self.prerelease,
config_setting: &self.config_setting,
@ -1808,6 +1830,7 @@ impl From<ResolverInstallerOptions> for ResolverInstallerSettings {
prerelease: value.prerelease.unwrap_or_default(),
index_strategy: value.index_strategy.unwrap_or_default(),
keyring_provider: value.keyring_provider.unwrap_or_default(),
allow_insecure_host: value.allow_insecure_host.unwrap_or_default(),
config_setting: value.config_settings.unwrap_or_default(),
no_build_isolation: value.no_build_isolation.unwrap_or_default(),
no_build_isolation_package: value.no_build_isolation_package.unwrap_or_default(),
@ -1852,6 +1875,7 @@ pub(crate) struct PipSettings {
pub(crate) prefix: Option<Prefix>,
pub(crate) index_strategy: IndexStrategy,
pub(crate) keyring_provider: KeyringProviderType,
pub(crate) allow_insecure_host: Vec<TrustedHost>,
pub(crate) no_build_isolation: bool,
pub(crate) no_build_isolation_package: Vec<PackageName>,
pub(crate) build_options: BuildOptions,
@ -1906,6 +1930,7 @@ impl PipSettings {
find_links,
index_strategy,
keyring_provider,
allow_insecure_host,
no_build,
no_binary,
only_binary,
@ -1955,6 +1980,7 @@ impl PipSettings {
find_links: top_level_find_links,
index_strategy: top_level_index_strategy,
keyring_provider: top_level_keyring_provider,
allow_insecure_host: top_level_allow_insecure_host,
resolution: top_level_resolution,
prerelease: top_level_prerelease,
config_settings: top_level_config_settings,
@ -1984,6 +2010,7 @@ impl PipSettings {
let find_links = find_links.combine(top_level_find_links);
let index_strategy = index_strategy.combine(top_level_index_strategy);
let keyring_provider = keyring_provider.combine(top_level_keyring_provider);
let allow_insecure_host = allow_insecure_host.combine(top_level_allow_insecure_host);
let resolution = resolution.combine(top_level_resolution);
let prerelease = prerelease.combine(top_level_prerelease);
let config_settings = config_settings.combine(top_level_config_settings);
@ -2043,6 +2070,10 @@ impl PipSettings {
.keyring_provider
.combine(keyring_provider)
.unwrap_or_default(),
allow_insecure_host: args
.allow_insecure_host
.combine(allow_insecure_host)
.unwrap_or_default(),
generate_hashes: args
.generate_hashes
.combine(generate_hashes)
@ -2156,6 +2187,7 @@ impl<'a> From<ResolverInstallerSettingsRef<'a>> for ResolverSettingsRef<'a> {
index_locations: settings.index_locations,
index_strategy: settings.index_strategy,
keyring_provider: settings.keyring_provider,
allow_insecure_host: settings.allow_insecure_host,
resolution: settings.resolution,
prerelease: settings.prerelease,
config_setting: settings.config_setting,
@ -2176,6 +2208,7 @@ impl<'a> From<ResolverInstallerSettingsRef<'a>> for InstallerSettingsRef<'a> {
index_locations: settings.index_locations,
index_strategy: settings.index_strategy,
keyring_provider: settings.keyring_provider,
allow_insecure_host: settings.allow_insecure_host,
config_setting: settings.config_setting,
no_build_isolation: settings.no_build_isolation,
no_build_isolation_package: settings.no_build_isolation_package,

View file

@ -128,6 +128,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
prefix: None,
index_strategy: FirstIndex,
keyring_provider: Disabled,
allow_insecure_host: [],
no_build_isolation: false,
no_build_isolation_package: [],
build_options: BuildOptions {
@ -266,6 +267,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
prefix: None,
index_strategy: FirstIndex,
keyring_provider: Disabled,
allow_insecure_host: [],
no_build_isolation: false,
no_build_isolation_package: [],
build_options: BuildOptions {
@ -405,6 +407,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
prefix: None,
index_strategy: FirstIndex,
keyring_provider: Disabled,
allow_insecure_host: [],
no_build_isolation: false,
no_build_isolation_package: [],
build_options: BuildOptions {
@ -576,6 +579,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
prefix: None,
index_strategy: FirstIndex,
keyring_provider: Disabled,
allow_insecure_host: [],
no_build_isolation: false,
no_build_isolation_package: [],
build_options: BuildOptions {
@ -693,6 +697,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
prefix: None,
index_strategy: FirstIndex,
keyring_provider: Disabled,
allow_insecure_host: [],
no_build_isolation: false,
no_build_isolation_package: [],
build_options: BuildOptions {
@ -842,6 +847,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
prefix: None,
index_strategy: FirstIndex,
keyring_provider: Disabled,
allow_insecure_host: [],
no_build_isolation: false,
no_build_isolation_package: [],
build_options: BuildOptions {
@ -1028,6 +1034,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
prefix: None,
index_strategy: FirstIndex,
keyring_provider: Disabled,
allow_insecure_host: [],
no_build_isolation: false,
no_build_isolation_package: [],
build_options: BuildOptions {
@ -1213,6 +1220,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
prefix: None,
index_strategy: FirstIndex,
keyring_provider: Disabled,
allow_insecure_host: [],
no_build_isolation: false,
no_build_isolation_package: [],
build_options: BuildOptions {
@ -1376,6 +1384,7 @@ fn resolve_find_links() -> anyhow::Result<()> {
prefix: None,
index_strategy: FirstIndex,
keyring_provider: Disabled,
allow_insecure_host: [],
no_build_isolation: false,
no_build_isolation_package: [],
build_options: BuildOptions {
@ -1515,6 +1524,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
prefix: None,
index_strategy: FirstIndex,
keyring_provider: Disabled,
allow_insecure_host: [],
no_build_isolation: false,
no_build_isolation_package: [],
build_options: BuildOptions {
@ -1692,6 +1702,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
prefix: None,
index_strategy: FirstIndex,
keyring_provider: Disabled,
allow_insecure_host: [],
no_build_isolation: false,
no_build_isolation_package: [],
build_options: BuildOptions {
@ -1852,6 +1863,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
prefix: None,
index_strategy: FirstIndex,
keyring_provider: Disabled,
allow_insecure_host: [],
no_build_isolation: false,
no_build_isolation_package: [],
build_options: BuildOptions {
@ -1991,6 +2003,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
prefix: None,
index_strategy: FirstIndex,
keyring_provider: Disabled,
allow_insecure_host: [],
no_build_isolation: false,
no_build_isolation_package: [],
build_options: BuildOptions {
@ -2113,6 +2126,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
prefix: None,
index_strategy: FirstIndex,
keyring_provider: Disabled,
allow_insecure_host: [],
no_build_isolation: false,
no_build_isolation_package: [],
build_options: BuildOptions {
@ -2235,6 +2249,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
prefix: None,
index_strategy: FirstIndex,
keyring_provider: Disabled,
allow_insecure_host: [],
no_build_isolation: false,
no_build_isolation_package: [],
build_options: BuildOptions {
@ -2359,6 +2374,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
prefix: None,
index_strategy: FirstIndex,
keyring_provider: Disabled,
allow_insecure_host: [],
no_build_isolation: false,
no_build_isolation_package: [],
build_options: BuildOptions {
@ -2488,6 +2504,7 @@ fn resolve_tool() -> anyhow::Result<()> {
find_links: None,
index_strategy: None,
keyring_provider: None,
allow_insecure_host: None,
resolution: Some(
LowestDirect,
),
@ -2523,6 +2540,7 @@ fn resolve_tool() -> anyhow::Result<()> {
},
index_strategy: FirstIndex,
keyring_provider: Disabled,
allow_insecure_host: [],
resolution: LowestDirect,
prerelease: IfNecessaryOrExplicit,
config_setting: ConfigSettings(
@ -2653,6 +2671,7 @@ fn resolve_poetry_toml() -> anyhow::Result<()> {
prefix: None,
index_strategy: FirstIndex,
keyring_provider: Disabled,
allow_insecure_host: [],
no_build_isolation: false,
no_build_isolation_package: [],
build_options: BuildOptions {
@ -2826,6 +2845,7 @@ fn resolve_both() -> anyhow::Result<()> {
prefix: None,
index_strategy: FirstIndex,
keyring_provider: Disabled,
allow_insecure_host: [],
no_build_isolation: false,
no_build_isolation_package: [],
build_options: BuildOptions {
@ -2991,6 +3011,7 @@ fn resolve_config_file() -> anyhow::Result<()> {
prefix: None,
index_strategy: FirstIndex,
keyring_provider: Disabled,
allow_insecure_host: [],
no_build_isolation: false,
no_build_isolation_package: [],
build_options: BuildOptions {
@ -3208,6 +3229,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> {
prefix: None,
index_strategy: FirstIndex,
keyring_provider: Disabled,
allow_insecure_host: [],
no_build_isolation: false,
no_build_isolation_package: [],
build_options: BuildOptions {
@ -3333,6 +3355,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> {
prefix: None,
index_strategy: FirstIndex,
keyring_provider: Disabled,
allow_insecure_host: [],
no_build_isolation: false,
no_build_isolation_package: [],
build_options: BuildOptions {

View file

@ -74,6 +74,23 @@ If client certificate authentication (mTLS) is desired, set the `SSL_CLIENT_CERT
variable to the path of the PEM formatted file containing the certificate followed by the private
key.
Finally, if you're using a setup in which you want to trust a self-signed certificate or otherwise
disable certificate verification, you can instruct uv to allow insecure connections to dedicated
hosts via the `allow-insecure-host` configuration option. For example, adding the following to
`pyproject.toml` will allow insecure connections to `example.com`:
```toml
[tool.uv]
allow-insecure-host = ["example.com"]
```
`allow-insecure-host` expects to receive a hostname (e.g., `localhost`) or hostname-port pair (e.g.,
`localhost:8080`), and is only applicable to HTTPS connections, as HTTP connections are inherently
insecure.
Use `allow-insecure-host` with caution and only in trusted environments, as it can expose you to
security risks due to the lack of certificate verification.
## Authentication with alternative package indexes
See the [alternative indexes integration guide](../guides/integration/alternative-indexes.md) for

View file

@ -70,6 +70,14 @@ uv run [OPTIONS] <COMMAND>
<p>This option is only available when running in a project.</p>
</dd><dt><code>--allow-insecure-host</code> <i>allow-insecure-host</i></dt><dd><p>Allow insecure connections to a host.</p>
<p>Can be provided multiple times.</p>
<p>Expects to receive either a hostname (e.g., <code>localhost</code>), a host-port pair (e.g., <code>localhost:8080</code>), or a URL (e.g., <code>https://localhost</code>).</p>
<p>WARNING: Hosts included in this list will not be verified against the system&#8217;s certificate store. Only use <code>--allow-insecure-host</code> in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.</p>
</dd><dt><code>--cache-dir</code> <i>cache-dir</i></dt><dd><p>Path to the cache directory.</p>
<p>Defaults to <code>$HOME/Library/Caches/uv</code> on macOS, <code>$XDG_CACHE_HOME/uv</code> or <code>$HOME/.cache/uv</code> on Linux, and <code>%LOCALAPPDATA%\uv\cache</code> on Windows.</p>
@ -483,7 +491,15 @@ uv add [OPTIONS] <PACKAGES|--requirements <REQUIREMENTS>>
<h3 class="cli-reference">Options</h3>
<dl class="cli-reference"><dt><code>--branch</code> <i>branch</i></dt><dd><p>Branch to use when adding a dependency from Git</p>
<dl class="cli-reference"><dt><code>--allow-insecure-host</code> <i>allow-insecure-host</i></dt><dd><p>Allow insecure connections to a host.</p>
<p>Can be provided multiple times.</p>
<p>Expects to receive either a hostname (e.g., <code>localhost</code>), a host-port pair (e.g., <code>localhost:8080</code>), or a URL (e.g., <code>https://localhost</code>).</p>
<p>WARNING: Hosts included in this list will not be verified against the system&#8217;s certificate store. Only use <code>--allow-insecure-host</code> in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.</p>
</dd><dt><code>--branch</code> <i>branch</i></dt><dd><p>Branch to use when adding a dependency from Git</p>
</dd><dt><code>--cache-dir</code> <i>cache-dir</i></dt><dd><p>Path to the cache directory.</p>
@ -767,7 +783,15 @@ uv remove [OPTIONS] <PACKAGES>...
<h3 class="cli-reference">Options</h3>
<dl class="cli-reference"><dt><code>--cache-dir</code> <i>cache-dir</i></dt><dd><p>Path to the cache directory.</p>
<dl class="cli-reference"><dt><code>--allow-insecure-host</code> <i>allow-insecure-host</i></dt><dd><p>Allow insecure connections to a host.</p>
<p>Can be provided multiple times.</p>
<p>Expects to receive either a hostname (e.g., <code>localhost</code>), a host-port pair (e.g., <code>localhost:8080</code>), or a URL (e.g., <code>https://localhost</code>).</p>
<p>WARNING: Hosts included in this list will not be verified against the system&#8217;s certificate store. Only use <code>--allow-insecure-host</code> in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.</p>
</dd><dt><code>--cache-dir</code> <i>cache-dir</i></dt><dd><p>Path to the cache directory.</p>
<p>Defaults to <code>$HOME/Library/Caches/uv</code> on macOS, <code>$XDG_CACHE_HOME/uv</code> or <code>$HOME/.cache/uv</code> on Linux, and <code>%LOCALAPPDATA%\uv\cache</code> on Windows.</p>
@ -1023,6 +1047,14 @@ uv sync [OPTIONS]
<dl class="cli-reference"><dt><code>--all-extras</code></dt><dd><p>Include all optional dependencies</p>
</dd><dt><code>--allow-insecure-host</code> <i>allow-insecure-host</i></dt><dd><p>Allow insecure connections to a host.</p>
<p>Can be provided multiple times.</p>
<p>Expects to receive either a hostname (e.g., <code>localhost</code>), a host-port pair (e.g., <code>localhost:8080</code>), or a URL (e.g., <code>https://localhost</code>).</p>
<p>WARNING: Hosts included in this list will not be verified against the system&#8217;s certificate store. Only use <code>--allow-insecure-host</code> in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.</p>
</dd><dt><code>--cache-dir</code> <i>cache-dir</i></dt><dd><p>Path to the cache directory.</p>
<p>Defaults to <code>$HOME/Library/Caches/uv</code> on macOS, <code>$XDG_CACHE_HOME/uv</code> or <code>$HOME/.cache/uv</code> on Linux, and <code>%LOCALAPPDATA%\uv\cache</code> on Windows.</p>
@ -1289,7 +1321,15 @@ uv lock [OPTIONS]
<h3 class="cli-reference">Options</h3>
<dl class="cli-reference"><dt><code>--cache-dir</code> <i>cache-dir</i></dt><dd><p>Path to the cache directory.</p>
<dl class="cli-reference"><dt><code>--allow-insecure-host</code> <i>allow-insecure-host</i></dt><dd><p>Allow insecure connections to a host.</p>
<p>Can be provided multiple times.</p>
<p>Expects to receive either a hostname (e.g., <code>localhost</code>), a host-port pair (e.g., <code>localhost:8080</code>), or a URL (e.g., <code>https://localhost</code>).</p>
<p>WARNING: Hosts included in this list will not be verified against the system&#8217;s certificate store. Only use <code>--allow-insecure-host</code> in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.</p>
</dd><dt><code>--cache-dir</code> <i>cache-dir</i></dt><dd><p>Path to the cache directory.</p>
<p>Defaults to <code>$HOME/Library/Caches/uv</code> on macOS, <code>$XDG_CACHE_HOME/uv</code> or <code>$HOME/.cache/uv</code> on Linux, and <code>%LOCALAPPDATA%\uv\cache</code> on Windows.</p>
@ -1513,7 +1553,15 @@ uv tree [OPTIONS]
<h3 class="cli-reference">Options</h3>
<dl class="cli-reference"><dt><code>--cache-dir</code> <i>cache-dir</i></dt><dd><p>Path to the cache directory.</p>
<dl class="cli-reference"><dt><code>--allow-insecure-host</code> <i>allow-insecure-host</i></dt><dd><p>Allow insecure connections to a host.</p>
<p>Can be provided multiple times.</p>
<p>Expects to receive either a hostname (e.g., <code>localhost</code>), a host-port pair (e.g., <code>localhost:8080</code>), or a URL (e.g., <code>https://localhost</code>).</p>
<p>WARNING: Hosts included in this list will not be verified against the system&#8217;s certificate store. Only use <code>--allow-insecure-host</code> in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.</p>
</dd><dt><code>--cache-dir</code> <i>cache-dir</i></dt><dd><p>Path to the cache directory.</p>
<p>Defaults to <code>$HOME/Library/Caches/uv</code> on macOS, <code>$XDG_CACHE_HOME/uv</code> or <code>$HOME/.cache/uv</code> on Linux, and <code>%LOCALAPPDATA%\uv\cache</code> on Windows.</p>
@ -1839,7 +1887,15 @@ uv tool run [OPTIONS] [COMMAND]
<h3 class="cli-reference">Options</h3>
<dl class="cli-reference"><dt><code>--cache-dir</code> <i>cache-dir</i></dt><dd><p>Path to the cache directory.</p>
<dl class="cli-reference"><dt><code>--allow-insecure-host</code> <i>allow-insecure-host</i></dt><dd><p>Allow insecure connections to a host.</p>
<p>Can be provided multiple times.</p>
<p>Expects to receive either a hostname (e.g., <code>localhost</code>), a host-port pair (e.g., <code>localhost:8080</code>), or a URL (e.g., <code>https://localhost</code>).</p>
<p>WARNING: Hosts included in this list will not be verified against the system&#8217;s certificate store. Only use <code>--allow-insecure-host</code> in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.</p>
</dd><dt><code>--cache-dir</code> <i>cache-dir</i></dt><dd><p>Path to the cache directory.</p>
<p>Defaults to <code>$HOME/Library/Caches/uv</code> on macOS, <code>$XDG_CACHE_HOME/uv</code> or <code>$HOME/.cache/uv</code> on Linux, and <code>%LOCALAPPDATA%\uv\cache</code> on Windows.</p>
@ -2081,7 +2137,15 @@ uv tool install [OPTIONS] <PACKAGE>
<h3 class="cli-reference">Options</h3>
<dl class="cli-reference"><dt><code>--cache-dir</code> <i>cache-dir</i></dt><dd><p>Path to the cache directory.</p>
<dl class="cli-reference"><dt><code>--allow-insecure-host</code> <i>allow-insecure-host</i></dt><dd><p>Allow insecure connections to a host.</p>
<p>Can be provided multiple times.</p>
<p>Expects to receive either a hostname (e.g., <code>localhost</code>), a host-port pair (e.g., <code>localhost:8080</code>), or a URL (e.g., <code>https://localhost</code>).</p>
<p>WARNING: Hosts included in this list will not be verified against the system&#8217;s certificate store. Only use <code>--allow-insecure-host</code> in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.</p>
</dd><dt><code>--cache-dir</code> <i>cache-dir</i></dt><dd><p>Path to the cache directory.</p>
<p>Defaults to <code>$HOME/Library/Caches/uv</code> on macOS, <code>$XDG_CACHE_HOME/uv</code> or <code>$HOME/.cache/uv</code> on Linux, and <code>%LOCALAPPDATA%\uv\cache</code> on Windows.</p>
@ -2323,6 +2387,14 @@ uv tool upgrade [OPTIONS] <NAME>
<dl class="cli-reference"><dt><code>--all</code></dt><dd><p>Upgrade all tools</p>
</dd><dt><code>--allow-insecure-host</code> <i>allow-insecure-host</i></dt><dd><p>Allow insecure connections to a host.</p>
<p>Can be provided multiple times.</p>
<p>Expects to receive either a hostname (e.g., <code>localhost</code>), a host-port pair (e.g., <code>localhost:8080</code>), or a URL (e.g., <code>https://localhost</code>).</p>
<p>WARNING: Hosts included in this list will not be verified against the system&#8217;s certificate store. Only use <code>--allow-insecure-host</code> in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.</p>
</dd><dt><code>--cache-dir</code> <i>cache-dir</i></dt><dd><p>Path to the cache directory.</p>
<p>Defaults to <code>$HOME/Library/Caches/uv</code> on macOS, <code>$XDG_CACHE_HOME/uv</code> or <code>$HOME/.cache/uv</code> on Linux, and <code>%LOCALAPPDATA%\uv\cache</code> on Windows.</p>
@ -3586,6 +3658,14 @@ uv pip compile [OPTIONS] <SRC_FILE>...
<p>Only applies to <code>pyproject.toml</code>, <code>setup.py</code>, and <code>setup.cfg</code> sources.</p>
</dd><dt><code>--allow-insecure-host</code> <i>allow-insecure-host</i></dt><dd><p>Allow insecure connections to a host.</p>
<p>Can be provided multiple times.</p>
<p>Expects to receive either a hostname (e.g., <code>localhost</code>), a host-port pair (e.g., <code>localhost:8080</code>), or a URL (e.g., <code>https://localhost</code>).</p>
<p>WARNING: Hosts included in this list will not be verified against the system&#8217;s certificate store. Only use <code>--allow-insecure-host</code> in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.</p>
</dd><dt><code>--annotation-style</code> <i>annotation-style</i></dt><dd><p>The style of the annotation comments included in the output file, used to indicate the source of each package.</p>
<p>Defaults to <code>split</code>.</p>
@ -3944,6 +4024,14 @@ uv pip sync [OPTIONS] <SRC_FILE>...
<dl class="cli-reference"><dt><code>--allow-empty-requirements</code></dt><dd><p>Allow sync of empty requirements, which will clear the environment of all packages</p>
</dd><dt><code>--allow-insecure-host</code> <i>allow-insecure-host</i></dt><dd><p>Allow insecure connections to a host.</p>
<p>Can be provided multiple times.</p>
<p>Expects to receive either a hostname (e.g., <code>localhost</code>), a host-port pair (e.g., <code>localhost:8080</code>), or a URL (e.g., <code>https://localhost</code>).</p>
<p>WARNING: Hosts included in this list will not be verified against the system&#8217;s certificate store. Only use <code>--allow-insecure-host</code> in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.</p>
</dd><dt><code>--break-system-packages</code></dt><dd><p>Allow uv to modify an <code>EXTERNALLY-MANAGED</code> Python installation.</p>
<p>WARNING: <code>--break-system-packages</code> is intended for use in continuous integration (CI) environments, when installing into Python installations that are managed by an external package manager, like <code>apt</code>. It should be used with caution, as such Python installations explicitly recommend against modifications by other package managers (like uv or <code>pip</code>).</p>
@ -4235,6 +4323,14 @@ uv pip install [OPTIONS] <PACKAGE|--requirement <REQUIREMENT>|--editable <EDITAB
<p>Only applies to <code>pyproject.toml</code>, <code>setup.py</code>, and <code>setup.cfg</code> sources.</p>
</dd><dt><code>--allow-insecure-host</code> <i>allow-insecure-host</i></dt><dd><p>Allow insecure connections to a host.</p>
<p>Can be provided multiple times.</p>
<p>Expects to receive either a hostname (e.g., <code>localhost</code>), a host-port pair (e.g., <code>localhost:8080</code>), or a URL (e.g., <code>https://localhost</code>).</p>
<p>WARNING: Hosts included in this list will not be verified against the system&#8217;s certificate store. Only use <code>--allow-insecure-host</code> in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.</p>
</dd><dt><code>--break-system-packages</code></dt><dd><p>Allow uv to modify an <code>EXTERNALLY-MANAGED</code> Python installation.</p>
<p>WARNING: <code>--break-system-packages</code> is intended for use in continuous integration (CI) environments, when installing into Python installations that are managed by an external package manager, like <code>apt</code>. It should be used with caution, as such Python installations explicitly recommend against modifications by other package managers (like uv or <code>pip</code>).</p>
@ -4582,7 +4678,15 @@ uv pip uninstall [OPTIONS] <PACKAGE|--requirement <REQUIREMENT>>
<h3 class="cli-reference">Options</h3>
<dl class="cli-reference"><dt><code>--break-system-packages</code></dt><dd><p>Allow uv to modify an <code>EXTERNALLY-MANAGED</code> Python installation.</p>
<dl class="cli-reference"><dt><code>--allow-insecure-host</code> <i>allow-insecure-host</i></dt><dd><p>Allow insecure connections to a host.</p>
<p>Can be provided multiple times.</p>
<p>Expects to receive either a hostname (e.g., <code>localhost</code>), a host-port pair (e.g., <code>localhost:8080</code>), or a URL (e.g., <code>https://localhost</code>).</p>
<p>WARNING: Hosts included in this list will not be verified against the system&#8217;s certificate store. Only use <code>--allow-insecure-host</code> in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.</p>
</dd><dt><code>--break-system-packages</code></dt><dd><p>Allow uv to modify an <code>EXTERNALLY-MANAGED</code> Python installation.</p>
<p>WARNING: <code>--break-system-packages</code> is intended for use in continuous integration (CI) environments, when installing into Python installations that are managed by an external package manager, like <code>apt</code>. It should be used with caution, as such Python installations explicitly recommend against modifications by other package managers (like uv or <code>pip</code>).</p>
@ -5228,6 +5332,14 @@ uv venv [OPTIONS] [NAME]
<p>WARNING: This option can lead to unexpected behavior if the existing virtual environment and the newly-created virtual environment are linked to different Python interpreters.</p>
</dd><dt><code>--allow-insecure-host</code> <i>allow-insecure-host</i></dt><dd><p>Allow insecure connections to a host.</p>
<p>Can be provided multiple times.</p>
<p>Expects to receive either a hostname (e.g., <code>localhost</code>), a host-port pair (e.g., <code>localhost:8080</code>), or a URL (e.g., <code>https://localhost</code>).</p>
<p>WARNING: Hosts included in this list will not be verified against the system&#8217;s certificate store. Only use <code>--allow-insecure-host</code> in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.</p>
</dd><dt><code>--cache-dir</code> <i>cache-dir</i></dt><dd><p>Path to the cache directory.</p>
<p>Defaults to <code>$HOME/Library/Caches/uv</code> on macOS, <code>$XDG_CACHE_HOME/uv</code> or <code>$HOME/.cache/uv</code> on Linux, and <code>%LOCALAPPDATA%\uv\cache</code> on Windows.</p>

View file

@ -1,4 +1,36 @@
## Global
#### [`allow-insecure-host`](#allow-insecure-host) {: #allow-insecure-host }
Allow insecure connections to host.
Expects to receive either a hostname (e.g., `localhost`), a host-port pair (e.g.,
`localhost:8080`), or a URL (e.g., `https://localhost`).
WARNING: Hosts included in this list will not be verified against the system's certificate
store. Only use `--allow-insecure-host` in a secure network with verified sources, as it
bypasses SSL verification and could expose you to MITM attacks.
**Default value**: `[]`
**Type**: `list[str]`
**Example usage**:
=== "pyproject.toml"
```toml
[tool.uv]
allow-insecure-host = ["localhost:8080"]
```
=== "uv.toml"
```toml
allow-insecure-host = ["localhost:8080"]
```
---
#### [`cache-dir`](#cache-dir) {: #cache-dir }
Path to the cache directory.
@ -1176,6 +1208,39 @@ packages.
---
#### [`allow-insecure-host`](#pip_allow-insecure-host) {: #pip_allow-insecure-host }
<span id="allow-insecure-host"></span>
Allow insecure connections to host.
Expects to receive either a hostname (e.g., `localhost`), a host-port pair (e.g.,
`localhost:8080`), or a URL (e.g., `https://localhost`).
WARNING: Hosts included in this list will not be verified against the system's certificate
store. Only use `--allow-insecure-host` in a secure network with verified sources, as it
bypasses SSL verification and could expose you to MITM attacks.
**Default value**: `[]`
**Type**: `list[str]`
**Example usage**:
=== "pyproject.toml"
```toml
[tool.uv.pip]
allow-insecure-host = ["localhost:8080"]
```
=== "uv.toml"
```toml
[pip]
allow-insecure-host = ["localhost:8080"]
```
---
#### [`annotation-style`](#pip_annotation-style) {: #pip_annotation-style }
<span id="annotation-style"></span>

24
uv.schema.json generated
View file

@ -4,6 +4,16 @@
"description": "Metadata and configuration for uv.",
"type": "object",
"properties": {
"allow-insecure-host": {
"description": "Allow insecure connections to host.\n\nExpects to receive either a hostname (e.g., `localhost`), a host-port pair (e.g., `localhost:8080`), or a URL (e.g., `https://localhost`).\n\nWARNING: Hosts included in this list will not be verified against the system's certificate store. Only use `--allow-insecure-host` in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/TrustedHost"
}
},
"cache-dir": {
"description": "Path to the cache directory.\n\nDefaults to `$HOME/Library/Caches/uv` on macOS, `$XDG_CACHE_HOME/uv` or `$HOME/.cache/uv` on Linux, and `%LOCALAPPDATA%\\uv\\cache` on Windows.",
"type": [
@ -561,6 +571,16 @@
"null"
]
},
"allow-insecure-host": {
"description": "Allow insecure connections to host.\n\nExpects to receive either a hostname (e.g., `localhost`), a host-port pair (e.g., `localhost:8080`), or a URL (e.g., `https://localhost`).\n\nWARNING: Hosts included in this list will not be verified against the system's certificate store. Only use `--allow-insecure-host` in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/TrustedHost"
}
},
"annotation-style": {
"description": "The style of the annotation comments included in the output file, used to indicate the source of each package.",
"anyOf": [
@ -1426,6 +1446,10 @@
}
},
"additionalProperties": false
},
"TrustedHost": {
"description": "A host or host-port pair.",
"type": "string"
}
}
}