From 8b1629c7db7d4bcea5c9fb5d6ac7164299a02839 Mon Sep 17 00:00:00 2001 From: zhom <2717306+zhom@users.noreply.github.com> Date: Tue, 7 Jul 2026 22:43:56 +0400 Subject: [PATCH] fix: self-reap proxy worker off-runtime and redact upstream creds in logs --- src-tauri/src/proxy_server.rs | 40 +++++++++++++++++++++++++++++----- src-tauri/src/proxy_storage.rs | 21 +++++++++++++++--- 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/src-tauri/src/proxy_server.rs b/src-tauri/src/proxy_server.rs index 30a4654..df70c38 100644 --- a/src-tauri/src/proxy_server.rs +++ b/src-tauri/src/proxy_server.rs @@ -1314,6 +1314,24 @@ pub async fn handle_proxy_connection( } } +/// Render an upstream proxy URL for logging with any embedded credentials +/// stripped. `config.upstream_url` carries `scheme://user:pass@host:port`, and +/// these logs land in a world-readable file under the system temp dir, so the +/// userinfo must never be emitted. +fn redacted_upstream(upstream: &str) -> String { + if upstream.is_empty() { + return "none".to_string(); + } + match Url::parse(upstream) { + Ok(u) => match (u.host_str(), u.port()) { + (Some(host), Some(port)) => format!("{}://{host}:{port}", u.scheme()), + (Some(host), None) => format!("{}://{host}", u.scheme()), + _ => "".to_string(), + }, + Err(_) => "".to_string(), + } +} + pub async fn run_proxy_server(config: ProxyConfig) -> Result<(), Box> { log::info!( "Proxy worker starting, looking for config id: {}", @@ -1333,7 +1351,7 @@ pub async fn run_proxy_server(config: ProxyConfig) -> Result<(), Box Result<(), Box match cfg.browser_pid { Some(bpid) if bpid != 0 => { @@ -1660,7 +1685,10 @@ async fn handle_connect_from_buffer( "CONNECT {}:{} (upstream={})", target_host, target_port, - upstream_url.as_deref().unwrap_or("DIRECT") + upstream_url + .as_deref() + .map(redacted_upstream) + .unwrap_or_else(|| "DIRECT".to_string()) ); // Connect to target (directly or via upstream proxy). diff --git a/src-tauri/src/proxy_storage.rs b/src-tauri/src/proxy_storage.rs index fff84e6..aa39844 100644 --- a/src-tauri/src/proxy_storage.rs +++ b/src-tauri/src/proxy_storage.rs @@ -191,7 +191,7 @@ mod tests { #[test] fn test_is_process_running_returns_false_for_dead_pid() { // Spawn a short-lived child and wait for it to exit - let child = std::process::Command::new(if cfg!(windows) { "cmd" } else { "true" }) + let mut child = std::process::Command::new(if cfg!(windows) { "cmd" } else { "true" }) .args(if cfg!(windows) { vec!["/C", "exit"] } else { @@ -200,11 +200,26 @@ mod tests { .spawn() .expect("failed to spawn child"); let pid = child.id(); - let mut child = child; child.wait().expect("child failed"); + // On Windows a terminated process remains a live kernel object (and sysinfo + // keeps reporting it) until the LAST handle to it is closed. std::Child + // holds that handle until dropped, so the check below would otherwise see + // the just-exited process as still running. Drop the handle, then allow a + // brief moment for the OS to reclaim the process before asserting. (In + // production these PIDs belong to detached browsers/workers that no handle + // outlives, so is_process_running already observes their exit promptly.) + drop(child); + let mut became_dead = false; + for _ in 0..50 { + if !is_process_running(pid) { + became_dead = true; + break; + } + std::thread::sleep(std::time::Duration::from_millis(20)); + } assert!( - !is_process_running(pid), + became_dead, "is_process_running must return false for a dead process (PID {pid})" ); }