refactor(permissions): lower Seatbelt from effective filesystem permissions

Co-authored-by: Codex noreply@openai.com
This commit is contained in:
viyatb-oai
2026-05-27 17:41:02 -07:00
co-authored by Codex noreply@openai.com
parent 4418bce894
commit 0dc8cf86c9
4 changed files with 100 additions and 49 deletions
+16 -4
View File
@@ -17,6 +17,10 @@ use codex_core::spawn::CODEX_SANDBOX_ENV_VAR;
use codex_core::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR;
use codex_protocol::config_types::SandboxMode;
use codex_protocol::permissions::NetworkSandboxPolicy;
#[cfg(target_os = "macos")]
use codex_sandboxing::EffectiveFilesystemPermissions;
#[cfg(target_os = "macos")]
use codex_sandboxing::FilesystemPermissionsContext;
use codex_sandboxing::landlock::allow_network_for_proxy;
use codex_sandboxing::landlock::create_linux_sandbox_command_args_for_permission_profile;
#[cfg(target_os = "macos")]
@@ -260,13 +264,21 @@ async fn run_command_under_sandbox(
let mut child = match sandbox_type {
#[cfg(target_os = "macos")]
SandboxType::Seatbelt => {
let file_system_sandbox_policy = config.permissions.file_system_sandbox_policy();
let network_sandbox_policy = config.permissions.network_sandbox_policy();
let permission_profile = config.permissions.effective_permission_profile();
let network_sandbox_policy = permission_profile.network_sandbox_policy();
let effective_filesystem_permissions = EffectiveFilesystemPermissions::from_profile(
&permission_profile,
FilesystemPermissionsContext {
policy_evaluation_cwd: &permission_profile_cwd,
},
)
.map_err(|err| {
anyhow::anyhow!("failed to derive effective filesystem permissions: {err}")
})?;
let args = create_seatbelt_command_args(CreateSeatbeltCommandArgsParams {
command,
file_system_sandbox_policy: &file_system_sandbox_policy,
effective_filesystem_permissions: &effective_filesystem_permissions,
network_sandbox_policy,
sandbox_policy_cwd: permission_profile_cwd.as_path(),
enforce_managed_network: false,
network: network.as_ref(),
extra_allow_unix_sockets: allow_unix_sockets,
+1 -2
View File
@@ -217,9 +217,8 @@ impl SandboxManager {
let mut args = create_seatbelt_command_args(CreateSeatbeltCommandArgsParams {
command: os_argv_to_strings(argv),
file_system_sandbox_policy: &effective_file_system_policy,
effective_filesystem_permissions: &effective_filesystem_permissions,
network_sandbox_policy: effective_network_policy,
sandbox_policy_cwd,
enforce_managed_network,
network,
extra_allow_unix_sockets: &[],
+46 -32
View File
@@ -1,7 +1,10 @@
use crate::EffectiveFilesystemPermissions;
use crate::FilesystemPermissionsContext;
use codex_network_proxy::NetworkProxy;
use codex_network_proxy::PROXY_URL_ENV_KEYS;
use codex_network_proxy::has_proxy_url_env_vars;
use codex_network_proxy::proxy_url_env_value;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::permissions::PROTECTED_METADATA_PATH_NAMES;
@@ -404,9 +407,8 @@ fn seatbelt_protected_metadata_name_regex(root: &AbsolutePathBuf, name: &str) ->
}
fn protected_metadata_names_for_writable_root(
file_system_sandbox_policy: &FileSystemSandboxPolicy,
effective_filesystem_permissions: &EffectiveFilesystemPermissions,
writable_root: &WritableRoot,
cwd: &Path,
) -> Vec<String> {
let mut names = writable_root.protected_metadata_names.clone();
for name in PROTECTED_METADATA_PATH_NAMES {
@@ -414,7 +416,7 @@ fn protected_metadata_names_for_writable_root(
continue;
}
let path = writable_root.root.join(*name);
if !file_system_sandbox_policy.can_write_path_with_cwd(path.as_path(), cwd) {
if !effective_filesystem_permissions.can_write(path.as_path()) {
names.push((*name).to_string());
}
}
@@ -422,25 +424,24 @@ fn protected_metadata_names_for_writable_root(
}
fn build_seatbelt_unreadable_glob_policy(
file_system_sandbox_policy: &FileSystemSandboxPolicy,
cwd: &Path,
effective_filesystem_permissions: &EffectiveFilesystemPermissions,
) -> String {
// Seatbelt does not understand the filesystem policy's glob syntax directly.
// Convert each unreadable pattern into an anchored regex deny rule and apply
// it to both reads and unlink-style writes so a denied path cannot be probed
// through destructive filesystem operations.
let unreadable_globs = file_system_sandbox_policy.get_unreadable_globs_with_cwd(cwd);
if unreadable_globs.is_empty() {
if effective_filesystem_permissions.unreadable_globs.is_empty() {
return String::new();
}
let mut policy_components = Vec::new();
for pattern in unreadable_globs {
for glob in &effective_filesystem_permissions.unreadable_globs {
let pattern = glob.pattern();
let mut regexes = BTreeSet::new();
if let Some(regex) = seatbelt_regex_for_unreadable_glob(&pattern) {
if let Some(regex) = seatbelt_regex_for_unreadable_glob(pattern) {
regexes.insert(regex);
}
if let Some(pattern) = canonicalize_glob_static_prefix_for_sandbox(&pattern)
if let Some(pattern) = canonicalize_glob_static_prefix_for_sandbox(pattern)
&& let Some(regex) = seatbelt_regex_for_unreadable_glob(&pattern)
{
regexes.insert(regex);
@@ -573,15 +574,31 @@ fn create_seatbelt_command_args_for_legacy_policy(
enforce_managed_network: bool,
network: Option<&NetworkProxy>,
) -> Vec<String> {
let legacy_workspace_root = AbsolutePathBuf::from_absolute_path(sandbox_policy_cwd)
.unwrap_or_else(|err| panic!("sandbox policy cwd should be absolute: {err}"));
let file_system_sandbox_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
sandbox_policy,
sandbox_policy_cwd,
)
.materialize_project_roots_with_workspace_roots(std::slice::from_ref(&legacy_workspace_root));
let network_sandbox_policy = NetworkSandboxPolicy::from(sandbox_policy);
let permission_profile = PermissionProfile::from_runtime_permissions(
&file_system_sandbox_policy,
network_sandbox_policy,
);
let effective_filesystem_permissions = EffectiveFilesystemPermissions::from_profile(
&permission_profile,
FilesystemPermissionsContext {
policy_evaluation_cwd: &legacy_workspace_root,
},
)
.unwrap_or_else(|err| {
panic!("legacy filesystem policy should yield effective permissions: {err}")
});
create_seatbelt_command_args(CreateSeatbeltCommandArgsParams {
command,
file_system_sandbox_policy: &file_system_sandbox_policy,
network_sandbox_policy: NetworkSandboxPolicy::from(sandbox_policy),
sandbox_policy_cwd,
effective_filesystem_permissions: &effective_filesystem_permissions,
network_sandbox_policy,
enforce_managed_network,
network,
extra_allow_unix_sockets: &[],
@@ -591,9 +608,8 @@ fn create_seatbelt_command_args_for_legacy_policy(
#[derive(Debug)]
pub struct CreateSeatbeltCommandArgsParams<'a> {
pub command: Vec<String>,
pub file_system_sandbox_policy: &'a FileSystemSandboxPolicy,
pub effective_filesystem_permissions: &'a EffectiveFilesystemPermissions,
pub network_sandbox_policy: NetworkSandboxPolicy,
pub sandbox_policy_cwd: &'a Path,
pub enforce_managed_network: bool,
pub network: Option<&'a NetworkProxy>,
pub extra_allow_unix_sockets: &'a [AbsolutePathBuf],
@@ -602,18 +618,16 @@ pub struct CreateSeatbeltCommandArgsParams<'a> {
pub fn create_seatbelt_command_args(args: CreateSeatbeltCommandArgsParams<'_>) -> Vec<String> {
let CreateSeatbeltCommandArgsParams {
command,
file_system_sandbox_policy,
effective_filesystem_permissions,
network_sandbox_policy,
sandbox_policy_cwd,
enforce_managed_network,
network,
extra_allow_unix_sockets,
} = args;
let unreadable_roots =
file_system_sandbox_policy.get_unreadable_roots_with_cwd(sandbox_policy_cwd);
let unreadable_roots = effective_filesystem_permissions.unreadable_roots.clone();
let (file_write_policy, file_write_dir_params) =
if file_system_sandbox_policy.has_full_disk_write_access() {
if effective_filesystem_permissions.has_full_disk_write_access() {
if unreadable_roots.is_empty() {
// Allegedly, this is more permissive than `(allow file-write*)`.
(
@@ -635,14 +649,14 @@ pub fn create_seatbelt_command_args(args: CreateSeatbeltCommandArgsParams<'_>) -
build_seatbelt_access_policy(
"file-write*",
"WRITABLE_ROOT",
file_system_sandbox_policy
.get_writable_roots_with_cwd(sandbox_policy_cwd)
.into_iter()
effective_filesystem_permissions
.writable_roots
.iter()
.cloned()
.map(|root| SeatbeltAccessRoot {
protected_metadata_names: protected_metadata_names_for_writable_root(
file_system_sandbox_policy,
effective_filesystem_permissions,
&root,
sandbox_policy_cwd,
),
root: root.root,
excluded_subpaths: root.read_only_subpaths,
@@ -652,7 +666,7 @@ pub fn create_seatbelt_command_args(args: CreateSeatbeltCommandArgsParams<'_>) -
};
let (file_read_policy, file_read_dir_params) =
if file_system_sandbox_policy.has_full_disk_read_access() {
if effective_filesystem_permissions.has_full_disk_read_access() {
if unreadable_roots.is_empty() {
(
"; allow read-only file operations\n(allow file-read*)".to_string(),
@@ -677,9 +691,10 @@ pub fn create_seatbelt_command_args(args: CreateSeatbeltCommandArgsParams<'_>) -
let (policy, params) = build_seatbelt_access_policy(
"file-read*",
"READABLE_ROOT",
file_system_sandbox_policy
.get_readable_roots_with_cwd(sandbox_policy_cwd)
.into_iter()
effective_filesystem_permissions
.readable_roots
.iter()
.cloned()
.map(|root| SeatbeltAccessRoot {
excluded_subpaths: unreadable_roots
.iter()
@@ -705,9 +720,8 @@ pub fn create_seatbelt_command_args(args: CreateSeatbeltCommandArgsParams<'_>) -
let network_policy =
dynamic_network_policy_for_network(network_sandbox_policy, enforce_managed_network, &proxy);
let include_platform_defaults = file_system_sandbox_policy.include_platform_defaults();
let deny_read_policy =
build_seatbelt_unreadable_glob_policy(file_system_sandbox_policy, sandbox_policy_cwd);
let include_platform_defaults = effective_filesystem_permissions.include_platform_defaults;
let deny_read_policy = build_seatbelt_unreadable_glob_policy(effective_filesystem_permissions);
let mut policy_sections = vec![
MACOS_SEATBELT_BASE_POLICY.to_string(),
file_read_policy,
+37 -11
View File
@@ -19,6 +19,7 @@ use codex_network_proxy::NetworkProxyConfig;
use codex_network_proxy::NetworkProxyConstraints;
use codex_network_proxy::NetworkProxyState;
use codex_network_proxy::build_config_state;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSandboxEntry;
@@ -50,6 +51,30 @@ fn absolute_path(path: &str) -> AbsolutePathBuf {
AbsolutePathBuf::from_absolute_path(Path::new(path)).expect("absolute path")
}
fn effective_permissions(
file_system_policy: &FileSystemSandboxPolicy,
cwd: &Path,
) -> crate::EffectiveFilesystemPermissions {
let policy_evaluation_cwd =
AbsolutePathBuf::from_absolute_path(cwd).expect("absolute sandbox policy cwd");
let file_system_policy = file_system_policy
.clone()
.materialize_project_roots_with_workspace_roots(std::slice::from_ref(
&policy_evaluation_cwd,
));
let permission_profile = PermissionProfile::from_runtime_permissions(
&file_system_policy,
NetworkSandboxPolicy::Restricted,
);
crate::EffectiveFilesystemPermissions::from_profile(
&permission_profile,
crate::FilesystemPermissionsContext {
policy_evaluation_cwd: &policy_evaluation_cwd,
},
)
.expect("derive effective filesystem permissions")
}
fn seatbelt_policy_arg(args: &[String]) -> &str {
let policy_index = args
.iter()
@@ -198,12 +223,12 @@ fn explicit_unreadable_paths_are_excluded_from_full_disk_read_and_write_access()
access: FileSystemAccessMode::Deny,
},
]);
let effective_permissions = effective_permissions(&file_system_policy, Path::new("/"));
let args = create_seatbelt_command_args(CreateSeatbeltCommandArgsParams {
command: vec!["/bin/true".to_string()],
file_system_sandbox_policy: &file_system_policy,
effective_filesystem_permissions: &effective_permissions,
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
sandbox_policy_cwd: Path::new("/"),
enforce_managed_network: false,
network: None,
extra_allow_unix_sockets: &[],
@@ -270,12 +295,12 @@ fn explicit_unreadable_paths_are_excluded_from_readable_roots() {
access: FileSystemAccessMode::Deny,
},
]);
let effective_permissions = effective_permissions(&file_system_policy, Path::new("/"));
let args = create_seatbelt_command_args(CreateSeatbeltCommandArgsParams {
command: vec!["/bin/true".to_string()],
file_system_sandbox_policy: &file_system_policy,
effective_filesystem_permissions: &effective_permissions,
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
sandbox_policy_cwd: Path::new("/"),
enforce_managed_network: false,
network: None,
extra_allow_unix_sockets: &[],
@@ -374,8 +399,9 @@ fn unreadable_glob_policy_includes_canonicalized_static_prefix() {
path: FileSystemPath::GlobPattern { pattern },
access: FileSystemAccessMode::Deny,
});
let effective_permissions = effective_permissions(&policy, temp_dir.path());
let seatbelt_policy = build_seatbelt_unreadable_glob_policy(&policy, temp_dir.path());
let seatbelt_policy = build_seatbelt_unreadable_glob_policy(&effective_permissions);
assert!(
seatbelt_policy.contains(&format!(r#"(deny file-read* (regex #"{expected_regex}"))"#)),
@@ -573,12 +599,12 @@ fn create_seatbelt_args_allowlists_explicit_unix_socket_paths_without_proxy() {
&SandboxPolicy::new_read_only_policy(),
cwd.path(),
);
let effective_permissions = effective_permissions(&file_system_policy, cwd.path());
let extra_allow_unix_sockets = vec![absolute_path("/tmp/codex-browser-use")];
let args = create_seatbelt_command_args(CreateSeatbeltCommandArgsParams {
command: vec!["/usr/bin/true".to_string()],
file_system_sandbox_policy: &file_system_policy,
effective_filesystem_permissions: &effective_permissions,
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
sandbox_policy_cwd: cwd.path(),
enforce_managed_network: false,
network: None,
extra_allow_unix_sockets: &extra_allow_unix_sockets,
@@ -613,6 +639,7 @@ async fn create_seatbelt_args_merges_proxy_and_explicit_unix_socket_paths() -> a
&SandboxPolicy::new_read_only_policy(),
cwd.path(),
);
let effective_permissions = effective_permissions(&file_system_policy, cwd.path());
let network_socket = "/tmp/codex-proxy-use";
let explicit_socket = "/tmp/codex-browser-use";
let mut network_config = NetworkProxyConfig::default();
@@ -634,9 +661,8 @@ async fn create_seatbelt_args_merges_proxy_and_explicit_unix_socket_paths() -> a
let args = create_seatbelt_command_args(CreateSeatbeltCommandArgsParams {
command: vec!["/usr/bin/true".to_string()],
file_system_sandbox_policy: &file_system_policy,
effective_filesystem_permissions: &effective_permissions,
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
sandbox_policy_cwd: cwd.path(),
enforce_managed_network: false,
network: Some(&network_proxy),
extra_allow_unix_sockets: &extra_allow_unix_sockets,
@@ -672,12 +698,12 @@ fn create_seatbelt_args_preserves_full_network_with_explicit_unix_socket_paths()
&SandboxPolicy::new_read_only_policy(),
cwd.path(),
);
let effective_permissions = effective_permissions(&file_system_policy, cwd.path());
let extra_allow_unix_sockets = vec![absolute_path("/tmp/codex-browser-use")];
let args = create_seatbelt_command_args(CreateSeatbeltCommandArgsParams {
command: vec!["/usr/bin/true".to_string()],
file_system_sandbox_policy: &file_system_policy,
effective_filesystem_permissions: &effective_permissions,
network_sandbox_policy: NetworkSandboxPolicy::Enabled,
sandbox_policy_cwd: cwd.path(),
enforce_managed_network: false,
network: None,
extra_allow_unix_sockets: &extra_allow_unix_sockets,