diff --git a/src/cortex-engine/src/exec/mod.rs b/src/cortex-engine/src/exec/mod.rs index 33d2ea0a..64dd20e0 100644 --- a/src/cortex-engine/src/exec/mod.rs +++ b/src/cortex-engine/src/exec/mod.rs @@ -3,11 +3,14 @@ mod environment; mod output; mod policy; -#[cfg(any(target_os = "linux", target_os = "macos"))] +// Process-group teardown is Unix-only; the runner itself is required on Windows +// so local_shell / plugin exec still compile (job-object isolation is separate). +#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))] mod runner; pub use environment::{build_safe_environment, is_sensitive_env_name}; pub use output::OutputCapture; +#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))] pub use runner::{ ExecOptions, ExecOutput, OutputChunk, execute_command, execute_command_streaming, }; diff --git a/src/cortex-engine/src/exec/policy.rs b/src/cortex-engine/src/exec/policy.rs index 6edbafb4..4c7e6ea6 100644 --- a/src/cortex-engine/src/exec/policy.rs +++ b/src/cortex-engine/src/exec/policy.rs @@ -3,25 +3,25 @@ use super::ExecOptions; use crate::error::{CortexError, Result}; use crate::sandbox::{SandboxPolicyType, SandboxRunner, SandboxedCommand, WritableRoot}; use cortex_protocol::SandboxPolicy; +use std::path::Path; pub(super) async fn prepare(command: &[String], options: &ExecOptions) -> Result { evaluate(command, options.approval_granted).await?; - let policy = match &options.sandbox_policy { + let policy = mapped_sandbox_policy(&options.sandbox_policy, &options.cwd)?; + SandboxRunner::new().prepare(command, &policy, &options.cwd) +} + +fn mapped_sandbox_policy(policy: &SandboxPolicy, cwd: &Path) -> Result { + Ok(match policy { SandboxPolicy::DangerFullAccess => SandboxPolicyType::DangerFullAccess, SandboxPolicy::ReadOnly => SandboxPolicyType::Custom { writable_roots: Vec::new(), network_access: false, allow_read_outside_workspace: true, }, - SandboxPolicy::WorkspaceWrite { - writable_roots, - network_access, - .. - } => { + SandboxPolicy::WorkspaceWrite { writable_roots, .. } => { // Never infer permission from HOME caches, TMPDIR, or /tmp. - let mut roots = vec![WritableRoot::with_standard_protections( - options.cwd.canonicalize()?, - )]; + let mut roots = vec![WritableRoot::with_standard_protections(cwd.canonicalize()?)]; for path in writable_roots { roots.push(WritableRoot::with_standard_protections( path.canonicalize()?, @@ -29,12 +29,13 @@ pub(super) async fn prepare(command: &[String], options: &ExecOptions) -> Result } SandboxPolicyType::Custom { writable_roots: roots, - network_access: *network_access, + // `network_access` is a `bool`. Do not dereference it: rustc match + // ergonomics bind the Copy field as `bool` on Windows nightly (E0614). + network_access: policy.has_full_network_access(), allow_read_outside_workspace: true, } } - }; - SandboxRunner::new().prepare(command, &policy, &options.cwd) + }) } async fn evaluate(command: &[String], approved: bool) -> Result<()> { @@ -52,3 +53,54 @@ async fn evaluate(command: &[String], approved: bool) -> Result<()> { )), } } + +#[cfg(test)] +mod tests { + use super::*; + + fn workspace_write(network_access: bool) -> SandboxPolicy { + SandboxPolicy::WorkspaceWrite { + writable_roots: vec![], + network_access, + exclude_tmpdir_env_var: false, + exclude_slash_tmp: false, + } + } + + #[test] + fn workspace_write_network_access_is_copied_as_bool() { + let dir = tempfile::tempdir().unwrap(); + let cwd = dir.path(); + + let enabled = mapped_sandbox_policy(&workspace_write(true), cwd).unwrap(); + assert!(matches!( + enabled, + SandboxPolicyType::Custom { + network_access: true, + .. + } + )); + + let disabled = mapped_sandbox_policy(&workspace_write(false), cwd).unwrap(); + assert!(matches!( + disabled, + SandboxPolicyType::Custom { + network_access: false, + .. + } + )); + } + + #[test] + fn read_only_disables_network_access() { + let dir = tempfile::tempdir().unwrap(); + let policy = mapped_sandbox_policy(&SandboxPolicy::ReadOnly, dir.path()).unwrap(); + assert!(matches!( + policy, + SandboxPolicyType::Custom { + network_access: false, + .. + } + )); + } +} diff --git a/src/cortex-engine/src/exec/runner.rs b/src/cortex-engine/src/exec/runner.rs index 1b87d17a..85082be5 100644 --- a/src/cortex-engine/src/exec/runner.rs +++ b/src/cortex-engine/src/exec/runner.rs @@ -211,7 +211,7 @@ pub async fn execute_command_streaming( Ok(output) } -#[cfg(test)] +#[cfg(all(test, unix))] mod tests { use super::*; #[tokio::test]