mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-02 12:59:45 +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