use domain::permission::{Capability, Effect, EffectivePermissions, Posture}; use domain::ports::StructuredProviderLaunchPolicy; use domain::sandbox::{compile_sandbox_plan, PathAccess, SandboxContext}; use domain::NetworkPolicy; pub(crate) fn compile_codex_launch_policy( project_root: &str, run_dir: &str, permissions: Option<&EffectivePermissions>, network: Option, extra_writable_roots: &[String], ) -> StructuredProviderLaunchPolicy { let mut writable_roots = codex_permission_writable_roots(project_root, run_dir, permissions); let sandbox_mode = codex_sandbox_mode(permissions, &writable_roots).to_owned(); if sandbox_mode == "workspace-write" { append_unique_paths(&mut writable_roots, extra_writable_roots); } else { writable_roots.clear(); } StructuredProviderLaunchPolicy::Codex { sandbox_mode, writable_roots, network_access: codex_network_access(network), } } pub(crate) fn codex_permission_writable_roots( project_root: &str, run_dir: &str, permissions: Option<&EffectivePermissions>, ) -> Vec { let Some(permissions) = permissions else { return if project_root.is_empty() { Vec::new() } else { vec![project_root.to_owned()] }; }; let mut roots = Vec::new(); if !has_rw_deny(permissions) && permissions.fallback() != Posture::Deny && permissions.decide_file(Capability::Write, "") != Posture::Deny { roots.push(project_root.to_owned()); } if let Some(plan) = compile_sandbox_plan( Some(permissions), &SandboxContext { project_root, run_dir, }, ) { for grant in plan .allowed .iter() .filter(|grant| grant.access.contains(PathAccess::RW)) { append_unique_path(&mut roots, &grant.abs_root); } } roots.sort(); roots } pub(crate) fn codex_sandbox_mode( permissions: Option<&EffectivePermissions>, writable_roots: &[String], ) -> &'static str { if !writable_roots.is_empty() { return "workspace-write"; } if permissions.is_some_and(has_rw_deny) { return "read-only"; } match permissions.map(EffectivePermissions::fallback) { Some(Posture::Ask | Posture::Allow) | None => "workspace-write", Some(Posture::Deny) => "read-only", } } fn has_rw_deny(permissions: &EffectivePermissions) -> bool { permissions.rules().iter().any(|rule| { rule.effect() == Effect::Deny && matches!(rule.capability(), Capability::Write | Capability::Delete) }) } fn codex_network_access(network: Option) -> bool { matches!(network, Some(NetworkPolicy::Allow)) } fn append_unique_paths(target: &mut Vec, roots: &[String]) { for root in roots { append_unique_path(target, root); } } fn append_unique_path(target: &mut Vec, root: &str) { if root.is_empty() || target.iter().any(|existing| existing == root) { return; } target.push(root.to_owned()); } #[cfg(test)] mod tests { use super::*; use domain::permission::{resolve, PathScope, PermissionRule, PermissionSet}; fn effective(rules: Vec, fallback: Posture) -> EffectivePermissions { resolve(Some(&PermissionSet::new(rules, fallback)), None).unwrap() } fn codex(policy: StructuredProviderLaunchPolicy) -> (String, Vec) { let StructuredProviderLaunchPolicy::Codex { sandbox_mode, writable_roots, .. } = policy; (sandbox_mode, writable_roots) } #[test] fn unconfigured_policy_writes_project_and_deduplicated_managed_temp() { let (mode, roots) = codex(compile_codex_launch_policy( "/project", "/project/.ideai/run/main", None, None, &["/managed/tmp".to_owned(), "/project".to_owned()], )); assert_eq!(mode, "workspace-write"); assert_eq!(roots, vec!["/project", "/managed/tmp"]); } #[test] fn explicit_write_deny_never_gains_project_or_managed_temp_root() { let deny = PermissionRule::file( Capability::Write, Effect::Deny, PathScope::new(["**".to_owned()]).unwrap(), ) .unwrap(); let permissions = effective(vec![deny], Posture::Ask); let (mode, roots) = codex(compile_codex_launch_policy( "/project", "/run", Some(&permissions), None, &["/managed/tmp".to_owned()], )); assert_eq!(mode, "read-only"); assert!(roots.is_empty()); } #[test] fn partial_allow_exposes_only_its_computed_rw_grant() { let allow = PermissionRule::file( Capability::Write, Effect::Allow, PathScope::new(["generated/**".to_owned()]).unwrap(), ) .unwrap(); let permissions = effective(vec![allow], Posture::Deny); let (mode, roots) = codex(compile_codex_launch_policy( "/project", "/run", Some(&permissions), None, &[], )); assert_eq!(mode, "workspace-write"); assert_eq!(roots, vec!["/project/generated"]); } }