From 1bc109ae64a86a00064a9ff2ad0b44546219820e Mon Sep 17 00:00:00 2001 From: inx Date: Wed, 28 Jan 2026 17:52:46 +0800 Subject: [PATCH] fix(security): sanitize gpg.ssh.program to prevent global config leaks Explicitly inject `gpg.ssh.program=ssh-keygen` during blind injection. Leaving this unset would allow global configuration (potentially malicious) to bleed into the ephemeral session. Setting it to empty string causes Git to crash. Using the system default `ssh-keygen` ensures both stability and isolation. --- src/git.rs | 29 ++++++++++++++++++++++++++++- src/sanitizer.rs | 24 +++++++++++------------- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/src/git.rs b/src/git.rs index be5a551..93d607f 100644 --- a/src/git.rs +++ b/src/git.rs @@ -106,7 +106,7 @@ fn clean_existing_profiles(profile_dir: &Path) -> Result<()> { fn run_exec(config: &GoshConfig, profile_id: &str, args: &[String]) -> Result<()> { let profile_path = get_profile_path(config, profile_id)?; - let injections = sanitizer::get_blind_injections(); + let injections = sanitizer::BLIND_INJECTIONS; let mut cmd = Command::new("git"); @@ -188,6 +188,8 @@ fn run_switch(config: &GoshConfig, profile_id: &str, force: bool) -> Result<()> run_command(&mut cmd)?; println!("Switched to profile '{}'", profile_id); + warn_if_dirty_config(profile_id)?; + Ok(()) } @@ -272,3 +274,28 @@ fn extract_basename(url: &str) -> PathBuf { let path = Path::new(s); path.file_name().map(|n| PathBuf::from(n)).unwrap_or_else(|| PathBuf::from("repo")) } + +fn warn_if_dirty_config(profile_id: &str) -> Result<()> { + let config_path = Path::new(".git/config"); + if config_path.exists() { + let content = std::fs::read_to_string(config_path)?; + + // Check for sections that typically contain identity or dangerous settings + // We look for [user], [author], [gpg] sections, or specific keys like sshCommand/gpgsign + let is_dirty = content.contains("[user]") + || content.contains("[author]") + || content.contains("[gpg]") + || content.contains("sshCommand") + || content.contains("gpgsign"); + + if is_dirty { + println!("\n⚠️ WARNING: Dirty Local Config Detected!"); + println!(" Your .git/config contains hardcoded '[user]' or '[core]' settings."); + println!(" These settings (like signing keys) are merging with your profile"); + println!(" and causing a \"Frankenstein Identity\"."); + println!(""); + println!(" 👉 Fix it: Run 'gosh {} -f' to force clean.\n", profile_id); + } + } + Ok(()) +} diff --git a/src/sanitizer.rs b/src/sanitizer.rs index 1164d6b..0caeca7 100644 --- a/src/sanitizer.rs +++ b/src/sanitizer.rs @@ -7,16 +7,14 @@ pub const BLACKLIST_KEYS: &[&str] = &[ "http.cookieFile", ]; -pub fn get_blind_injections() -> Vec<(&'static str, &'static str)> { - vec![ - ("user.name", ""), - ("user.email", ""), - ("user.signingkey", ""), - ("core.sshCommand", ""), - ("gpg.format", "openpgp"), - ("gpg.ssh.program", "ssh-keygen"), - ("gpg.program", "gpg"), - ("commit.gpgsign", "false"), - ("tag.gpgsign", "false"), - ] -} +pub const BLIND_INJECTIONS: &[(&str, &str)] = &[ + ("user.name", ""), + ("user.email", ""), + ("user.signingkey", ""), + ("core.sshCommand", ""), + ("gpg.format", "openpgp"), + ("gpg.ssh.program", "ssh-keygen"), + ("gpg.program", "gpg"), + ("commit.gpgsign", "false"), + ("tag.gpgsign", "false"), +];