Skip to content

Commit 5b55695

Browse files
committed
feat(sandbox): implement cross-platform support with Windows fallback
- Add conditional compilation for Unix-specific gaol sandbox functionality - Implement Windows-compatible SandboxExecutor with no-op sandboxing - Update ProfileBuilder to handle both Unix and Windows platforms - Add platform-specific dependency declaration for gaol crate - Modify agent and claude commands to use appropriate sandbox implementation - Update test modules with conditional compilation for Unix-only tests - Ensure graceful degradation on Windows with logging warnings This change enables the application to build and run on Windows while maintaining full sandbox security on Unix-like systems.
1 parent 3dbeaa4 commit 5b55695

7 files changed

Lines changed: 255 additions & 50 deletions

File tree

src-tauri/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ zstd = "0.13"
5050
uuid = { version = "1.6", features = ["v4", "serde"] }
5151
walkdir = "2"
5252

53+
[target.'cfg(unix)'.dependencies]
54+
gaol = "0.2"
55+
5356
[target.'cfg(target_os = "macos")'.dependencies]
5457
cocoa = "0.26"
5558
objc = "0.2"

src-tauri/src/commands/agents.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -996,12 +996,20 @@ pub async fn execute_agent(
996996
Ok(build_result) => {
997997

998998
// Create the enhanced sandbox executor
999+
#[cfg(unix)]
9991000
let executor = crate::sandbox::executor::SandboxExecutor::new_with_serialization(
10001001
build_result.profile,
10011002
project_path_buf.clone(),
10021003
build_result.serialized
10031004
);
10041005

1006+
#[cfg(not(unix))]
1007+
let executor = crate::sandbox::executor::SandboxExecutor::new_with_serialization(
1008+
(),
1009+
project_path_buf.clone(),
1010+
build_result.serialized
1011+
);
1012+
10051013
// Prepare the sandboxed command
10061014
let args = vec![
10071015
"-p", &task,

src-tauri/src/commands/claude.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1021,7 +1021,14 @@ fn create_sandboxed_claude_command(app: &AppHandle, project_path: &str) -> Resul
10211021

10221022
// Use the helper function to create sandboxed command
10231023
let claude_path = find_claude_binary(app)?;
1024-
Ok(create_sandboxed_command(&claude_path, &[], &project_path_buf, profile, project_path_buf.clone()))
1024+
#[cfg(unix)]
1025+
return Ok(create_sandboxed_command(&claude_path, &[], &project_path_buf, profile, project_path_buf.clone()));
1026+
1027+
#[cfg(not(unix))]
1028+
{
1029+
log::warn!("Sandboxing not supported on Windows, using regular command");
1030+
Ok(create_command_with_env(&claude_path))
1031+
}
10251032
}
10261033
Err(e) => {
10271034
log::error!("Failed to build sandbox profile: {}, falling back to non-sandboxed", e);

src-tauri/src/sandbox/executor.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use anyhow::{Context, Result};
2+
#[cfg(unix)]
23
use gaol::sandbox::{ChildSandbox, ChildSandboxMethods, Command as GaolCommand, Sandbox, SandboxMethods};
34
use log::{info, warn, error, debug};
45
use std::env;
@@ -8,11 +9,13 @@ use tokio::process::Command;
89

910
/// Sandbox executor for running commands in a sandboxed environment
1011
pub struct SandboxExecutor {
12+
#[cfg(unix)]
1113
profile: gaol::profile::Profile,
1214
project_path: PathBuf,
1315
serialized_profile: Option<SerializedProfile>,
1416
}
1517

18+
#[cfg(unix)]
1619
impl SandboxExecutor {
1720
/// Create a new sandbox executor with the given profile
1821
pub fn new(profile: gaol::profile::Profile, project_path: PathBuf) -> Self {
@@ -267,12 +270,76 @@ impl SandboxExecutor {
267270
}
268271
}
269272

273+
// Windows implementation - no sandboxing
274+
#[cfg(not(unix))]
275+
impl SandboxExecutor {
276+
/// Create a new sandbox executor (no-op on Windows)
277+
pub fn new(_profile: (), project_path: PathBuf) -> Self {
278+
Self {
279+
project_path,
280+
serialized_profile: None,
281+
}
282+
}
283+
284+
/// Create a new sandbox executor with serialized profile (no-op on Windows)
285+
pub fn new_with_serialization(
286+
_profile: (),
287+
project_path: PathBuf,
288+
serialized_profile: SerializedProfile
289+
) -> Self {
290+
Self {
291+
project_path,
292+
serialized_profile: Some(serialized_profile),
293+
}
294+
}
295+
296+
/// Execute a command in the sandbox (Windows - no sandboxing)
297+
pub fn execute_sandboxed_spawn(&self, command: &str, args: &[&str], cwd: &Path) -> Result<std::process::Child> {
298+
info!("Executing command without sandbox on Windows: {} {:?}", command, args);
299+
300+
std::process::Command::new(command)
301+
.args(args)
302+
.current_dir(cwd)
303+
.stdin(Stdio::piped())
304+
.stdout(Stdio::piped())
305+
.stderr(Stdio::piped())
306+
.spawn()
307+
.context("Failed to spawn process")
308+
}
309+
310+
/// Prepare a sandboxed tokio Command (Windows - no sandboxing)
311+
pub fn prepare_sandboxed_command(&self, command: &str, args: &[&str], cwd: &Path) -> Command {
312+
info!("Preparing command without sandbox on Windows: {} {:?}", command, args);
313+
314+
let mut cmd = Command::new(command);
315+
cmd.args(args)
316+
.current_dir(cwd)
317+
.stdin(Stdio::null())
318+
.stdout(Stdio::piped())
319+
.stderr(Stdio::piped());
320+
321+
cmd
322+
}
323+
324+
/// Extract sandbox rules (no-op on Windows)
325+
fn extract_sandbox_rules(&self) -> Result<SerializedProfile> {
326+
Ok(SerializedProfile { operations: vec![] })
327+
}
328+
329+
/// Activate sandbox in child process (no-op on Windows)
330+
pub fn activate_sandbox_in_child() -> Result<()> {
331+
debug!("Sandbox activation skipped on Windows");
332+
Ok(())
333+
}
334+
}
335+
270336
/// Check if the current process should activate sandbox
271337
pub fn should_activate_sandbox() -> bool {
272338
env::var("GAOL_SANDBOX_ACTIVE").unwrap_or_default() == "1"
273339
}
274340

275341
/// Helper to create a sandboxed tokio Command
342+
#[cfg(unix)]
276343
pub fn create_sandboxed_command(
277344
command: &str,
278345
args: &[&str],
@@ -300,6 +367,7 @@ pub enum SerializedOperation {
300367
SystemInfoRead,
301368
}
302369

370+
#[cfg(unix)]
303371
fn deserialize_profile(serialized: SerializedProfile, project_path: &Path) -> Result<gaol::profile::Profile> {
304372
let mut operations = Vec::new();
305373

@@ -366,6 +434,7 @@ fn deserialize_profile(serialized: SerializedProfile, project_path: &Path) -> Re
366434
})
367435
}
368436

437+
#[cfg(unix)]
369438
fn create_minimal_profile(project_path: PathBuf) -> Result<gaol::profile::Profile> {
370439
let operations = vec![
371440
gaol::profile::Operation::FileReadAll(

0 commit comments

Comments
 (0)