mirror of
				https://github.com/astral-sh/uv.git
				synced 2025-11-03 21:23:54 +00:00 
			
		
		
		
	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
					
				
					 5 changed files with 54 additions and 9 deletions
				
			
		| 
						 | 
				
			
			@ -29,6 +29,6 @@ impl AsRef<[u8]> for AccessToken {
 | 
			
		|||
 | 
			
		||||
impl std::fmt::Display for AccessToken {
 | 
			
		||||
    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
 | 
			
		||||
    Bearer {
 | 
			
		||||
        /// 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 {
 | 
			
		||||
    /// Create a set of HTTP Basic Authentication credentials.
 | 
			
		||||
    #[allow(dead_code)]
 | 
			
		||||
| 
						 | 
				
			
			@ -115,7 +145,9 @@ impl Credentials {
 | 
			
		|||
    /// Create a set of Bearer Authentication credentials.
 | 
			
		||||
    #[allow(dead_code)]
 | 
			
		||||
    pub fn bearer(token: Vec<u8>) -> Self {
 | 
			
		||||
        Self::Bearer { token }
 | 
			
		||||
        Self::Bearer {
 | 
			
		||||
            token: Token::new(token),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn username(&self) -> Option<&str> {
 | 
			
		||||
| 
						 | 
				
			
			@ -286,7 +318,7 @@ impl Credentials {
 | 
			
		|||
        // Parse a `Bearer` authentication header.
 | 
			
		||||
        if let Some(token) = header.as_bytes().strip_prefix(b"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(****) }"
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[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 crate::Credentials;
 | 
			
		||||
use crate::credentials::Token;
 | 
			
		||||
use crate::realm::{Realm, RealmRef};
 | 
			
		||||
 | 
			
		||||
/// The [`Realm`] for the Hugging Face platform.
 | 
			
		||||
| 
						 | 
				
			
			@ -45,7 +46,7 @@ impl HuggingFaceProvider {
 | 
			
		|||
        if RealmRef::from(url) == *HUGGING_FACE_REALM {
 | 
			
		||||
            if let Some(token) = HUGGING_FACE_TOKEN.as_ref() {
 | 
			
		||||
                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_static::EnvVars;
 | 
			
		||||
 | 
			
		||||
use crate::credentials::Token;
 | 
			
		||||
use crate::{AccessToken, Credentials, Realm};
 | 
			
		||||
 | 
			
		||||
/// 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 {
 | 
			
		||||
    fn from(access_token: AccessToken) -> Self {
 | 
			
		||||
        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_static::EnvVars;
 | 
			
		||||
 | 
			
		||||
use crate::credentials::{Password, Username};
 | 
			
		||||
use crate::credentials::{Password, Token, Username};
 | 
			
		||||
use crate::realm::Realm;
 | 
			
		||||
use crate::service::Service;
 | 
			
		||||
use crate::{Credentials, KeyringProvider};
 | 
			
		||||
| 
						 | 
				
			
			@ -142,7 +142,7 @@ impl From<TomlCredential> for TomlCredentialWire {
 | 
			
		|||
                username: Username::new(None),
 | 
			
		||||
                scheme: AuthScheme::Bearer,
 | 
			
		||||
                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 {
 | 
			
		||||
                    token: value.token.unwrap().into_bytes(),
 | 
			
		||||
                    token: Token::new(value.token.unwrap().into_bytes()),
 | 
			
		||||
                };
 | 
			
		||||
                Ok(Self {
 | 
			
		||||
                    service: value.service,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue