mirror of
https://github.com/openai/codex.git
synced 2026-09-09 15:58:47 +00:00
exec-server: carry filesystem sandbox profiles
This commit is contained in:
@@ -110,6 +110,7 @@ use codex_protocol::request_user_input::RequestUserInputResponse;
|
||||
use codex_rmcp_client::ElicitationResponse;
|
||||
use codex_rollout::RolloutConfig;
|
||||
use codex_rollout::state_db;
|
||||
use codex_sandboxing::policy_transforms::merge_permission_profiles;
|
||||
use codex_shell_command::parse_command::parse_command;
|
||||
use codex_terminal_detection::user_agent;
|
||||
use codex_thread_store::LocalThreadStore;
|
||||
@@ -1011,15 +1012,21 @@ impl TurnContext {
|
||||
&self,
|
||||
additional_permissions: Option<PermissionProfile>,
|
||||
) -> FileSystemSandboxContext {
|
||||
let base_permissions = PermissionProfile::from_runtime_permissions(
|
||||
&self.file_system_sandbox_policy,
|
||||
self.network_sandbox_policy,
|
||||
);
|
||||
let permissions =
|
||||
merge_permission_profiles(Some(&base_permissions), additional_permissions.as_ref())
|
||||
.unwrap_or(base_permissions);
|
||||
FileSystemSandboxContext {
|
||||
sandbox_policy: self.sandbox_policy.get().clone(),
|
||||
permissions,
|
||||
windows_sandbox_level: self.windows_sandbox_level,
|
||||
windows_sandbox_private_desktop: self
|
||||
.config
|
||||
.permissions
|
||||
.windows_sandbox_private_desktop,
|
||||
use_legacy_landlock: self.features.use_legacy_landlock(),
|
||||
additional_permissions,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ use codex_protocol::protocol::FileChange;
|
||||
use codex_protocol::protocol::ReviewDecision;
|
||||
use codex_sandboxing::SandboxType;
|
||||
use codex_sandboxing::SandboxablePreference;
|
||||
use codex_sandboxing::policy_transforms::merge_permission_profiles;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use futures::future::BoxFuture;
|
||||
use std::path::PathBuf;
|
||||
@@ -74,12 +75,18 @@ impl ApplyPatchRuntime {
|
||||
return None;
|
||||
}
|
||||
|
||||
let base_permissions = PermissionProfile::from_runtime_permissions(
|
||||
attempt.file_system_policy,
|
||||
attempt.network_policy,
|
||||
);
|
||||
let permissions =
|
||||
merge_permission_profiles(Some(&base_permissions), req.additional_permissions.as_ref())
|
||||
.unwrap_or(base_permissions);
|
||||
Some(FileSystemSandboxContext {
|
||||
sandbox_policy: attempt.policy.clone(),
|
||||
permissions,
|
||||
windows_sandbox_level: attempt.windows_sandbox_level,
|
||||
windows_sandbox_private_desktop: attempt.windows_sandbox_private_desktop,
|
||||
use_legacy_landlock: attempt.use_legacy_landlock,
|
||||
additional_permissions: req.additional_permissions.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use codex_protocol::protocol::GranularApprovalConfig;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_sandboxing::SandboxManager;
|
||||
use codex_sandboxing::SandboxType;
|
||||
use codex_sandboxing::policy_transforms::merge_permission_profiles;
|
||||
use core_test_support::PathBufExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::HashMap;
|
||||
@@ -118,8 +119,16 @@ fn file_system_sandbox_context_uses_active_attempt() {
|
||||
let sandbox = ApplyPatchRuntime::file_system_sandbox_context_for_attempt(&req, &attempt)
|
||||
.expect("sandbox context");
|
||||
|
||||
assert_eq!(sandbox.sandbox_policy, sandbox_policy);
|
||||
assert_eq!(sandbox.additional_permissions, Some(additional_permissions));
|
||||
let base_permissions = PermissionProfile::from_runtime_permissions(
|
||||
&file_system_policy,
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
);
|
||||
let Some(expected_permissions) =
|
||||
merge_permission_profiles(Some(&base_permissions), Some(&additional_permissions))
|
||||
else {
|
||||
panic!("merged permissions should not be empty");
|
||||
};
|
||||
assert_eq!(sandbox.permissions, expected_permissions);
|
||||
assert_eq!(
|
||||
sandbox.windows_sandbox_level,
|
||||
WindowsSandboxLevel::RestrictedToken
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use async_trait::async_trait;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::permissions::FileSystemSandboxKind;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use tokio::io;
|
||||
@@ -40,31 +43,36 @@ pub struct ReadDirectoryEntry {
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FileSystemSandboxContext {
|
||||
pub sandbox_policy: SandboxPolicy,
|
||||
pub permissions: PermissionProfile,
|
||||
pub windows_sandbox_level: WindowsSandboxLevel,
|
||||
#[serde(default)]
|
||||
pub windows_sandbox_private_desktop: bool,
|
||||
#[serde(default)]
|
||||
pub use_legacy_landlock: bool,
|
||||
pub additional_permissions: Option<PermissionProfile>,
|
||||
}
|
||||
|
||||
impl FileSystemSandboxContext {
|
||||
pub fn new(sandbox_policy: SandboxPolicy) -> Self {
|
||||
let permissions = PermissionProfile::from_runtime_permissions(
|
||||
&FileSystemSandboxPolicy::from(&sandbox_policy),
|
||||
NetworkSandboxPolicy::from(&sandbox_policy),
|
||||
);
|
||||
Self::from_permission_profile(permissions)
|
||||
}
|
||||
|
||||
pub fn from_permission_profile(permissions: PermissionProfile) -> Self {
|
||||
Self {
|
||||
sandbox_policy,
|
||||
permissions,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
windows_sandbox_private_desktop: false,
|
||||
use_legacy_landlock: false,
|
||||
additional_permissions: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn should_run_in_sandbox(&self) -> bool {
|
||||
matches!(
|
||||
self.sandbox_policy,
|
||||
SandboxPolicy::ReadOnly { .. } | SandboxPolicy::WorkspaceWrite { .. }
|
||||
)
|
||||
let file_system_policy = self.permissions.file_system_sandbox_policy();
|
||||
matches!(file_system_policy.kind, FileSystemSandboxKind::Restricted)
|
||||
&& !file_system_policy.has_full_disk_write_access()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use codex_app_server_protocol::JSONRPCErrorError;
|
||||
use codex_protocol::models::FileSystemPermissions;
|
||||
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;
|
||||
@@ -45,19 +44,21 @@ impl FileSystemSandboxRunner {
|
||||
sandbox: &FileSystemSandboxContext,
|
||||
request: FsHelperRequest,
|
||||
) -> Result<FsHelperPayload, JSONRPCErrorError> {
|
||||
let helper_sandbox_policy = normalize_sandbox_policy_root_aliases(
|
||||
sandbox_policy_with_helper_runtime_defaults(&sandbox.sandbox_policy),
|
||||
);
|
||||
let cwd = current_sandbox_cwd().map_err(io_error)?;
|
||||
let cwd = AbsolutePathBuf::from_absolute_path(cwd.as_path())
|
||||
.map_err(|err| invalid_request(format!("current directory is not absolute: {err}")))?;
|
||||
let file_system_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy(
|
||||
&helper_sandbox_policy,
|
||||
let mut file_system_policy = sandbox.permissions.file_system_sandbox_policy();
|
||||
add_helper_runtime_permissions(
|
||||
&mut file_system_policy,
|
||||
helper_read_root(&self.runtime_paths),
|
||||
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(
|
||||
&helper_sandbox_policy,
|
||||
&sandbox_policy,
|
||||
&file_system_policy,
|
||||
network_policy,
|
||||
&cwd,
|
||||
@@ -89,9 +90,7 @@ impl FileSystemSandboxRunner {
|
||||
args: vec![CODEX_FS_HELPER_ARG1.to_string()],
|
||||
cwd: cwd.clone(),
|
||||
env: HashMap::new(),
|
||||
additional_permissions: Some(
|
||||
self.helper_permissions(sandbox_context.additional_permissions.as_ref()),
|
||||
),
|
||||
additional_permissions: None,
|
||||
};
|
||||
sandbox_manager
|
||||
.transform(SandboxTransformRequest {
|
||||
@@ -110,74 +109,91 @@ impl FileSystemSandboxRunner {
|
||||
})
|
||||
.map_err(|err| invalid_request(format!("failed to prepare fs sandbox: {err}")))
|
||||
}
|
||||
}
|
||||
|
||||
fn helper_permissions(
|
||||
&self,
|
||||
additional_permissions: Option<&PermissionProfile>,
|
||||
) -> PermissionProfile {
|
||||
let helper_read_root = self
|
||||
.runtime_paths
|
||||
.codex_self_exe
|
||||
.parent()
|
||||
.and_then(|path| AbsolutePathBuf::from_absolute_path(path).ok());
|
||||
let file_system =
|
||||
match additional_permissions.and_then(|permissions| permissions.file_system.clone()) {
|
||||
Some(mut file_system) => {
|
||||
if let Some(helper_read_root) = &helper_read_root {
|
||||
let helper_read_entry = FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: helper_read_root.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
};
|
||||
if !file_system.entries.contains(&helper_read_entry) {
|
||||
file_system.entries.push(helper_read_entry);
|
||||
}
|
||||
}
|
||||
Some(file_system)
|
||||
}
|
||||
None => helper_read_root.map(|helper_read_root| {
|
||||
FileSystemPermissions::from_read_write_roots(
|
||||
Some(vec![helper_read_root]),
|
||||
/*write*/ None,
|
||||
)
|
||||
}),
|
||||
};
|
||||
fn helper_read_root(runtime_paths: &ExecServerRuntimePaths) -> Option<AbsolutePathBuf> {
|
||||
runtime_paths
|
||||
.codex_self_exe
|
||||
.parent()
|
||||
.and_then(|path| AbsolutePathBuf::from_absolute_path(path).ok())
|
||||
}
|
||||
|
||||
PermissionProfile {
|
||||
network: None,
|
||||
file_system,
|
||||
fn add_helper_runtime_permissions(
|
||||
file_system_policy: &mut FileSystemSandboxPolicy,
|
||||
helper_read_root: Option<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);
|
||||
}
|
||||
}
|
||||
|
||||
let Some(helper_read_root) = helper_read_root else {
|
||||
return;
|
||||
};
|
||||
if file_system_policy.can_read_path_with_cwd(helper_read_root.as_path(), cwd) {
|
||||
return;
|
||||
}
|
||||
|
||||
file_system_policy.entries.push(FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: helper_read_root,
|
||||
},
|
||||
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_sandbox_policy_root_aliases(sandbox_policy: SandboxPolicy) -> SandboxPolicy {
|
||||
let mut sandbox_policy = sandbox_policy;
|
||||
match &mut sandbox_policy {
|
||||
SandboxPolicy::ReadOnly {
|
||||
access: ReadOnlyAccess::Restricted { readable_roots, .. },
|
||||
..
|
||||
} => {
|
||||
normalize_root_aliases(readable_roots);
|
||||
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());
|
||||
}
|
||||
SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots,
|
||||
read_only_access,
|
||||
..
|
||||
} => {
|
||||
normalize_root_aliases(writable_roots);
|
||||
if let ReadOnlyAccess::Restricted { readable_roots, .. } = read_only_access {
|
||||
normalize_root_aliases(readable_roots);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
sandbox_policy
|
||||
}
|
||||
|
||||
fn normalize_root_aliases(paths: &mut Vec<AbsolutePathBuf>) {
|
||||
for path in paths {
|
||||
*path = normalize_top_level_alias(path.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,28 +278,6 @@ fn spawn_command(
|
||||
command.spawn().map_err(io_error)
|
||||
}
|
||||
|
||||
fn sandbox_policy_with_helper_runtime_defaults(sandbox_policy: &SandboxPolicy) -> SandboxPolicy {
|
||||
let mut sandbox_policy = sandbox_policy.clone();
|
||||
match &mut sandbox_policy {
|
||||
SandboxPolicy::ReadOnly { access, .. } => enable_platform_defaults(access),
|
||||
SandboxPolicy::WorkspaceWrite {
|
||||
read_only_access, ..
|
||||
} => enable_platform_defaults(read_only_access),
|
||||
SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } => {}
|
||||
}
|
||||
sandbox_policy
|
||||
}
|
||||
|
||||
fn enable_platform_defaults(access: &mut ReadOnlyAccess) {
|
||||
if let ReadOnlyAccess::Restricted {
|
||||
include_platform_defaults,
|
||||
..
|
||||
} = access
|
||||
{
|
||||
*include_platform_defaults = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn io_error(err: std::io::Error) -> JSONRPCErrorError {
|
||||
internal_error(err.to_string())
|
||||
}
|
||||
@@ -296,21 +290,23 @@ fn json_error(err: serde_json::Error) -> JSONRPCErrorError {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use codex_protocol::models::FileSystemPermissions;
|
||||
use codex_protocol::models::NetworkPermissions;
|
||||
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::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::sandbox_policy_with_helper_runtime_defaults;
|
||||
use super::add_helper_runtime_permissions;
|
||||
use super::helper_read_root;
|
||||
|
||||
#[test]
|
||||
fn helper_sandbox_policy_enables_platform_defaults_for_read_only_access() {
|
||||
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,
|
||||
@@ -318,23 +314,18 @@ mod tests {
|
||||
},
|
||||
network_access: false,
|
||||
};
|
||||
let mut policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy(&sandbox_policy, cwd.as_path());
|
||||
|
||||
let updated = sandbox_policy_with_helper_runtime_defaults(&sandbox_policy);
|
||||
add_helper_runtime_permissions(&mut policy, /*helper_read_root*/ None, cwd.as_path());
|
||||
|
||||
assert_eq!(
|
||||
updated,
|
||||
SandboxPolicy::ReadOnly {
|
||||
access: ReadOnlyAccess::Restricted {
|
||||
include_platform_defaults: true,
|
||||
readable_roots: Vec::new(),
|
||||
},
|
||||
network_access: false,
|
||||
}
|
||||
);
|
||||
assert!(policy.include_platform_defaults());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn helper_sandbox_policy_enables_platform_defaults_for_workspace_read_access() {
|
||||
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 {
|
||||
@@ -345,83 +336,87 @@ mod tests {
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
};
|
||||
let mut policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy(&sandbox_policy, cwd.as_path());
|
||||
|
||||
let updated = sandbox_policy_with_helper_runtime_defaults(&sandbox_policy);
|
||||
add_helper_runtime_permissions(&mut policy, /*helper_read_root*/ None, cwd.as_path());
|
||||
|
||||
assert_eq!(
|
||||
updated,
|
||||
SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: Vec::new(),
|
||||
read_only_access: ReadOnlyAccess::Restricted {
|
||||
include_platform_defaults: true,
|
||||
readable_roots: Vec::new(),
|
||||
},
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
}
|
||||
);
|
||||
assert!(policy.include_platform_defaults());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn helper_permissions_strip_network_grants() {
|
||||
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.clone(),
|
||||
/*codex_linux_sandbox_exe*/ None,
|
||||
)
|
||||
.expect("runtime paths");
|
||||
let runner = FileSystemSandboxRunner::new(runtime_paths);
|
||||
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(
|
||||
codex_self_exe.parent().expect("current exe parent"),
|
||||
runtime_paths
|
||||
.codex_self_exe
|
||||
.parent()
|
||||
.expect("current exe parent"),
|
||||
)
|
||||
.expect("absolute readable path");
|
||||
let writable = AbsolutePathBuf::from_absolute_path(std::env::temp_dir().as_path())
|
||||
.expect("absolute writable path");
|
||||
|
||||
let permissions = runner.helper_permissions(Some(&PermissionProfile {
|
||||
network: Some(NetworkPermissions {
|
||||
enabled: Some(true),
|
||||
}),
|
||||
file_system: Some(FileSystemPermissions::from_read_write_roots(
|
||||
Some(vec![]),
|
||||
Some(vec![writable.clone()]),
|
||||
)),
|
||||
}));
|
||||
let (read, write) = permissions
|
||||
.file_system
|
||||
.as_ref()
|
||||
.and_then(FileSystemPermissions::legacy_read_write_roots)
|
||||
.expect("helper permissions should stay lossless as legacy read/write roots");
|
||||
add_helper_runtime_permissions(
|
||||
&mut policy,
|
||||
helper_read_root(&runtime_paths),
|
||||
cwd.as_path(),
|
||||
);
|
||||
|
||||
assert_eq!(permissions.network, None);
|
||||
assert_eq!(write, Some(vec![writable]));
|
||||
assert_eq!(read, Some(vec![readable]));
|
||||
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_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.clone(),
|
||||
/*codex_linux_sandbox_exe*/ None,
|
||||
)
|
||||
.expect("runtime paths");
|
||||
let runner = FileSystemSandboxRunner::new(runtime_paths);
|
||||
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(
|
||||
codex_self_exe.parent().expect("current exe parent"),
|
||||
runtime_paths
|
||||
.codex_self_exe
|
||||
.parent()
|
||||
.expect("current exe parent"),
|
||||
)
|
||||
.expect("absolute readable path");
|
||||
|
||||
let permissions = runner.helper_permissions(/*additional_permissions*/ None);
|
||||
|
||||
assert_eq!(permissions.network, None);
|
||||
assert_eq!(
|
||||
permissions.file_system,
|
||||
Some(FileSystemPermissions::from_read_write_roots(
|
||||
Some(vec![readable]),
|
||||
None,
|
||||
))
|
||||
add_helper_runtime_permissions(
|
||||
&mut policy,
|
||||
helper_read_root(&runtime_paths),
|
||||
cwd.as_path(),
|
||||
);
|
||||
|
||||
assert!(policy.can_read_path_with_cwd(readable.as_path(), cwd.as_path()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ use codex_protocol::models::FileSystemPermissions;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::protocol::ReadOnlyAccess;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_sandboxing::policy_transforms::merge_permission_profiles;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::TempDir;
|
||||
@@ -473,13 +474,19 @@ async fn file_system_sandboxed_write_allows_additional_write_root(use_remote: bo
|
||||
std::fs::create_dir_all(&writable_dir)?;
|
||||
|
||||
let mut sandbox = read_only_sandbox(readable_dir);
|
||||
sandbox.additional_permissions = Some(PermissionProfile {
|
||||
let additional_permissions = PermissionProfile {
|
||||
network: None,
|
||||
file_system: Some(FileSystemPermissions::from_read_write_roots(
|
||||
None,
|
||||
Some(vec![absolute_path(writable_dir)]),
|
||||
)),
|
||||
});
|
||||
};
|
||||
let Some(permissions) =
|
||||
merge_permission_profiles(Some(&sandbox.permissions), Some(&additional_permissions))
|
||||
else {
|
||||
panic!("merged permissions should not be empty");
|
||||
};
|
||||
sandbox.permissions = permissions;
|
||||
|
||||
file_system
|
||||
.write_file(
|
||||
|
||||
Reference in New Issue
Block a user