From ddc1460d2631b355fb3b2946e176ed92477c33ab Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 13:10:26 +0000 Subject: [PATCH] Fix command injection vulnerability by restricting bitcoin-cli binary name - Validates `profile.bitcoin_cli` to ensure the executable file name is strictly `bitcoin-cli` or `bitcoin-cli.exe`. - Removes unused and duplicate `run_bitcoin_cli` from `src/wallet_service.rs` to centralize command execution and minimize attack surface. Co-authored-by: bitcoiner-dev <75873427+bitcoiner-dev@users.noreply.github.com> --- src/utils.rs | 11 +++++++++++ src/wallet_service.rs | 22 ---------------------- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/src/utils.rs b/src/utils.rs index 8e3996f..e4bf4c1 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -104,6 +104,17 @@ pub fn run_bitcoin_cli( profile: &Profile, args: &[String], ) -> Result { + let file_name = std::path::Path::new(&profile.bitcoin_cli) + .file_name() + .and_then(|n| n.to_str()) + .unwrap_or(""); + + if file_name != "bitcoin-cli" && file_name != "bitcoin-cli.exe" { + return Err(crate::error::AppError::Config( + "Invalid bitcoin-cli binary. Only 'bitcoin-cli' or 'bitcoin-cli.exe' are allowed for security reasons.".to_string(), + )); + } + let mut cmd = std::process::Command::new(&profile.bitcoin_cli); for arg in &profile.bitcoin_cli_args { cmd.arg(arg); diff --git a/src/wallet_service.rs b/src/wallet_service.rs index f9e7ef4..cb15d61 100644 --- a/src/wallet_service.rs +++ b/src/wallet_service.rs @@ -6,7 +6,6 @@ use rpassword::read_password; use std::fs; use std::io::{self, Write}; use std::path::{Path, PathBuf}; -use std::process::Command as ShellCommand; pub use zinc_core::{ decrypt_wallet_internal, encrypt_wallet_internal, generate_wallet_internal, @@ -226,27 +225,6 @@ pub fn persist_wallet_session(session: &mut WalletSession) -> Result<(), AppErro write_profile(&session.profile_path, &session.profile) } -#[allow(dead_code)] -pub fn run_bitcoin_cli(profile: &Profile, args: &[String]) -> Result { - let output = ShellCommand::new(&profile.bitcoin_cli) - .args(&profile.bitcoin_cli_args) - .args(args) - .output() - .map_err(|e| AppError::Config(format!("failed to launch {}: {e}", profile.bitcoin_cli)))?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); - let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string(); - let details = if !stderr.is_empty() { stderr } else { stdout }; - return Err(AppError::Network(format!( - "bitcoin-cli command failed: {} {}", - profile.bitcoin_cli, details - ))); - } - - Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) -} - #[cfg(test)] mod tests { use super::*;