fix: allow restricted filesystem profiles to read helper executables (#15114)

## Summary

This PR fixes restricted filesystem permission profiles so Codex's
runtime-managed helper executables remain readable without requiring
explicit user configuration.

- add implicit readable roots for the configured `zsh` helper path and
the main execve wrapper
- allowlist the shared `$CODEX_HOME/tmp/arg0` root when the execve
wrapper lives there, so session-specific helper paths keep working
- dedupe injected paths and avoid adding duplicate read entries to the
sandbox policy
- add regression coverage for restricted read mode with helper
executable overrides

## Testing 
before this change: got this error when executing a shell command via
zsh fork:
```
"sandbox error: sandbox denied exec error, exit code: 127, stdout: , stderr: /etc/zprofile:11: operation not permitted: /usr/libexec/path_helper\nzsh:1: operation not permitted: .codex/skills/proxy-a/scripts/fetch_example.sh\n"
```

saw this change went away after this change, meaning the readable roots
and injected correctly.
This commit is contained in:
Celia Chen authored and GitHub committed 2026-03-20 15:51:06 -07:00
1 parent 10a936d127
commit 9eef2e91fc
4 files changed
+220 -27

No files matched your search

+31
View File
@@ -190,6 +190,37 @@ pub(crate) fn compile_permission_profile(
))
}
/// Returns a list of paths that must be readable by shell tools in order
/// for Codex to function. These should always be added to the
/// `FileSystemSandboxPolicy` for a thread.
pub(crate) fn get_readable_roots_required_for_codex_runtime(
codex_home: &Path,
zsh_path: Option<&PathBuf>,
main_execve_wrapper_exe: Option<&PathBuf>,
) -> Vec<AbsolutePathBuf> {
let arg0_root = AbsolutePathBuf::from_absolute_path(codex_home.join("tmp").join("arg0")).ok();
let zsh_path = zsh_path.and_then(|path| AbsolutePathBuf::from_absolute_path(path).ok());
let execve_wrapper_root = main_execve_wrapper_exe.and_then(|path| {
let path = AbsolutePathBuf::from_absolute_path(path).ok()?;
if let Some(arg0_root) = arg0_root.as_ref()
&& path.as_path().starts_with(arg0_root.as_path())
{
path.parent()
} else {
Some(path)
}
});
let mut readable_roots = Vec::new();
if let Some(zsh_path) = zsh_path {
readable_roots.push(zsh_path);
}
if let Some(execve_wrapper_root) = execve_wrapper_root {
readable_roots.push(execve_wrapper_root);
}
readable_roots
}
fn compile_network_sandbox_policy(network: Option<&NetworkToml>) -> NetworkSandboxPolicy {
let Some(network) = network else {
return NetworkSandboxPolicy::Restricted;