Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c321815
feat(auth): add configuration profiles for account switching
joeVenner Mar 7, 2026
ceb9579
fix(auth): centralize base_dir resolution and fix arg parsing
joeVenner Mar 7, 2026
60bb1aa
fix(auth): prevent path traversal and centralize profile resolution
joeVenner Mar 7, 2026
1cbd74b
fix(auth): handle file removal errors during profile switch
joeVenner Mar 7, 2026
4147995
refactor(auth): deduplicate profile logic and paths
joeVenner Mar 7, 2026
1ea183c
fix(auth): prevent path traversal from disk active_profile
joeVenner Mar 7, 2026
fba0899
Update src/auth_commands.rs
joeVenner Mar 7, 2026
32f5525
fix(auth): resolve profile parsing compiler error and simplify iteration
joeVenner Mar 7, 2026
2a07ceb
fix(auth): prevent empty profile names in validation
joeVenner Mar 7, 2026
ed48063
fix(auth): secure cross-platform profile names and parsing
joeVenner Mar 7, 2026
2f62a9d
fix(auth): secure configuration config env vars from path traversal a…
joeVenner Mar 7, 2026
e7bf8d9
fix(cli): prevent subcommands from being parsed as profile names
joeVenner Mar 7, 2026
2cdcb80
fix(cli): allow profile names to match service names
joeVenner Mar 7, 2026
311351a
fix(cli): resolve --help regression and async auth file system I/O
joeVenner Mar 7, 2026
1218c18
fix(auth): make config path security check cross-platform
joeVenner Mar 7, 2026
a440634
fix(cli): resolve --api-version parsing regression
joeVenner Mar 7, 2026
fb44e06
fix(auth): prevent symlink traversal and enforce lowercase profiles
joeVenner Mar 7, 2026
014a653
fix(auth): secure config symlink TOCTOU vulnerability
joeVenner Mar 7, 2026
257e085
fix(auth): secure path configuration fallback from dot-dot traversals
joeVenner Mar 7, 2026
62656fa
fix(auth): reject profile names starting with a hyphen
joeVenner Mar 7, 2026
8cea731
refactor: tokio async migration and clap --profile integration
joeVenner Mar 7, 2026
576b035
fix(auth): secure TOCTOU vulnerability and replace blocking exists calls
joeVenner Mar 7, 2026
9a97916
fix(auth): replace remaining std::fs blocking operations with tokio::…
joeVenner Mar 7, 2026
d6d7e71
fix(auth): handle non-existent GOOGLE_WORKSPACE_CLI_CONFIG_DIR gracef…
joeVenner Mar 7, 2026
558af1d
fix(auth): safely modify unix permissions reading metadata prior to r…
joeVenner Mar 7, 2026
87f738e
fix(async): replace remaining std::fs blocking I/O calls with tokio::…
joeVenner Mar 7, 2026
0a4f5a2
fix(auth): add missing parent check to config path validation and sta…
joeVenner Mar 7, 2026
ea2e600
fix(security): address TOCTOU canonical parent symlink vulnerability …
joeVenner Mar 7, 2026
dd45838
refactor: extract duplicated security checks, simplify async I/O toke…
joeVenner Mar 7, 2026
a8a4620
fix(security): resolve TOCTOU config directory race condition via pro…
joeVenner Mar 7, 2026
fca3f35
fix(cli): replace thread-unsafe std::env::set_var with OnceLock for p…
joeVenner Mar 7, 2026
d8f2ab1
refactor: replace static RwLock cache with idiomatic tokio::sync::Onc…
joeVenner Mar 7, 2026
7238c0d
fix(security): resolve final sync I/O traces across async credential …
joeVenner Mar 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ pub async fn get_token(scopes: &[&str]) -> anyhow::Result<String> {
}

let creds_file = std::env::var("GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE").ok();
let config_dir = crate::auth_commands::config_dir();
let enc_path = credential_store::encrypted_credentials_path();
let config_dir = crate::auth_commands::config_dir().await;
let enc_path = credential_store::encrypted_credentials_path().await;
let default_path = config_dir.join("credentials.json");
let token_cache = config_dir.join("token_cache.json");

Expand Down Expand Up @@ -175,7 +175,7 @@ async fn load_credentials_inner(
// 1. Explicit env var — plaintext file (User or Service Account)
if let Some(path) = env_file {
let p = PathBuf::from(path);
if p.exists() {
if tokio::fs::metadata(&p).await.is_ok() {
let content = tokio::fs::read_to_string(&p)
.await
.with_context(|| format!("Failed to read credentials from {path}"))?;
Expand All @@ -187,8 +187,9 @@ async fn load_credentials_inner(
}

// 2. Encrypted credentials (always AuthorizedUser for now)
if enc_path.exists() {
if tokio::fs::metadata(enc_path).await.is_ok() {
let json_str = credential_store::load_encrypted_from_path(enc_path)
.await
.context("Failed to decrypt credentials")?;

let creds: serde_json::Value =
Expand Down Expand Up @@ -216,7 +217,7 @@ async fn load_credentials_inner(
}

// 3. Plaintext credentials at default path (Default to AuthorizedUser)
if default_path.exists() {
if tokio::fs::metadata(default_path).await.is_ok() {
return Ok(Credential::AuthorizedUser(
yup_oauth2::read_authorized_user_secret(default_path)
.await
Expand All @@ -229,7 +230,7 @@ async fn load_credentials_inner(
// 4a. GOOGLE_APPLICATION_CREDENTIALS env var (explicit path — hard error if missing)
if let Ok(adc_env) = std::env::var("GOOGLE_APPLICATION_CREDENTIALS") {
let adc_path = PathBuf::from(&adc_env);
if adc_path.exists() {
if tokio::fs::metadata(&adc_path).await.is_ok() {
let content = tokio::fs::read_to_string(&adc_path)
.await
.with_context(|| format!("Failed to read ADC from {adc_env}"))?;
Expand All @@ -243,7 +244,7 @@ async fn load_credentials_inner(
// 4b. Well-known ADC path: ~/.config/gcloud/application_default_credentials.json
// (populated by `gcloud auth application-default login`). Silent if absent.
if let Some(well_known) = adc_well_known_path() {
if well_known.exists() {
if tokio::fs::metadata(&well_known).await.is_ok() {
let content = tokio::fs::read_to_string(&well_known)
.await
.with_context(|| format!("Failed to read ADC from {}", well_known.display()))?;
Expand Down Expand Up @@ -539,7 +540,7 @@ mod tests {
let enc_path = dir.path().join("credentials.enc");

// Encrypt and write
let encrypted = crate::credential_store::encrypt(json.as_bytes()).unwrap();
let encrypted = crate::credential_store::encrypt(json.as_bytes()).await.unwrap();
std::fs::write(&enc_path, &encrypted).unwrap();

let res = load_credentials_inner(None, &enc_path, &PathBuf::from("/does/not/exist"))
Expand Down Expand Up @@ -576,7 +577,7 @@ mod tests {
let enc_path = dir.path().join("credentials.enc");
let plain_path = dir.path().join("credentials.json");

let encrypted = crate::credential_store::encrypt(enc_json.as_bytes()).unwrap();
let encrypted = crate::credential_store::encrypt(enc_json.as_bytes()).await.unwrap();
std::fs::write(&enc_path, &encrypted).unwrap();
std::fs::write(&plain_path, plain_json).unwrap();

Expand Down
Loading