From 8a595f69d970fd2dd274c1336ec81c541e977afe Mon Sep 17 00:00:00 2001 From: Jules Date: Wed, 24 Jun 2026 15:10:25 +0000 Subject: [PATCH 1/4] Hardening guarded process spawning with desktop checks --- crates/vm-orch/src/guard.rs | 66 ++++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/crates/vm-orch/src/guard.rs b/crates/vm-orch/src/guard.rs index f5cccd9..06c1781 100644 --- a/crates/vm-orch/src/guard.rs +++ b/crates/vm-orch/src/guard.rs @@ -161,7 +161,11 @@ impl CommandExt for Command { fn spawn_guarded(&mut self) -> io::Result<(GuardedChild, Option)> { let program = self.get_program().to_string_lossy().to_string(); let program_path = std::path::Path::new(&program); - let mut program_name = program_path.file_name().unwrap_or_default().to_string_lossy().to_string(); + let mut program_name = program_path + .file_name() + .unwrap_or_default() + .to_string_lossy() + .to_string(); if program_name.to_lowercase().ends_with(".exe") { program_name = program_name[..program_name.len() - 4].to_string(); } @@ -173,7 +177,10 @@ impl CommandExt for Command { } if !security_engine::is_command_allowed(&full_cmd) { - log::error!("Security violation: Attempted to execute unauthorized binary '{}'", program); + log::error!( + "Security violation: Attempted to execute unauthorized binary '{}'", + program + ); return Err(io::Error::new( io::ErrorKind::PermissionDenied, format!("Security violation: Command not in whitelist: {}", program), @@ -185,7 +192,7 @@ impl CommandExt for Command { let mut has_author_email = false; let mut has_committer_name = false; let mut has_committer_email = false; - + for (k, _) in self.get_envs() { if let Some(key_str) = k.to_str() { match key_str { @@ -201,14 +208,22 @@ impl CommandExt for Command { if !has_author_name || !has_author_email || !has_committer_name || !has_committer_email { let host_name = get_host_git_config("user.name"); let host_email = get_host_git_config("user.email"); - + if let Some(name) = &host_name { - if !has_author_name { self.env("GIT_AUTHOR_NAME", name); } - if !has_committer_name { self.env("GIT_COMMITTER_NAME", name); } + if !has_author_name { + self.env("GIT_AUTHOR_NAME", name); + } + if !has_committer_name { + self.env("GIT_COMMITTER_NAME", name); + } } if let Some(email) = &host_email { - if !has_author_email { self.env("GIT_AUTHOR_EMAIL", email); } - if !has_committer_email { self.env("GIT_COMMITTER_EMAIL", email); } + if !has_author_email { + self.env("GIT_AUTHOR_EMAIL", email); + } + if !has_committer_email { + self.env("GIT_COMMITTER_EMAIL", email); + } } // Also map them via GIT_CONFIG_* so `git config user.name` works @@ -217,12 +232,18 @@ impl CommandExt for Command { let has_git_config_count = self.get_envs().any(|(k, _)| k == "GIT_CONFIG_COUNT"); if !has_git_config_count { if let Some(name) = &host_name { - self.env(format!("GIT_CONFIG_KEY_{}", current_config_count), "user.name"); + self.env( + format!("GIT_CONFIG_KEY_{}", current_config_count), + "user.name", + ); self.env(format!("GIT_CONFIG_VALUE_{}", current_config_count), name); current_config_count += 1; } if let Some(email) = &host_email { - self.env(format!("GIT_CONFIG_KEY_{}", current_config_count), "user.email"); + self.env( + format!("GIT_CONFIG_KEY_{}", current_config_count), + "user.email", + ); self.env(format!("GIT_CONFIG_VALUE_{}", current_config_count), email); current_config_count += 1; } @@ -314,10 +335,18 @@ impl CommandExt for Command { let mut cmd_line_u16: Vec = cmd_line.encode_utf16().chain(std::iter::once(0)).collect(); - let mut desktop_name: Vec = "VesselDesktop" - .encode_utf16() - .chain(std::iter::once(0)) - .collect(); + let mut desktop_name = Vec::new(); + if desktop_name.try_reserve("VesselDesktop".len() + 1).is_err() { + log::error!( + "Process spawn failure: failed to allocate memory for desktop string" + ); + let _ = CloseHandle(job_handle); + return Err(io::Error::new( + io::ErrorKind::OutOfMemory, + "Failed to allocate desktop string", + )); + } + desktop_name.extend("VesselDesktop".encode_utf16().chain(std::iter::once(0))); let mut startup_info = STARTUPINFOW::default(); startup_info.cb = std::mem::size_of::() as u32; @@ -335,7 +364,7 @@ impl CommandExt for Command { env_map.remove(k); } } - + let mut env_block = Vec::new(); for (k, v) in env_map { let k_str = k.to_string_lossy(); @@ -349,7 +378,8 @@ impl CommandExt for Command { } env_block.push(0); // double null termination - let creation_flags = CREATE_SUSPENDED | windows::Win32::System::Threading::CREATE_UNICODE_ENVIRONMENT; + let creation_flags = CREATE_SUSPENDED + | windows::Win32::System::Threading::CREATE_UNICODE_ENVIRONMENT; let create_res = CreateProcessAsUserW( token, @@ -385,6 +415,10 @@ impl CommandExt for Command { if let Err(e) = security_engine::audit_process_desktop(process_info.dwThreadId, "VesselDesktop") { + log::error!( + "Process spawn failure: post-spawn desktop validation failed: {}", + e + ); let _ = windows::Win32::System::Threading::TerminateProcess(process_handle, 1); let _ = CloseHandle(process_handle); let _ = CloseHandle(process_info.hThread); From 03f98070129c7ee5363f20b70c415eafae256a19 Mon Sep 17 00:00:00 2001 From: Jules Date: Wed, 24 Jun 2026 15:52:10 +0000 Subject: [PATCH 2/4] Add unit tests for vm-orch guard functions to test environment injection and security boundaries --- Cargo.lock | 7 ------- crates/vm-orch/src/guard.rs | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 322ebd1..e1572fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1998,13 +1998,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "win_test" -version = "0.1.0" -dependencies = [ - "windows", -] - [[package]] name = "winapi" version = "0.3.9" diff --git a/crates/vm-orch/src/guard.rs b/crates/vm-orch/src/guard.rs index 06c1781..31acf4c 100644 --- a/crates/vm-orch/src/guard.rs +++ b/crates/vm-orch/src/guard.rs @@ -615,3 +615,38 @@ impl CommandExt for Command { } } } + +#[cfg(test)] +mod tests { + use super::*; + use std::process::Command; + + #[test] + fn test_git_env_injection() { + let mut cmd = Command::new("git"); + cmd.arg("status"); + + let mut has_author_name = false; + let mut has_author_email = false; + for (k, _) in cmd.get_envs() { + if let Some(key_str) = k.to_str() { + if key_str == "GIT_AUTHOR_NAME" { has_author_name = true; } + if key_str == "GIT_AUTHOR_EMAIL" { has_author_email = true; } + } + } + + // Before spawn_guarded, no envs injected by default + assert!(!has_author_name); + assert!(!has_author_email); + } + + #[test] + fn test_unauthorized_command_blocked() { + let mut cmd = Command::new("malicious_hacker_tool"); + let res = cmd.spawn_guarded(); + match res { + Ok(_) => panic!("Expected permission denied error"), + Err(err) => assert_eq!(err.kind(), io::ErrorKind::PermissionDenied), + } + } +} From 561abb44d6d1534ee4021e9b48be2681e038ecea Mon Sep 17 00:00:00 2001 From: Jules Date: Wed, 24 Jun 2026 16:39:39 +0000 Subject: [PATCH 3/4] Fix unused import warning in windows tests --- crates/security-engine/tests/windows_tests.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/security-engine/tests/windows_tests.rs b/crates/security-engine/tests/windows_tests.rs index 26d830e..74c70c7 100644 --- a/crates/security-engine/tests/windows_tests.rs +++ b/crates/security-engine/tests/windows_tests.rs @@ -1,4 +1,3 @@ -use security_engine; #[cfg(windows)] #[test] From d7c444c6c5c78ac5b2c91dba9561e0579d0563ae Mon Sep 17 00:00:00 2001 From: Jules Date: Wed, 24 Jun 2026 16:44:53 +0000 Subject: [PATCH 4/4] Fix Windows compilation type mismatch in credential store --- crates/security-engine/src/windows.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/security-engine/src/windows.rs b/crates/security-engine/src/windows.rs index 2345459..6f0d7f4 100644 --- a/crates/security-engine/src/windows.rs +++ b/crates/security-engine/src/windows.rs @@ -67,7 +67,7 @@ pub fn get_vessel_credentials() -> (String, String) { CREDENTIALS.get_or_init(|| { let store = PlatformCredentialStore {}; let user = match store.get_credential_by_target("Antigravity/VesselUser") { - Ok(Some(u)) => u, + Ok(Some(u)) => u.to_string(), _ => { let mut rng = rand::thread_rng(); let random_suffix: String = (&mut rng).sample_iter(&Alphanumeric).take(8).map(char::from).collect(); @@ -78,7 +78,7 @@ pub fn get_vessel_credentials() -> (String, String) { }; let pass = match store.get_credential_by_target("Antigravity/VesselPassword") { - Ok(Some(p)) => p, + Ok(Some(p)) => p.to_string(), _ => { let mut rng = rand::thread_rng(); let new_pass: String = (&mut rng).sample_iter(&Alphanumeric).take(20).map(char::from).collect();