@@ -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,19 @@ 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 legacy sandbox config.
646+ ///
647+ /// Call this only after ruling out `default_permissions`: named
648+ /// `[permissions]` profiles must be compiled through the permissions
649+ /// profile pipeline, not reconstructed from `sandbox_mode`.
650+ pub async fn derive_permission_profile (
646651 & self ,
647652 sandbox_mode_override : Option < SandboxMode > ,
648653 profile_sandbox_mode : Option < SandboxMode > ,
649654 windows_sandbox_level : WindowsSandboxLevel ,
650655 active_project : Option < & ProjectConfig > ,
651656 permission_profile_constraint : Option < & crate :: Constrained < PermissionProfile > > ,
652- ) -> SandboxPolicy {
657+ ) -> PermissionProfile {
653658 let sandbox_mode_was_explicit = sandbox_mode_override. is_some ( )
654659 || profile_sandbox_mode. is_some ( )
655660 || self . sandbox_mode . is_some ( ) ;
@@ -677,50 +682,81 @@ impl ConfigToml {
677682 } )
678683 } )
679684 . unwrap_or_default ( ) ;
680- let mut sandbox_policy = match resolved_sandbox_mode {
681- SandboxMode :: ReadOnly => SandboxPolicy :: new_read_only_policy ( ) ,
685+ let workspace_write_unsupported = cfg ! ( target_os = "windows" )
686+ // If the experimental Windows sandbox is enabled, do not force a downgrade.
687+ && windows_sandbox_level == WindowsSandboxLevel :: Disabled
688+ && matches ! ( resolved_sandbox_mode, SandboxMode :: WorkspaceWrite ) ;
689+ let mut permission_profile = match resolved_sandbox_mode {
690+ SandboxMode :: ReadOnly => PermissionProfile :: read_only ( ) ,
682691 SandboxMode :: WorkspaceWrite => match self . sandbox_workspace_write . as_ref ( ) {
683692 Some ( SandboxWorkspaceWrite {
684693 writable_roots,
685694 network_access,
686695 exclude_tmpdir_env_var,
687696 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 ( ) ,
697+ } ) => {
698+ let network_policy = if * network_access {
699+ NetworkSandboxPolicy :: Enabled
700+ } else {
701+ NetworkSandboxPolicy :: Restricted
702+ } ;
703+ PermissionProfile :: workspace_write_with (
704+ writable_roots,
705+ network_policy,
706+ * exclude_tmpdir_env_var,
707+ * exclude_slash_tmp,
708+ )
709+ }
710+ None => PermissionProfile :: workspace_write ( ) ,
695711 } ,
696- SandboxMode :: DangerFullAccess => SandboxPolicy :: DangerFullAccess ,
712+ SandboxMode :: DangerFullAccess => PermissionProfile :: Disabled ,
697713 } ;
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- }
706- } ;
707- if matches ! ( resolved_sandbox_mode, SandboxMode :: WorkspaceWrite ) {
708- downgrade_workspace_write_if_unsupported ( & mut sandbox_policy) ;
714+ if workspace_write_unsupported {
715+ permission_profile = PermissionProfile :: read_only ( ) ;
709716 }
710717 if !sandbox_mode_was_explicit
711718 && let Some ( constraint) = permission_profile_constraint
712- && let Err ( err) = constraint. can_set ( & PermissionProfile :: from_legacy_sandbox_policy (
713- & sandbox_policy,
714- ) )
719+ && let Err ( err) = constraint. can_set ( & permission_profile)
715720 {
716721 tracing:: warn!(
717722 error = %err,
718723 "default sandbox policy is disallowed by requirements; falling back to required default"
719724 ) ;
720- sandbox_policy = SandboxPolicy :: new_read_only_policy ( ) ;
721- downgrade_workspace_write_if_unsupported ( & mut sandbox_policy) ;
725+ permission_profile = PermissionProfile :: read_only ( ) ;
726+ }
727+ permission_profile
728+ }
729+
730+ /// Derive the legacy sandbox projection from configuration.
731+ ///
732+ /// New callers should use [`Self::derive_permission_profile`] instead.
733+ pub async fn derive_sandbox_policy (
734+ & self ,
735+ sandbox_mode_override : Option < SandboxMode > ,
736+ profile_sandbox_mode : Option < SandboxMode > ,
737+ windows_sandbox_level : WindowsSandboxLevel ,
738+ active_project : Option < & ProjectConfig > ,
739+ permission_profile_constraint : Option < & crate :: Constrained < PermissionProfile > > ,
740+ ) -> SandboxPolicy {
741+ let permission_profile = self
742+ . derive_permission_profile (
743+ sandbox_mode_override,
744+ profile_sandbox_mode,
745+ windows_sandbox_level,
746+ active_project,
747+ permission_profile_constraint,
748+ )
749+ . await ;
750+ match permission_profile. to_legacy_sandbox_policy ( Path :: new ( "/" ) ) {
751+ Ok ( sandbox_policy) => sandbox_policy,
752+ Err ( err) => {
753+ tracing:: warn!(
754+ error = %err,
755+ "derived permission profile cannot be represented as a legacy sandbox policy; falling back to read-only"
756+ ) ;
757+ SandboxPolicy :: new_read_only_policy ( )
758+ }
722759 }
723- sandbox_policy
724760 }
725761
726762 /// Resolves the cwd to an existing project, or returns None if ConfigToml
0 commit comments