mirror of https://github.com/astral-sh/uv
Obfuscate Bearer Token values in logs (#16164)
Sometimes a credential's `Debug` formatted value appears in tracing logs - make sure the credential doesn't appear there. ## Test plan Added a test case + ran ``` uv pip install --default-index $PYX_API_URL/$SOME_INDEX $SOME_PACKAGE -vv ``` With an authenticated uv client and confirmed the tokens are obfuscated. --------- Co-authored-by: Zsolt Dollenstein <zsol.zsol@gmail.com>
This commit is contained in:
parent
01d43382be
commit
15829bb30a
|
|
@ -29,6 +29,6 @@ impl AsRef<[u8]> for AccessToken {
|
||||||
|
|
||||||
impl std::fmt::Display for AccessToken {
|
impl std::fmt::Display for AccessToken {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{}", self.0)
|
write!(f, "****")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ pub enum Credentials {
|
||||||
/// RFC 6750 Bearer Token Authentication
|
/// RFC 6750 Bearer Token Authentication
|
||||||
Bearer {
|
Bearer {
|
||||||
/// The token to use for authentication.
|
/// The token to use for authentication.
|
||||||
token: Vec<u8>,
|
token: Token,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,6 +102,36 @@ impl fmt::Debug for Password {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Default, Deserialize)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub struct Token(Vec<u8>);
|
||||||
|
|
||||||
|
impl Token {
|
||||||
|
pub fn new(token: Vec<u8>) -> Self {
|
||||||
|
Self(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the [`Token`] as a byte slice.
|
||||||
|
pub fn as_slice(&self) -> &[u8] {
|
||||||
|
self.0.as_slice()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert the [`Token`] into its underlying [`Vec<u8>`].
|
||||||
|
pub fn into_bytes(self) -> Vec<u8> {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return whether the [`Token`] is empty.
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.0.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Token {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "****")
|
||||||
|
}
|
||||||
|
}
|
||||||
impl Credentials {
|
impl Credentials {
|
||||||
/// Create a set of HTTP Basic Authentication credentials.
|
/// Create a set of HTTP Basic Authentication credentials.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
|
@ -115,7 +145,9 @@ impl Credentials {
|
||||||
/// Create a set of Bearer Authentication credentials.
|
/// Create a set of Bearer Authentication credentials.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn bearer(token: Vec<u8>) -> Self {
|
pub fn bearer(token: Vec<u8>) -> Self {
|
||||||
Self::Bearer { token }
|
Self::Bearer {
|
||||||
|
token: Token::new(token),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn username(&self) -> Option<&str> {
|
pub fn username(&self) -> Option<&str> {
|
||||||
|
|
@ -286,7 +318,7 @@ impl Credentials {
|
||||||
// Parse a `Bearer` authentication header.
|
// Parse a `Bearer` authentication header.
|
||||||
if let Some(token) = header.as_bytes().strip_prefix(b"Bearer ") {
|
if let Some(token) = header.as_bytes().strip_prefix(b"Bearer ") {
|
||||||
return Some(Self::Bearer {
|
return Some(Self::Bearer {
|
||||||
token: token.to_vec(),
|
token: Token::new(token.to_vec()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -591,4 +623,15 @@ mod tests {
|
||||||
"Basic { username: Username(Some(\"user\")), password: Some(****) }"
|
"Basic { username: Username(Some(\"user\")), password: Some(****) }"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bearer_token_obfuscation() {
|
||||||
|
let token = "super_secret_token";
|
||||||
|
let credentials = Credentials::bearer(token.into());
|
||||||
|
let debugged = format!("{credentials:?}");
|
||||||
|
assert!(
|
||||||
|
!debugged.contains(token),
|
||||||
|
"Token should be obfuscated in Debug impl: {debugged}"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ use uv_static::EnvVars;
|
||||||
use uv_warnings::warn_user_once;
|
use uv_warnings::warn_user_once;
|
||||||
|
|
||||||
use crate::Credentials;
|
use crate::Credentials;
|
||||||
|
use crate::credentials::Token;
|
||||||
use crate::realm::{Realm, RealmRef};
|
use crate::realm::{Realm, RealmRef};
|
||||||
|
|
||||||
/// The [`Realm`] for the Hugging Face platform.
|
/// The [`Realm`] for the Hugging Face platform.
|
||||||
|
|
@ -45,7 +46,7 @@ impl HuggingFaceProvider {
|
||||||
if RealmRef::from(url) == *HUGGING_FACE_REALM {
|
if RealmRef::from(url) == *HUGGING_FACE_REALM {
|
||||||
if let Some(token) = HUGGING_FACE_TOKEN.as_ref() {
|
if let Some(token) = HUGGING_FACE_TOKEN.as_ref() {
|
||||||
return Some(Credentials::Bearer {
|
return Some(Credentials::Bearer {
|
||||||
token: token.clone(),
|
token: Token::new(token.clone()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ use uv_small_str::SmallString;
|
||||||
use uv_state::{StateBucket, StateStore};
|
use uv_state::{StateBucket, StateStore};
|
||||||
use uv_static::EnvVars;
|
use uv_static::EnvVars;
|
||||||
|
|
||||||
|
use crate::credentials::Token;
|
||||||
use crate::{AccessToken, Credentials, Realm};
|
use crate::{AccessToken, Credentials, Realm};
|
||||||
|
|
||||||
/// Retrieve the pyx API key from the environment variable, or return `None`.
|
/// Retrieve the pyx API key from the environment variable, or return `None`.
|
||||||
|
|
@ -84,7 +85,7 @@ impl From<PyxTokens> for Credentials {
|
||||||
impl From<AccessToken> for Credentials {
|
impl From<AccessToken> for Credentials {
|
||||||
fn from(access_token: AccessToken) -> Self {
|
fn from(access_token: AccessToken) -> Self {
|
||||||
Self::Bearer {
|
Self::Bearer {
|
||||||
token: access_token.into_bytes(),
|
token: Token::new(access_token.into_bytes()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ use uv_redacted::DisplaySafeUrl;
|
||||||
use uv_state::{StateBucket, StateStore};
|
use uv_state::{StateBucket, StateStore};
|
||||||
use uv_static::EnvVars;
|
use uv_static::EnvVars;
|
||||||
|
|
||||||
use crate::credentials::{Password, Username};
|
use crate::credentials::{Password, Token, Username};
|
||||||
use crate::realm::Realm;
|
use crate::realm::Realm;
|
||||||
use crate::service::Service;
|
use crate::service::Service;
|
||||||
use crate::{Credentials, KeyringProvider};
|
use crate::{Credentials, KeyringProvider};
|
||||||
|
|
@ -142,7 +142,7 @@ impl From<TomlCredential> for TomlCredentialWire {
|
||||||
username: Username::new(None),
|
username: Username::new(None),
|
||||||
scheme: AuthScheme::Bearer,
|
scheme: AuthScheme::Bearer,
|
||||||
password: None,
|
password: None,
|
||||||
token: Some(String::from_utf8(token).expect("Token is valid UTF-8")),
|
token: Some(String::from_utf8(token.into_bytes()).expect("Token is valid UTF-8")),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -190,7 +190,7 @@ impl TryFrom<TomlCredentialWire> for TomlCredential {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let credentials = Credentials::Bearer {
|
let credentials = Credentials::Bearer {
|
||||||
token: value.token.unwrap().into_bytes(),
|
token: Token::new(value.token.unwrap().into_bytes()),
|
||||||
};
|
};
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
service: value.service,
|
service: value.service,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue