Files
codex/prs/bolinfest/PR-1785.md
2025-09-02 15:17:45 -07:00

6.0 KiB

PR #1785: chore: introduce SandboxPolicy::WorkspaceWrite::include_default_writable_roots

Description

Without this change, it is challenging to create integration tests to verify that the folders not included in writable_roots in SandboxPolicy::WorkspaceWrite are read-only because, by default, get_writable_roots_with_cwd() includes TMPDIR, which is where most integration tests do their work.

This introduces a include_default_writable_roots option to disable the default includes returned by get_writable_roots_with_cwd().


Stack created with Sapling. Best reviewed with ReviewStack.

  • #1765
  • -> #1785

Full Diff

diff --git a/codex-rs/common/src/sandbox_summary.rs b/codex-rs/common/src/sandbox_summary.rs
index 3d33d92836..e0e309a9d9 100644
--- a/codex-rs/common/src/sandbox_summary.rs
+++ b/codex-rs/common/src/sandbox_summary.rs
@@ -7,6 +7,7 @@ pub fn summarize_sandbox_policy(sandbox_policy: &SandboxPolicy) -> String {
         SandboxPolicy::WorkspaceWrite {
             writable_roots,
             network_access,
+            include_default_writable_roots,
         } => {
             let mut summary = "workspace-write".to_string();
             if !writable_roots.is_empty() {
@@ -19,6 +20,9 @@ pub fn summarize_sandbox_policy(sandbox_policy: &SandboxPolicy) -> String {
                         .join(", ")
                 ));
             }
+            if !*include_default_writable_roots {
+                summary.push_str(" (exact writable roots)");
+            }
             if *network_access {
                 summary.push_str(" (network access enabled)");
             }
diff --git a/codex-rs/core/src/config.rs b/codex-rs/core/src/config.rs
index a65ec09674..20bdebe789 100644
--- a/codex-rs/core/src/config.rs
+++ b/codex-rs/core/src/config.rs
@@ -350,6 +350,7 @@ impl ConfigToml {
                 Some(s) => SandboxPolicy::WorkspaceWrite {
                     writable_roots: s.writable_roots.clone(),
                     network_access: s.network_access,
+                    include_default_writable_roots: true,
                 },
                 None => SandboxPolicy::new_workspace_write_policy(),
             },
@@ -720,6 +721,7 @@ writable_roots = [
             SandboxPolicy::WorkspaceWrite {
                 writable_roots: vec![PathBuf::from("/tmp")],
                 network_access: false,
+                include_default_writable_roots: true,
             },
             sandbox_workspace_write_cfg.derive_sandbox_policy(sandbox_mode_override)
         );
diff --git a/codex-rs/core/src/protocol.rs b/codex-rs/core/src/protocol.rs
index bc922eb0e2..e333373c7b 100644
--- a/codex-rs/core/src/protocol.rs
+++ b/codex-rs/core/src/protocol.rs
@@ -175,9 +175,19 @@ pub enum SandboxPolicy {
         /// default.
         #[serde(default)]
         network_access: bool,
+
+        /// When set to `true`, will include defaults like the current working
+        /// directory and TMPDIR (on macOS). When `false`, only `writable_roots`
+        /// are used. (Mainly used for testing.)
+        #[serde(default = "default_true")]
+        include_default_writable_roots: bool,
     },
 }
 
+fn default_true() -> bool {
+    true
+}
+
 impl FromStr for SandboxPolicy {
     type Err = serde_json::Error;
 
@@ -199,6 +209,7 @@ impl SandboxPolicy {
         SandboxPolicy::WorkspaceWrite {
             writable_roots: vec![],
             network_access: false,
+            include_default_writable_roots: true,
         }
     }
 
@@ -230,7 +241,15 @@ impl SandboxPolicy {
         match self {
             SandboxPolicy::DangerFullAccess => Vec::new(),
             SandboxPolicy::ReadOnly => Vec::new(),
-            SandboxPolicy::WorkspaceWrite { writable_roots, .. } => {
+            SandboxPolicy::WorkspaceWrite {
+                writable_roots,
+                include_default_writable_roots,
+                ..
+            } => {
+                if !*include_default_writable_roots {
+                    return writable_roots.clone();
+                }
+
                 let mut roots = writable_roots.clone();
                 roots.push(cwd.to_path_buf());
 
diff --git a/codex-rs/linux-sandbox/tests/landlock.rs b/codex-rs/linux-sandbox/tests/landlock.rs
index 7eacda46c1..472a54e604 100644
--- a/codex-rs/linux-sandbox/tests/landlock.rs
+++ b/codex-rs/linux-sandbox/tests/landlock.rs
@@ -49,6 +49,7 @@ async fn run_cmd(cmd: &[&str], writable_roots: &[PathBuf], timeout_ms: u64) {
     let sandbox_policy = SandboxPolicy::WorkspaceWrite {
         writable_roots: writable_roots.to_vec(),
         network_access: false,
+        include_default_writable_roots: true,
     };
     let sandbox_program = env!("CARGO_BIN_EXE_codex-linux-sandbox");
     let codex_linux_sandbox_exe = Some(PathBuf::from(sandbox_program));

Review Comments

codex-rs/core/src/protocol.rs

@@ -199,6 +203,7 @@ impl SandboxPolicy {
         SandboxPolicy::WorkspaceWrite {
             writable_roots: vec![],
             network_access: false,
+            use_exact_writable_roots: false,

I usually try to name things so that it makes sense for the default value to be false, but I agree it reads in a strange way in this case, so I'll flip it.

I think an appropriate name would be include_default_writable_roots because it affects both TMPDIR and cwd.

@@ -199,6 +203,7 @@ impl SandboxPolicy {
         SandboxPolicy::WorkspaceWrite {
             writable_roots: vec![],
             network_access: false,
+            use_exact_writable_roots: false,

Flipped, though I had to add #[serde(default = "default_true")].