Compare commits

...

2 Commits

Author SHA1 Message Date
celia-oai
c9716e5999 changes 2026-03-18 16:11:31 -07:00
celia-oai
0c80aedde9 changes 2026-03-18 15:11:25 -07:00
2 changed files with 140 additions and 1 deletions

View File

@@ -1299,6 +1299,67 @@ fn add_dir_override_extends_workspace_writable_roots() -> std::io::Result<()> {
Ok(())
}
#[test]
fn restricted_read_implicitly_allows_helper_executables() -> std::io::Result<()> {
let temp_dir = TempDir::new()?;
let cwd = temp_dir.path().join("workspace");
let codex_home = temp_dir.path().join(".codex");
let zsh_path = temp_dir.path().join("runtime").join("zsh");
let arg0_root = codex_home.join("tmp").join("arg0");
let execve_wrapper = arg0_root
.join("codex-arg0-session")
.join("codex-execve-wrapper");
std::fs::create_dir_all(&cwd)?;
std::fs::create_dir_all(zsh_path.parent().expect("zsh path should have parent"))?;
std::fs::create_dir_all(
execve_wrapper
.parent()
.expect("wrapper path should have parent"),
)?;
std::fs::write(&zsh_path, "")?;
std::fs::write(&execve_wrapper, "")?;
let config = Config::load_from_base_config_with_overrides(
ConfigToml {
default_permissions: Some("workspace".to_string()),
permissions: Some(PermissionsToml {
entries: BTreeMap::from([(
"workspace".to_string(),
PermissionProfileToml {
filesystem: Some(FilesystemPermissionsToml {
entries: BTreeMap::new(),
}),
network: None,
},
)]),
}),
..Default::default()
},
ConfigOverrides {
cwd: Some(cwd.clone()),
zsh_path: Some(zsh_path.clone()),
main_execve_wrapper_exe: Some(execve_wrapper),
..Default::default()
},
codex_home,
)?;
let expected_zsh = AbsolutePathBuf::try_from(zsh_path)?;
let expected_arg0_root = AbsolutePathBuf::try_from(arg0_root)?;
let policy = &config.permissions.file_system_sandbox_policy;
assert!(
policy.can_read_path_with_cwd(expected_zsh.as_path(), &cwd),
"expected zsh helper path to be readable, policy: {policy:?}"
);
assert!(
policy.can_read_path_with_cwd(expected_arg0_root.as_path(), &cwd),
"expected arg0 helper root to be readable, policy: {policy:?}"
);
Ok(())
}
#[test]
fn sqlite_home_defaults_to_codex_home_for_workspace_write() -> std::io::Result<()> {
let codex_home = TempDir::new()?;

View File

@@ -1897,6 +1897,75 @@ fn add_additional_file_system_writes(
}
}
fn dedup_absolute_paths(paths: Vec<AbsolutePathBuf>) -> Vec<AbsolutePathBuf> {
let mut deduped = Vec::new();
for path in paths {
if !deduped.contains(&path) {
deduped.push(path);
}
}
deduped
}
/// Returns implicit readable roots for runtime-managed helper executables that
/// must stay usable under restricted filesystem policies without requiring
/// explicit user permission-profile entries.
///
/// When the execve wrapper lives under `$CODEX_HOME/tmp/arg0`, we allowlist the
/// shared arg0 temp root instead of a single session-specific wrapper path so
/// future helper paths created in that tree remain readable too.
fn helper_readable_roots(
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())
{
return Some(arg0_root.clone());
}
Some(path)
});
dedup_absolute_paths(
zsh_path
.into_iter()
.chain(execve_wrapper_root)
.collect::<Vec<_>>(),
)
}
fn add_additional_file_system_reads(
file_system_sandbox_policy: &mut FileSystemSandboxPolicy,
additional_readable_roots: &[AbsolutePathBuf],
) {
if file_system_sandbox_policy.has_full_disk_read_access() {
return;
}
for path in additional_readable_roots {
let exists = file_system_sandbox_policy.entries.iter().any(|entry| {
matches!(
&entry.path,
codex_protocol::permissions::FileSystemPath::Path { path: existing }
if existing == path && entry.access == codex_protocol::permissions::FileSystemAccessMode::Read
)
});
if !exists {
file_system_sandbox_policy.entries.push(
codex_protocol::permissions::FileSystemSandboxEntry {
path: codex_protocol::permissions::FileSystemPath::Path { path: path.clone() },
access: codex_protocol::permissions::FileSystemAccessMode::Read,
},
);
}
}
}
/// Optional overrides for user configuration (e.g., from CLI flags).
#[derive(Default, Debug, Clone)]
pub struct ConfigOverrides {
@@ -2602,8 +2671,13 @@ impl Config {
} else {
network.enabled().then_some(network)
};
let helper_readable_roots = helper_readable_roots(
&codex_home,
zsh_path.as_ref(),
main_execve_wrapper_exe.as_ref(),
);
let effective_sandbox_policy = constrained_sandbox_policy.value.get().clone();
let effective_file_system_sandbox_policy =
let mut effective_file_system_sandbox_policy =
if effective_sandbox_policy == original_sandbox_policy {
file_system_sandbox_policy
} else {
@@ -2612,6 +2686,10 @@ impl Config {
&resolved_cwd,
)
};
add_additional_file_system_reads(
&mut effective_file_system_sandbox_policy,
&helper_readable_roots,
);
let effective_network_sandbox_policy =
if effective_sandbox_policy == original_sandbox_policy {
network_sandbox_policy