From 9c3af9af8ddabe00977600c07c60784453cfc410 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Mon, 1 Jun 2026 18:05:53 -0700 Subject: [PATCH] core: merge built-in permission profiles as TOML --- codex-rs/core/src/config/config_tests.rs | 32 ++++++++- codex-rs/core/src/config/permissions.rs | 91 +++++++++++++++--------- 2 files changed, 87 insertions(+), 36 deletions(-) diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index 70719843ba..eecb784093 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -2356,7 +2356,13 @@ async fn default_permissions_profile_can_extend_builtin_workspace() -> std::io:: description: None, extends: Some(BUILT_IN_PERMISSION_PROFILE_WORKSPACE.to_string()), workspace_roots: None, - filesystem: None, + filesystem: Some(FilesystemPermissionsToml { + glob_scan_max_depth: None, + entries: BTreeMap::from([( + ":tmpdir".to_string(), + FilesystemPermissionToml::Access(FileSystemAccessMode::Read), + )]), + }), network: Some(NetworkToml { enabled: Some(true), ..Default::default() @@ -2383,6 +2389,30 @@ async fn default_permissions_profile_can_extend_builtin_workspace() -> std::io:: !policy.can_write_path_with_cwd(&cwd.path().join(".git"), cwd.path()), "expected profile extending :workspace to keep metadata carveouts, policy: {policy:?}" ); + assert!( + policy.entries.iter().any(|entry| matches!( + entry, + FileSystemSandboxEntry { + path: FileSystemPath::Special { + value: FileSystemSpecialPath::Tmpdir, + }, + access: FileSystemAccessMode::Read, + } + )), + "expected child :tmpdir read entry to replace the inherited write entry, policy: {policy:?}" + ); + assert!( + !policy.entries.iter().any(|entry| matches!( + entry, + FileSystemSandboxEntry { + path: FileSystemPath::Special { + value: FileSystemSpecialPath::Tmpdir, + }, + access: FileSystemAccessMode::Write, + } + )), + "expected inherited :tmpdir write entry to be removed, policy: {policy:?}" + ); assert_eq!( config.permissions.network_sandbox_policy(), NetworkSandboxPolicy::Enabled diff --git a/codex-rs/core/src/config/permissions.rs b/codex-rs/core/src/config/permissions.rs index a8546858f1..12aebf16d9 100644 --- a/codex-rs/core/src/config/permissions.rs +++ b/codex-rs/core/src/config/permissions.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::collections::BTreeMap; use std::io; use std::path::Component; use std::path::Path; @@ -196,25 +197,59 @@ pub(crate) fn resolve_permission_profile( profile_name: &str, ) -> io::Result { permissions - .resolve_profile(profile_name, extensible_builtin_parent_profile_marker) + .resolve_profile(profile_name, extensible_builtin_parent_profile) .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err.to_string())) } -/// Built-in parents provide their runtime permissions below. Resolution only -/// needs an empty profile marker so inheritance can terminate while preserving -/// the built-in parent id in `inherited_profile_names`. -fn extensible_builtin_parent_profile_marker(profile_name: &str) -> Option { - matches!( - profile_name, - BUILT_IN_READ_ONLY_PROFILE | BUILT_IN_WORKSPACE_PROFILE - ) - .then_some(PermissionProfileToml { - description: None, - extends: None, - workspace_roots: None, - filesystem: None, - network: None, - }) +fn extensible_builtin_parent_profile(profile_name: &str) -> Option { + match profile_name { + BUILT_IN_READ_ONLY_PROFILE => Some(PermissionProfileToml { + description: None, + extends: None, + workspace_roots: None, + filesystem: Some(FilesystemPermissionsToml { + glob_scan_max_depth: None, + entries: BTreeMap::from([( + ":root".to_string(), + FilesystemPermissionToml::Access(FileSystemAccessMode::Read), + )]), + }), + network: None, + }), + BUILT_IN_WORKSPACE_PROFILE => Some(PermissionProfileToml { + description: None, + extends: None, + workspace_roots: None, + filesystem: Some(FilesystemPermissionsToml { + glob_scan_max_depth: None, + entries: BTreeMap::from([ + ( + ":root".to_string(), + FilesystemPermissionToml::Access(FileSystemAccessMode::Read), + ), + ( + ":workspace_roots".to_string(), + FilesystemPermissionToml::Scoped(BTreeMap::from([ + (".".to_string(), FileSystemAccessMode::Write), + (".git".to_string(), FileSystemAccessMode::Read), + (".agents".to_string(), FileSystemAccessMode::Read), + (".codex".to_string(), FileSystemAccessMode::Read), + ])), + ), + ( + ":slash_tmp".to_string(), + FilesystemPermissionToml::Access(FileSystemAccessMode::Write), + ), + ( + ":tmpdir".to_string(), + FilesystemPermissionToml::Access(FileSystemAccessMode::Write), + ), + ]), + }), + network: None, + }), + _ => None, + } } pub(crate) fn network_proxy_config_for_profile_selection( @@ -244,25 +279,10 @@ pub(crate) fn compile_permission_profile( policy_cwd: &Path, startup_warnings: &mut Vec, ) -> io::Result<(FileSystemSandboxPolicy, NetworkSandboxPolicy)> { - let ResolvedPermissionProfileToml { - profile, - inherited_profile_names, - } = resolve_permission_profile(permissions, profile_name)?; - let base_permissions = inherited_profile_names.iter().find_map(|name| { - match name.as_str() { - BUILT_IN_READ_ONLY_PROFILE => Some(PermissionProfile::read_only()), - BUILT_IN_WORKSPACE_PROFILE => Some(PermissionProfile::workspace_write()), - _ => None, - } - .map(|profile| profile.to_runtime_permissions()) - }); - let (mut file_system_sandbox_policy, base_network_sandbox_policy) = base_permissions - .unwrap_or_else(|| { - ( - FileSystemSandboxPolicy::restricted(Vec::new()), - NetworkSandboxPolicy::Restricted, - ) - }); + let ResolvedPermissionProfileToml { profile, .. } = + resolve_permission_profile(permissions, profile_name)?; + let mut file_system_sandbox_policy = FileSystemSandboxPolicy::restricted(Vec::new()); + let base_network_sandbox_policy = NetworkSandboxPolicy::Restricted; if let Some(filesystem) = profile.filesystem.as_ref() { if filesystem.is_empty() && file_system_sandbox_policy.entries.is_empty() { push_warning( @@ -693,6 +713,7 @@ fn parse_special_path(path: &str) -> Option { ":minimal" => Some(FileSystemSpecialPath::Minimal), ":workspace_roots" => Some(FileSystemSpecialPath::project_roots(/*subpath*/ None)), ":tmpdir" => Some(FileSystemSpecialPath::Tmpdir), + ":slash_tmp" => Some(FileSystemSpecialPath::SlashTmp), _ if path.starts_with(':') => { Some(FileSystemSpecialPath::unknown(path, /*subpath*/ None)) }