Canonicalize sandbox paths to prevent symlink bypass

Richard Feldman created

Resolve symlinks via std::fs::canonicalize on all sandbox paths
before applying the OS-level sandbox. Without this, a symlink
pointing outside the intended sandbox boundary would be followed
and the target would be accessible.

project_dir canonicalization failure logs a warning. System and
additional paths silently fall back to the original path if they
don't exist (handled gracefully downstream).

Change summary

crates/terminal/src/sandbox_exec.rs      |  3 +
crates/terminal/src/terminal_settings.rs | 29 ++++++++++++++++++++++++++
2 files changed, 31 insertions(+), 1 deletion(-)

Detailed changes

crates/terminal/src/sandbox_exec.rs 🔗

@@ -139,7 +139,8 @@ pub fn sandbox_exec_main(config_json: &str, shell_args: &[String]) -> ! {
         std::process::exit(1);
     }
 
-    let sandbox_config = config.to_sandbox_config();
+    let mut sandbox_config = config.to_sandbox_config();
+    sandbox_config.canonicalize_paths();
 
     // Step 1: Filter environment variables.
     // Keep only allowed vars + a few Zed-specific ones.

crates/terminal/src/terminal_settings.rs 🔗

@@ -403,4 +403,33 @@ impl SandboxConfig {
                 .unwrap_or_else(Self::default_allowed_env_vars),
         }
     }
+
+    pub fn canonicalize_paths(&mut self) {
+        match std::fs::canonicalize(&self.project_dir) {
+            Ok(canonical) => self.project_dir = canonical,
+            Err(err) => log::warn!(
+                "Failed to canonicalize project dir {:?}: {}",
+                self.project_dir,
+                err
+            ),
+        }
+        canonicalize_path_list(&mut self.system_paths.executable);
+        canonicalize_path_list(&mut self.system_paths.read_only);
+        canonicalize_path_list(&mut self.system_paths.read_write);
+        canonicalize_path_list(&mut self.additional_executable_paths);
+        canonicalize_path_list(&mut self.additional_read_only_paths);
+        canonicalize_path_list(&mut self.additional_read_write_paths);
+    }
+}
+
+fn try_canonicalize(path: &mut PathBuf) {
+    if let Ok(canonical) = std::fs::canonicalize(&*path) {
+        *path = canonical;
+    }
+}
+
+fn canonicalize_path_list(paths: &mut Vec<PathBuf>) {
+    for path in paths.iter_mut() {
+        try_canonicalize(path);
+    }
 }