Add support for `HF_TOKEN` (#14797)

## Summary

If `HF_TOKEN` is set, we'll automatically wire it up to authenticate
requests when hitting private `huggingface.co` URLs in `uv run`.

## Test Plan

An unauthenticated request:

```
> cargo run -- run https://huggingface.co/datasets/cmarsh/test/resolve/main/main.py

  File "/var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/mainYadr5M.py", line 1
    Invalid username or password.
            ^^^^^^^^
SyntaxError: invalid syntax
```

An authenticated request:

```
> HF_TOKEN=hf_... cargo run run https://huggingface.co/datasets/cmarsh/test/resolve/main/main.py

Hello from main.py!
```
This commit is contained in:
Charlie Marsh 2025-07-21 16:55:33 -04:00 committed by GitHub
parent 7a56950bab
commit a3ea1b69f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 91 additions and 3 deletions

View File

@ -15,6 +15,7 @@ mod credentials;
mod index;
mod keyring;
mod middleware;
mod providers;
mod realm;
// TODO(zanieb): Consider passing a cache explicitly throughout

View File

@ -7,6 +7,7 @@ use reqwest::{Request, Response};
use reqwest_middleware::{Error, Middleware, Next};
use tracing::{debug, trace, warn};
use crate::providers::HuggingFaceProvider;
use crate::{
CREDENTIALS_CACHE, CredentialsCache, KeyringProvider,
cache::FetchUrl,
@ -457,9 +458,8 @@ impl AuthMiddleware {
Some(credentials)
};
return self
.complete_request(credentials, request, extensions, next, auth_policy)
.await;
self.complete_request(credentials, request, extensions, next, auth_policy)
.await
}
/// Fetch credentials for a URL.
@ -503,6 +503,13 @@ impl AuthMiddleware {
return credentials;
}
// Support for known providers, like Hugging Face.
if let Some(credentials) = HuggingFaceProvider::credentials_for(url).map(Arc::new) {
debug!("Found Hugging Face credentials for {url}");
self.cache().fetches.done(key, Some(credentials.clone()));
return Some(credentials);
}
// Netrc support based on: <https://github.com/gribouille/netrc>.
let credentials = if let Some(credentials) = self.netrc.get().and_then(|netrc| {
debug!("Checking netrc for credentials for {url}");

View File

@ -0,0 +1,49 @@
use std::sync::LazyLock;
use tracing::debug;
use url::Url;
use uv_static::EnvVars;
use crate::Credentials;
use crate::realm::Realm;
/// The [`Realm`] for the Hugging Face platform.
static HUGGING_FACE_REALM: LazyLock<Realm> = LazyLock::new(|| {
let url = Url::parse("https://huggingface.co").expect("Failed to parse Hugging Face URL");
Realm::from(&url)
});
/// The authentication token for the Hugging Face platform, if set.
static HUGGING_FACE_TOKEN: LazyLock<Option<Vec<u8>>> = LazyLock::new(|| {
// Extract the Hugging Face token from the environment variable, if it exists.
let hf_token = std::env::var(EnvVars::HF_TOKEN)
.ok()
.map(String::into_bytes)
.filter(|token| !token.is_empty())?;
if std::env::var_os(EnvVars::UV_NO_HF_TOKEN).is_some() {
debug!("Ignoring Hugging Face token from environment due to `UV_NO_HF_TOKEN`");
return None;
}
debug!("Found Hugging Face token in environment");
Some(hf_token)
});
/// A provider for authentication credentials for the Hugging Face platform.
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct HuggingFaceProvider;
impl HuggingFaceProvider {
/// Returns the credentials for the Hugging Face platform, if available.
pub(crate) fn credentials_for(url: &Url) -> Option<Credentials> {
if Realm::from(url) == *HUGGING_FACE_REALM {
if let Some(token) = HUGGING_FACE_TOKEN.as_ref() {
return Some(Credentials::Bearer {
token: token.clone(),
});
}
}
None
}
}

View File

@ -765,4 +765,11 @@ impl EnvVars {
/// Disable GitHub-specific requests that allow uv to skip `git fetch` in some circumstances.
pub const UV_NO_GITHUB_FAST_PATH: &'static str = "UV_NO_GITHUB_FAST_PATH";
/// Authentication token for Hugging Face requests. When set, uv will use this token
/// when making requests to `https://huggingface.co/` and any subdomains.
pub const HF_TOKEN: &'static str = "HF_TOKEN";
/// Disable Hugging Face authentication, even if `HF_TOKEN` is set.
pub const UV_NO_HF_TOKEN: &'static str = "UV_NO_HF_TOKEN";
}

View File

@ -151,3 +151,18 @@ 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.
## Hugging Face support
uv supports automatic authentication for the Hugging Face Hub. Specifically, if the `HF_TOKEN`
environment variable is set, uv will propagate it to requests to `huggingface.co`.
This is particularly useful for accessing private scripts in Hugging Face Datasets. For example, you
can run the following command to execute the script `main.py` script from a private dataset:
```console
$ HF_TOKEN=hf_... uv run https://huggingface.co/datasets/<user>/<name>/resolve/<branch>/main.py
```
You can disable automatic Hugging Face authentication by setting the `UV_NO_HF_TOKEN=1` environment
variable.

View File

@ -252,6 +252,10 @@ Ignore `.env` files when executing `uv run` commands.
Disable GitHub-specific requests that allow uv to skip `git fetch` in some circumstances.
### `UV_NO_HF_TOKEN`
Disable Hugging Face authentication, even if `HF_TOKEN` is set.
### `UV_NO_INSTALLER_METADATA`
Skip writing `uv` installer metadata files (e.g., `INSTALLER`, `REQUESTED`, and `direct_url.json`) to site-packages `.dist-info` directories.
@ -528,6 +532,11 @@ See [force-color.org](https://force-color.org).
Used for trusted publishing via `uv publish`.
### `HF_TOKEN`
Authentication token for Hugging Face requests. When set, uv will use this token
when making requests to `https://huggingface.co/` and any subdomains.
### `HOME`
The standard `HOME` env var.