@@ -49,6 +49,7 @@ use codex_protocol::config_types::WebSearchToolConfig;
4949use codex_protocol:: config_types:: WindowsSandboxLevel ;
5050use codex_protocol:: models:: PermissionProfile ;
5151use codex_protocol:: openai_models:: ReasoningEffort ;
52+ use codex_protocol:: permissions:: NetworkSandboxPolicy ;
5253use codex_protocol:: protocol:: AskForApproval ;
5354use codex_protocol:: protocol:: SandboxPolicy ;
5455use codex_utils_absolute_path:: AbsolutePathBuf ;
@@ -641,15 +642,15 @@ pub struct GhostSnapshotToml {
641642}
642643
643644impl ConfigToml {
644- /// Derive the effective sandbox policy from the configuration.
645- pub async fn derive_sandbox_policy (
645+ /// Derive the effective permission profile from the configuration.
646+ pub async fn derive_permission_profile (
646647 & self ,
647648 sandbox_mode_override : Option < SandboxMode > ,
648649 profile_sandbox_mode : Option < SandboxMode > ,
649650 windows_sandbox_level : WindowsSandboxLevel ,
650651 active_project : Option < & ProjectConfig > ,
651652 permission_profile_constraint : Option < & crate :: Constrained < PermissionProfile > > ,
652- ) -> SandboxPolicy {
653+ ) -> PermissionProfile {
653654 let sandbox_mode_was_explicit = sandbox_mode_override. is_some ( )
654655 || profile_sandbox_mode. is_some ( )
655656 || self . sandbox_mode . is_some ( ) ;
@@ -677,50 +678,81 @@ impl ConfigToml {
677678 } )
678679 } )
679680 . unwrap_or_default ( ) ;
680- let mut sandbox_policy = match resolved_sandbox_mode {
681- SandboxMode :: ReadOnly => SandboxPolicy :: new_read_only_policy ( ) ,
681+ let workspace_write_unsupported = cfg ! ( target_os = "windows" )
682+ // If the experimental Windows sandbox is enabled, do not force a downgrade.
683+ && windows_sandbox_level == WindowsSandboxLevel :: Disabled
684+ && matches ! ( resolved_sandbox_mode, SandboxMode :: WorkspaceWrite ) ;
685+ let mut permission_profile = match resolved_sandbox_mode {
686+ SandboxMode :: ReadOnly => PermissionProfile :: read_only ( ) ,
682687 SandboxMode :: WorkspaceWrite => match self . sandbox_workspace_write . as_ref ( ) {
683688 Some ( SandboxWorkspaceWrite {
684689 writable_roots,
685690 network_access,
686691 exclude_tmpdir_env_var,
687692 exclude_slash_tmp,
688- } ) => SandboxPolicy :: WorkspaceWrite {
689- writable_roots : writable_roots. clone ( ) ,
690- network_access : * network_access,
691- exclude_tmpdir_env_var : * exclude_tmpdir_env_var,
692- exclude_slash_tmp : * exclude_slash_tmp,
693- } ,
694- None => SandboxPolicy :: new_workspace_write_policy ( ) ,
693+ } ) => {
694+ let network_policy = if * network_access {
695+ NetworkSandboxPolicy :: Enabled
696+ } else {
697+ NetworkSandboxPolicy :: Restricted
698+ } ;
699+ PermissionProfile :: workspace_write_with (
700+ writable_roots,
701+ network_policy,
702+ * exclude_tmpdir_env_var,
703+ * exclude_slash_tmp,
704+ )
705+ }
706+ None => PermissionProfile :: workspace_write ( ) ,
695707 } ,
696- SandboxMode :: DangerFullAccess => SandboxPolicy :: DangerFullAccess ,
697- } ;
698- let downgrade_workspace_write_if_unsupported = |policy : & mut SandboxPolicy | {
699- if cfg ! ( target_os = "windows" )
700- // If the experimental Windows sandbox is enabled, do not force a downgrade.
701- && windows_sandbox_level == WindowsSandboxLevel :: Disabled
702- && matches ! ( & * policy, SandboxPolicy :: WorkspaceWrite { .. } )
703- {
704- * policy = SandboxPolicy :: new_read_only_policy ( ) ;
705- }
708+ SandboxMode :: DangerFullAccess => PermissionProfile :: Disabled ,
706709 } ;
707- if matches ! ( resolved_sandbox_mode , SandboxMode :: WorkspaceWrite ) {
708- downgrade_workspace_write_if_unsupported ( & mut sandbox_policy ) ;
710+ if workspace_write_unsupported {
711+ permission_profile = PermissionProfile :: read_only ( ) ;
709712 }
710713 if !sandbox_mode_was_explicit
711714 && let Some ( constraint) = permission_profile_constraint
712- && let Err ( err) = constraint. can_set ( & PermissionProfile :: from_legacy_sandbox_policy (
713- & sandbox_policy,
714- ) )
715+ && let Err ( err) = constraint. can_set ( & permission_profile)
715716 {
716717 tracing:: warn!(
717718 error = %err,
718719 "default sandbox policy is disallowed by requirements; falling back to required default"
719720 ) ;
720- sandbox_policy = SandboxPolicy :: new_read_only_policy ( ) ;
721- downgrade_workspace_write_if_unsupported ( & mut sandbox_policy) ;
721+ permission_profile = PermissionProfile :: read_only ( ) ;
722+ }
723+ permission_profile
724+ }
725+
726+ /// Derive the legacy sandbox projection from configuration.
727+ ///
728+ /// New callers should use [`Self::derive_permission_profile`] instead.
729+ pub async fn derive_sandbox_policy (
730+ & self ,
731+ sandbox_mode_override : Option < SandboxMode > ,
732+ profile_sandbox_mode : Option < SandboxMode > ,
733+ windows_sandbox_level : WindowsSandboxLevel ,
734+ active_project : Option < & ProjectConfig > ,
735+ permission_profile_constraint : Option < & crate :: Constrained < PermissionProfile > > ,
736+ ) -> SandboxPolicy {
737+ let permission_profile = self
738+ . derive_permission_profile (
739+ sandbox_mode_override,
740+ profile_sandbox_mode,
741+ windows_sandbox_level,
742+ active_project,
743+ permission_profile_constraint,
744+ )
745+ . await ;
746+ match permission_profile. to_legacy_sandbox_policy ( Path :: new ( "/" ) ) {
747+ Ok ( sandbox_policy) => sandbox_policy,
748+ Err ( err) => {
749+ tracing:: warn!(
750+ error = %err,
751+ "derived permission profile cannot be represented as a legacy sandbox policy; falling back to read-only"
752+ ) ;
753+ SandboxPolicy :: new_read_only_policy ( )
754+ }
722755 }
723- sandbox_policy
724756 }
725757
726758 /// Resolves the cwd to an existing project, or returns None if ConfigToml
0 commit comments