mirror of
https://github.com/openai/codex.git
synced 2026-05-01 09:56:37 +00:00
Allow the sandboxed filesystem helper to read both the real Codex binary parent and the codex-linux-sandbox arg0 alias parent. The Docker remote-env sandbox tests also pass an in-container cwd so the remote exec-server does not try to spawn helpers from a host checkout path. Co-authored-by: Codex <noreply@openai.com>
683 lines
24 KiB
Rust
683 lines
24 KiB
Rust
use std::collections::HashMap;
|
|
|
|
use codex_app_server_protocol::JSONRPCErrorError;
|
|
use codex_protocol::permissions::FileSystemAccessMode;
|
|
use codex_protocol::permissions::FileSystemPath;
|
|
use codex_protocol::permissions::FileSystemSandboxEntry;
|
|
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
|
use codex_protocol::permissions::FileSystemSpecialPath;
|
|
use codex_protocol::permissions::NetworkSandboxPolicy;
|
|
use codex_protocol::protocol::ReadOnlyAccess;
|
|
use codex_protocol::protocol::SandboxPolicy;
|
|
use codex_sandboxing::SandboxCommand;
|
|
use codex_sandboxing::SandboxExecRequest;
|
|
use codex_sandboxing::SandboxManager;
|
|
use codex_sandboxing::SandboxTransformRequest;
|
|
use codex_sandboxing::SandboxablePreference;
|
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
|
use codex_utils_absolute_path::canonicalize_preserving_symlinks;
|
|
use tokio::io::AsyncWriteExt;
|
|
use tokio::process::Command;
|
|
|
|
use crate::ExecServerRuntimePaths;
|
|
use crate::FileSystemSandboxContext;
|
|
use crate::fs_helper::CODEX_FS_HELPER_ARG1;
|
|
use crate::fs_helper::FsHelperPayload;
|
|
use crate::fs_helper::FsHelperRequest;
|
|
use crate::fs_helper::FsHelperResponse;
|
|
use crate::local_file_system::current_sandbox_cwd;
|
|
use crate::rpc::internal_error;
|
|
use crate::rpc::invalid_request;
|
|
|
|
const FS_HELPER_ENV_ALLOWLIST: &[&str] = &["PATH", "TMPDIR", "TMP", "TEMP"];
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub(crate) struct FileSystemSandboxRunner {
|
|
runtime_paths: ExecServerRuntimePaths,
|
|
helper_env: HashMap<String, String>,
|
|
}
|
|
|
|
impl FileSystemSandboxRunner {
|
|
pub(crate) fn new(runtime_paths: ExecServerRuntimePaths) -> Self {
|
|
Self {
|
|
runtime_paths,
|
|
helper_env: helper_env(),
|
|
}
|
|
}
|
|
|
|
pub(crate) async fn run(
|
|
&self,
|
|
sandbox: &FileSystemSandboxContext,
|
|
request: FsHelperRequest,
|
|
) -> Result<FsHelperPayload, JSONRPCErrorError> {
|
|
let cwd = sandbox_cwd(sandbox)?;
|
|
let mut file_system_policy = sandbox.permissions.file_system_sandbox_policy();
|
|
let helper_read_roots = if sandbox.use_legacy_landlock {
|
|
Vec::new()
|
|
} else {
|
|
helper_read_roots(&self.runtime_paths)
|
|
};
|
|
add_helper_runtime_permissions(&mut file_system_policy, &helper_read_roots, cwd.as_path());
|
|
normalize_file_system_policy_root_aliases(&mut file_system_policy);
|
|
let network_policy = NetworkSandboxPolicy::Restricted;
|
|
let sandbox_policy =
|
|
compatibility_sandbox_policy(&file_system_policy, network_policy, cwd.as_path());
|
|
let command = self.sandbox_exec_request(
|
|
&sandbox_policy,
|
|
&file_system_policy,
|
|
network_policy,
|
|
&cwd,
|
|
sandbox,
|
|
)?;
|
|
let request_json = serde_json::to_vec(&request).map_err(json_error)?;
|
|
run_command(command, request_json).await
|
|
}
|
|
|
|
fn sandbox_exec_request(
|
|
&self,
|
|
sandbox_policy: &SandboxPolicy,
|
|
file_system_policy: &FileSystemSandboxPolicy,
|
|
network_policy: NetworkSandboxPolicy,
|
|
cwd: &AbsolutePathBuf,
|
|
sandbox_context: &FileSystemSandboxContext,
|
|
) -> Result<SandboxExecRequest, JSONRPCErrorError> {
|
|
let helper = &self.runtime_paths.codex_self_exe;
|
|
let sandbox_manager = SandboxManager::new();
|
|
let sandbox = sandbox_manager.select_initial(
|
|
file_system_policy,
|
|
network_policy,
|
|
SandboxablePreference::Auto,
|
|
sandbox_context.windows_sandbox_level,
|
|
/*has_managed_network_requirements*/ false,
|
|
);
|
|
let command = SandboxCommand {
|
|
program: helper.as_path().as_os_str().to_owned(),
|
|
args: vec![CODEX_FS_HELPER_ARG1.to_string()],
|
|
cwd: cwd.clone(),
|
|
env: self.helper_env.clone(),
|
|
additional_permissions: None,
|
|
};
|
|
sandbox_manager
|
|
.transform(SandboxTransformRequest {
|
|
command,
|
|
policy: sandbox_policy,
|
|
file_system_policy,
|
|
network_policy,
|
|
sandbox,
|
|
enforce_managed_network: false,
|
|
network: None,
|
|
sandbox_policy_cwd: cwd.as_path(),
|
|
codex_linux_sandbox_exe: self.runtime_paths.codex_linux_sandbox_exe.as_deref(),
|
|
use_legacy_landlock: sandbox_context.use_legacy_landlock,
|
|
windows_sandbox_level: sandbox_context.windows_sandbox_level,
|
|
windows_sandbox_private_desktop: sandbox_context.windows_sandbox_private_desktop,
|
|
})
|
|
.map_err(|err| invalid_request(format!("failed to prepare fs sandbox: {err}")))
|
|
}
|
|
}
|
|
|
|
fn sandbox_cwd(sandbox: &FileSystemSandboxContext) -> Result<AbsolutePathBuf, JSONRPCErrorError> {
|
|
if let Some(cwd) = &sandbox.cwd {
|
|
return Ok(cwd.clone());
|
|
}
|
|
|
|
let file_system_policy = sandbox.permissions.file_system_sandbox_policy();
|
|
if file_system_policy_has_cwd_dependent_entries(&file_system_policy) {
|
|
return Err(invalid_request(
|
|
"file system sandbox context with cwd-relative permissions requires cwd".to_string(),
|
|
));
|
|
}
|
|
|
|
let cwd = current_sandbox_cwd().map_err(io_error)?;
|
|
AbsolutePathBuf::from_absolute_path(cwd.as_path())
|
|
.map_err(|err| invalid_request(format!("current directory is not absolute: {err}")))
|
|
}
|
|
|
|
fn file_system_policy_has_cwd_dependent_entries(
|
|
file_system_policy: &FileSystemSandboxPolicy,
|
|
) -> bool {
|
|
file_system_policy
|
|
.entries
|
|
.iter()
|
|
.any(|entry| match &entry.path {
|
|
FileSystemPath::GlobPattern { pattern } => !std::path::Path::new(pattern).is_absolute(),
|
|
FileSystemPath::Special {
|
|
value:
|
|
FileSystemSpecialPath::CurrentWorkingDirectory
|
|
| FileSystemSpecialPath::ProjectRoots { .. },
|
|
} => true,
|
|
FileSystemPath::Path { .. } | FileSystemPath::Special { .. } => false,
|
|
})
|
|
}
|
|
|
|
fn helper_read_roots(runtime_paths: &ExecServerRuntimePaths) -> Vec<AbsolutePathBuf> {
|
|
let mut roots = Vec::new();
|
|
let helper_paths = std::iter::once(runtime_paths.codex_self_exe.as_path()).chain(
|
|
runtime_paths
|
|
.codex_linux_sandbox_exe
|
|
.as_ref()
|
|
.map(|path| path.as_path()),
|
|
);
|
|
|
|
for helper_path in helper_paths {
|
|
let Some(parent) = helper_path.parent() else {
|
|
continue;
|
|
};
|
|
let Ok(root) = AbsolutePathBuf::from_absolute_path(parent) else {
|
|
continue;
|
|
};
|
|
if !roots.contains(&root) {
|
|
roots.push(root);
|
|
}
|
|
}
|
|
|
|
roots
|
|
}
|
|
|
|
fn add_helper_runtime_permissions(
|
|
file_system_policy: &mut FileSystemSandboxPolicy,
|
|
helper_read_roots: &[AbsolutePathBuf],
|
|
cwd: &std::path::Path,
|
|
) {
|
|
if !file_system_policy.has_full_disk_read_access() {
|
|
let minimal_read_entry = FileSystemSandboxEntry {
|
|
path: FileSystemPath::Special {
|
|
value: FileSystemSpecialPath::Minimal,
|
|
},
|
|
access: FileSystemAccessMode::Read,
|
|
};
|
|
if !file_system_policy.entries.contains(&minimal_read_entry) {
|
|
file_system_policy.entries.push(minimal_read_entry);
|
|
}
|
|
}
|
|
|
|
for helper_read_root in helper_read_roots {
|
|
if file_system_policy.can_read_path_with_cwd(helper_read_root.as_path(), cwd) {
|
|
continue;
|
|
}
|
|
|
|
file_system_policy.entries.push(FileSystemSandboxEntry {
|
|
path: FileSystemPath::Path {
|
|
path: helper_read_root.clone(),
|
|
},
|
|
access: FileSystemAccessMode::Read,
|
|
});
|
|
}
|
|
}
|
|
|
|
fn compatibility_sandbox_policy(
|
|
file_system_policy: &FileSystemSandboxPolicy,
|
|
network_policy: NetworkSandboxPolicy,
|
|
cwd: &std::path::Path,
|
|
) -> SandboxPolicy {
|
|
file_system_policy
|
|
.to_legacy_sandbox_policy(network_policy, cwd)
|
|
.unwrap_or_else(|_| compatibility_workspace_write_policy(file_system_policy, cwd))
|
|
}
|
|
|
|
fn compatibility_workspace_write_policy(
|
|
file_system_policy: &FileSystemSandboxPolicy,
|
|
cwd: &std::path::Path,
|
|
) -> SandboxPolicy {
|
|
let read_only_access = if file_system_policy.has_full_disk_read_access() {
|
|
ReadOnlyAccess::FullAccess
|
|
} else {
|
|
ReadOnlyAccess::Restricted {
|
|
include_platform_defaults: file_system_policy.include_platform_defaults(),
|
|
readable_roots: file_system_policy.get_readable_roots_with_cwd(cwd),
|
|
}
|
|
};
|
|
let cwd_abs = AbsolutePathBuf::from_absolute_path(cwd).ok();
|
|
let writable_roots = file_system_policy
|
|
.get_writable_roots_with_cwd(cwd)
|
|
.into_iter()
|
|
.map(|root| root.root)
|
|
.filter(|root| cwd_abs.as_ref() != Some(root))
|
|
.collect();
|
|
|
|
SandboxPolicy::WorkspaceWrite {
|
|
writable_roots,
|
|
read_only_access,
|
|
network_access: false,
|
|
exclude_tmpdir_env_var: true,
|
|
exclude_slash_tmp: true,
|
|
}
|
|
}
|
|
|
|
fn normalize_file_system_policy_root_aliases(file_system_policy: &mut FileSystemSandboxPolicy) {
|
|
for entry in &mut file_system_policy.entries {
|
|
if let FileSystemPath::Path { path } = &mut entry.path {
|
|
*path = normalize_top_level_alias(path.clone());
|
|
}
|
|
}
|
|
}
|
|
|
|
fn normalize_top_level_alias(path: AbsolutePathBuf) -> AbsolutePathBuf {
|
|
let raw_path = path.to_path_buf();
|
|
for ancestor in raw_path.ancestors() {
|
|
if std::fs::symlink_metadata(ancestor).is_err() {
|
|
continue;
|
|
}
|
|
let Ok(normalized_ancestor) = canonicalize_preserving_symlinks(ancestor) else {
|
|
continue;
|
|
};
|
|
if normalized_ancestor == ancestor {
|
|
continue;
|
|
}
|
|
let Ok(suffix) = raw_path.strip_prefix(ancestor) else {
|
|
continue;
|
|
};
|
|
if let Ok(normalized_path) =
|
|
AbsolutePathBuf::from_absolute_path(normalized_ancestor.join(suffix))
|
|
{
|
|
return normalized_path;
|
|
}
|
|
}
|
|
path
|
|
}
|
|
|
|
fn helper_env() -> HashMap<String, String> {
|
|
helper_env_from_vars(std::env::vars_os())
|
|
}
|
|
|
|
fn helper_env_from_vars(
|
|
vars: impl IntoIterator<Item = (std::ffi::OsString, std::ffi::OsString)>,
|
|
) -> HashMap<String, String> {
|
|
vars.into_iter()
|
|
.filter_map(|(key, value)| {
|
|
let key = key.to_string_lossy();
|
|
helper_env_key_is_allowed(&key)
|
|
.then(|| (key.into_owned(), value.to_string_lossy().into_owned()))
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn helper_env_key_is_allowed(key: &str) -> bool {
|
|
FS_HELPER_ENV_ALLOWLIST.contains(&key) || (cfg!(windows) && key.eq_ignore_ascii_case("PATH"))
|
|
}
|
|
|
|
async fn run_command(
|
|
command: SandboxExecRequest,
|
|
request_json: Vec<u8>,
|
|
) -> Result<FsHelperPayload, JSONRPCErrorError> {
|
|
let mut child = spawn_command(command)?;
|
|
let mut stdin = child
|
|
.stdin
|
|
.take()
|
|
.ok_or_else(|| internal_error("failed to open fs sandbox helper stdin".to_string()))?;
|
|
stdin.write_all(&request_json).await.map_err(io_error)?;
|
|
stdin.shutdown().await.map_err(io_error)?;
|
|
drop(stdin);
|
|
|
|
let output = child.wait_with_output().await.map_err(io_error)?;
|
|
if !output.status.success() {
|
|
return Err(internal_error(format!(
|
|
"fs sandbox helper failed with status {status}: {stderr}",
|
|
status = output.status,
|
|
stderr = String::from_utf8_lossy(&output.stderr).trim()
|
|
)));
|
|
}
|
|
let response: FsHelperResponse = serde_json::from_slice(&output.stdout).map_err(json_error)?;
|
|
match response {
|
|
FsHelperResponse::Ok(payload) => Ok(payload),
|
|
FsHelperResponse::Error(error) => Err(error),
|
|
}
|
|
}
|
|
|
|
fn spawn_command(
|
|
SandboxExecRequest {
|
|
command: argv,
|
|
cwd,
|
|
env,
|
|
arg0,
|
|
..
|
|
}: SandboxExecRequest,
|
|
) -> Result<tokio::process::Child, JSONRPCErrorError> {
|
|
let Some((program, args)) = argv.split_first() else {
|
|
return Err(invalid_request("fs sandbox command was empty".to_string()));
|
|
};
|
|
let mut command = Command::new(program);
|
|
#[cfg(unix)]
|
|
if let Some(arg0) = arg0 {
|
|
command.arg0(arg0);
|
|
}
|
|
#[cfg(not(unix))]
|
|
let _ = arg0;
|
|
command.args(args);
|
|
command.current_dir(cwd.as_path());
|
|
command.env_clear();
|
|
command.envs(env);
|
|
command.stdin(std::process::Stdio::piped());
|
|
command.stdout(std::process::Stdio::piped());
|
|
command.stderr(std::process::Stdio::piped());
|
|
command.spawn().map_err(io_error)
|
|
}
|
|
|
|
fn io_error(err: std::io::Error) -> JSONRPCErrorError {
|
|
internal_error(err.to_string())
|
|
}
|
|
|
|
fn json_error(err: serde_json::Error) -> JSONRPCErrorError {
|
|
internal_error(format!(
|
|
"failed to encode or decode fs sandbox helper message: {err}"
|
|
))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::collections::HashMap;
|
|
use std::ffi::OsString;
|
|
|
|
use codex_protocol::models::PermissionProfile;
|
|
use codex_protocol::permissions::FileSystemAccessMode;
|
|
use codex_protocol::permissions::FileSystemPath;
|
|
use codex_protocol::permissions::FileSystemSandboxEntry;
|
|
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
|
use codex_protocol::permissions::FileSystemSpecialPath;
|
|
use codex_protocol::permissions::NetworkSandboxPolicy;
|
|
use codex_protocol::protocol::ReadOnlyAccess;
|
|
use codex_protocol::protocol::SandboxPolicy;
|
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
|
use pretty_assertions::assert_eq;
|
|
|
|
use crate::ExecServerRuntimePaths;
|
|
|
|
use super::FileSystemSandboxRunner;
|
|
use super::add_helper_runtime_permissions;
|
|
use super::helper_env;
|
|
use super::helper_env_from_vars;
|
|
use super::helper_env_key_is_allowed;
|
|
use super::helper_read_roots;
|
|
use super::sandbox_cwd;
|
|
|
|
#[test]
|
|
fn helper_permissions_enable_minimal_reads_for_read_only_access() {
|
|
let cwd = AbsolutePathBuf::from_absolute_path(std::env::temp_dir().as_path())
|
|
.expect("absolute cwd");
|
|
let sandbox_policy = SandboxPolicy::ReadOnly {
|
|
access: ReadOnlyAccess::Restricted {
|
|
include_platform_defaults: false,
|
|
readable_roots: Vec::new(),
|
|
},
|
|
network_access: false,
|
|
};
|
|
let mut policy =
|
|
FileSystemSandboxPolicy::from_legacy_sandbox_policy(&sandbox_policy, cwd.as_path());
|
|
|
|
add_helper_runtime_permissions(&mut policy, &[], cwd.as_path());
|
|
|
|
assert!(policy.include_platform_defaults());
|
|
}
|
|
|
|
#[test]
|
|
fn helper_permissions_enable_minimal_reads_for_workspace_read_access() {
|
|
let cwd = AbsolutePathBuf::from_absolute_path(std::env::temp_dir().as_path())
|
|
.expect("absolute cwd");
|
|
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
|
|
writable_roots: Vec::new(),
|
|
read_only_access: ReadOnlyAccess::Restricted {
|
|
include_platform_defaults: false,
|
|
readable_roots: Vec::new(),
|
|
},
|
|
network_access: false,
|
|
exclude_tmpdir_env_var: true,
|
|
exclude_slash_tmp: true,
|
|
};
|
|
let mut policy =
|
|
FileSystemSandboxPolicy::from_legacy_sandbox_policy(&sandbox_policy, cwd.as_path());
|
|
|
|
add_helper_runtime_permissions(&mut policy, &[], cwd.as_path());
|
|
|
|
assert!(policy.include_platform_defaults());
|
|
}
|
|
|
|
#[test]
|
|
fn helper_permissions_preserve_existing_writes() {
|
|
let codex_self_exe = std::env::current_exe().expect("current exe");
|
|
let runtime_paths =
|
|
ExecServerRuntimePaths::new(codex_self_exe, /*codex_linux_sandbox_exe*/ None)
|
|
.expect("runtime paths");
|
|
let cwd = AbsolutePathBuf::from_absolute_path(std::env::temp_dir().as_path())
|
|
.expect("absolute cwd");
|
|
let writable = cwd.join("writable");
|
|
let sandbox_policy = SandboxPolicy::ReadOnly {
|
|
access: ReadOnlyAccess::Restricted {
|
|
include_platform_defaults: false,
|
|
readable_roots: Vec::new(),
|
|
},
|
|
network_access: true,
|
|
};
|
|
let mut policy =
|
|
FileSystemSandboxPolicy::from_legacy_sandbox_policy(&sandbox_policy, cwd.as_path());
|
|
policy.entries.push(FileSystemSandboxEntry {
|
|
path: FileSystemPath::Path {
|
|
path: writable.clone(),
|
|
},
|
|
access: FileSystemAccessMode::Write,
|
|
});
|
|
let readable = AbsolutePathBuf::from_absolute_path(
|
|
runtime_paths
|
|
.codex_self_exe
|
|
.parent()
|
|
.expect("current exe parent"),
|
|
)
|
|
.expect("absolute readable path");
|
|
|
|
add_helper_runtime_permissions(
|
|
&mut policy,
|
|
&helper_read_roots(&runtime_paths),
|
|
cwd.as_path(),
|
|
);
|
|
|
|
assert!(policy.can_read_path_with_cwd(readable.as_path(), cwd.as_path()));
|
|
assert!(policy.can_write_path_with_cwd(writable.as_path(), cwd.as_path()));
|
|
}
|
|
|
|
#[test]
|
|
fn helper_env_carries_only_allowlisted_runtime_vars() {
|
|
let env = helper_env();
|
|
|
|
let expected = std::env::vars_os()
|
|
.filter_map(|(key, value)| {
|
|
let key = key.to_string_lossy();
|
|
helper_env_key_is_allowed(&key)
|
|
.then(|| (key.into_owned(), value.to_string_lossy().into_owned()))
|
|
})
|
|
.collect::<HashMap<_, _>>();
|
|
|
|
assert_eq!(env, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn helper_env_preserves_path_for_system_bwrap_discovery_without_leaking_secrets() {
|
|
let env = helper_env_from_vars(
|
|
[
|
|
("PATH", "/usr/bin:/bin"),
|
|
("TMPDIR", "/tmp/codex"),
|
|
("TMP", "/tmp"),
|
|
("TEMP", "/tmp"),
|
|
("HOME", "/home/user"),
|
|
("OPENAI_API_KEY", "secret"),
|
|
("HTTPS_PROXY", "http://proxy.example"),
|
|
]
|
|
.map(|(key, value)| (OsString::from(key), OsString::from(value))),
|
|
);
|
|
|
|
assert_eq!(
|
|
env,
|
|
HashMap::from([
|
|
("PATH".to_string(), "/usr/bin:/bin".to_string()),
|
|
("TMPDIR".to_string(), "/tmp/codex".to_string()),
|
|
("TMP".to_string(), "/tmp".to_string()),
|
|
("TEMP".to_string(), "/tmp".to_string()),
|
|
])
|
|
);
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
#[test]
|
|
fn helper_env_preserves_windows_path_key_for_system_bwrap_discovery() {
|
|
let env = helper_env_from_vars(
|
|
[
|
|
("Path", r"C:\Windows\System32"),
|
|
("PATH_INJECTION", "bad"),
|
|
("OPENAI_API_KEY", "secret"),
|
|
]
|
|
.map(|(key, value)| (OsString::from(key), OsString::from(value))),
|
|
);
|
|
|
|
assert_eq!(
|
|
env,
|
|
HashMap::from([("Path".to_string(), r"C:\Windows\System32".to_string())])
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn sandbox_exec_request_carries_helper_env() {
|
|
let Some((path_key, path)) = std::env::vars_os().find(|(key, _)| {
|
|
let key = key.to_string_lossy();
|
|
key == "PATH" || (cfg!(windows) && key.eq_ignore_ascii_case("PATH"))
|
|
}) else {
|
|
return;
|
|
};
|
|
let path_key = path_key.to_string_lossy().into_owned();
|
|
let path = path.to_string_lossy().into_owned();
|
|
let codex_self_exe = std::env::current_exe().expect("current exe");
|
|
let runtime_paths =
|
|
ExecServerRuntimePaths::new(codex_self_exe.clone(), Some(codex_self_exe))
|
|
.expect("runtime paths");
|
|
let runner = FileSystemSandboxRunner::new(runtime_paths);
|
|
let cwd = AbsolutePathBuf::current_dir().expect("cwd");
|
|
let sandbox_policy = SandboxPolicy::new_workspace_write_policy();
|
|
let file_system_policy =
|
|
FileSystemSandboxPolicy::from_legacy_sandbox_policy(&sandbox_policy, cwd.as_path());
|
|
let sandbox_context = crate::FileSystemSandboxContext::new(sandbox_policy.clone());
|
|
|
|
let request = runner
|
|
.sandbox_exec_request(
|
|
&sandbox_policy,
|
|
&file_system_policy,
|
|
NetworkSandboxPolicy::Restricted,
|
|
&cwd,
|
|
&sandbox_context,
|
|
)
|
|
.expect("sandbox exec request");
|
|
|
|
assert_eq!(request.env.get(&path_key), Some(&path));
|
|
}
|
|
|
|
#[test]
|
|
fn sandbox_cwd_uses_context_cwd() {
|
|
let cwd = AbsolutePathBuf::from_absolute_path(std::env::temp_dir().as_path())
|
|
.expect("absolute cwd");
|
|
let sandbox_context = crate::FileSystemSandboxContext::from_legacy_sandbox_policy(
|
|
SandboxPolicy::new_workspace_write_policy(),
|
|
cwd.clone(),
|
|
);
|
|
|
|
assert_eq!(sandbox_cwd(&sandbox_context).expect("sandbox cwd"), cwd);
|
|
}
|
|
|
|
#[test]
|
|
fn sandbox_cwd_rejects_cwd_dependent_profile_without_context_cwd() {
|
|
let policy = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
|
path: FileSystemPath::Special {
|
|
value: FileSystemSpecialPath::CurrentWorkingDirectory,
|
|
},
|
|
access: FileSystemAccessMode::Write,
|
|
}]);
|
|
let sandbox_context =
|
|
crate::FileSystemSandboxContext::from_permission_profile(PermissionProfile {
|
|
network: None,
|
|
file_system: Some((&policy).into()),
|
|
});
|
|
|
|
let err = sandbox_cwd(&sandbox_context).expect_err("missing cwd should be rejected");
|
|
|
|
assert_eq!(
|
|
err.message,
|
|
"file system sandbox context with cwd-relative permissions requires cwd"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn helper_permissions_include_helper_read_root_without_additional_permissions() {
|
|
let codex_self_exe = std::env::current_exe().expect("current exe");
|
|
let runtime_paths =
|
|
ExecServerRuntimePaths::new(codex_self_exe, /*codex_linux_sandbox_exe*/ None)
|
|
.expect("runtime paths");
|
|
let cwd = AbsolutePathBuf::from_absolute_path(std::env::temp_dir().as_path())
|
|
.expect("absolute cwd");
|
|
let sandbox_policy = SandboxPolicy::ReadOnly {
|
|
access: ReadOnlyAccess::Restricted {
|
|
include_platform_defaults: false,
|
|
readable_roots: Vec::new(),
|
|
},
|
|
network_access: false,
|
|
};
|
|
let mut policy =
|
|
FileSystemSandboxPolicy::from_legacy_sandbox_policy(&sandbox_policy, cwd.as_path());
|
|
let readable = AbsolutePathBuf::from_absolute_path(
|
|
runtime_paths
|
|
.codex_self_exe
|
|
.parent()
|
|
.expect("current exe parent"),
|
|
)
|
|
.expect("absolute readable path");
|
|
|
|
add_helper_runtime_permissions(
|
|
&mut policy,
|
|
&helper_read_roots(&runtime_paths),
|
|
cwd.as_path(),
|
|
);
|
|
|
|
assert!(policy.can_read_path_with_cwd(readable.as_path(), cwd.as_path()));
|
|
}
|
|
|
|
#[test]
|
|
fn helper_permissions_include_linux_sandbox_alias_root() {
|
|
let temp_dir = tempfile::tempdir().expect("temp dir");
|
|
let codex_self_exe = temp_dir.path().join("remote-env").join("codex");
|
|
let codex_linux_sandbox_exe = temp_dir
|
|
.path()
|
|
.join("arg0")
|
|
.join("codex-session")
|
|
.join("codex-linux-sandbox");
|
|
let runtime_paths = ExecServerRuntimePaths::new(
|
|
codex_self_exe.clone(),
|
|
Some(codex_linux_sandbox_exe.clone()),
|
|
)
|
|
.expect("runtime paths");
|
|
let cwd = AbsolutePathBuf::from_absolute_path(std::env::temp_dir().as_path())
|
|
.expect("absolute cwd");
|
|
let sandbox_policy = SandboxPolicy::ReadOnly {
|
|
access: ReadOnlyAccess::Restricted {
|
|
include_platform_defaults: false,
|
|
readable_roots: Vec::new(),
|
|
},
|
|
network_access: false,
|
|
};
|
|
let mut policy =
|
|
FileSystemSandboxPolicy::from_legacy_sandbox_policy(&sandbox_policy, cwd.as_path());
|
|
let codex_self_root = AbsolutePathBuf::from_absolute_path(
|
|
codex_self_exe.parent().expect("codex self parent"),
|
|
)
|
|
.expect("codex self root should be absolute");
|
|
let linux_sandbox_root = AbsolutePathBuf::from_absolute_path(
|
|
codex_linux_sandbox_exe
|
|
.parent()
|
|
.expect("linux sandbox parent"),
|
|
)
|
|
.expect("linux sandbox root should be absolute");
|
|
|
|
add_helper_runtime_permissions(
|
|
&mut policy,
|
|
&helper_read_roots(&runtime_paths),
|
|
cwd.as_path(),
|
|
);
|
|
|
|
assert!(policy.can_read_path_with_cwd(codex_self_root.as_path(), cwd.as_path()));
|
|
assert!(policy.can_read_path_with_cwd(linux_sandbox_root.as_path(), cwd.as_path()));
|
|
}
|
|
}
|