Remove legacy sandbox-policy accessors from configuration (#61395)

## What changed

- Remove `Config::legacy_sandbox_policy` and `Permissions::legacy_sandbox_policy`.
- Update core and TUI tests to use permission profiles and filesystem/network policies directly, with explicit conversion where legacy wire formats are required.
- Report the active sandbox label in app-server resume override mismatch diagnostics.

GitOrigin-RevId: cbc5417944e23404523e69dc5516d66d6bfafefe
This commit is contained in:
ioanbudea-OAI
2026-11-13 23:03:49 +00:00
committed by copyberry
parent 5158e9780e
commit f626b8fde9
15 changed files with 170 additions and 277 deletions
@@ -212,9 +212,8 @@ fn collect_resume_override_mismatches(
} }
}; };
if !sandbox_matches { if !sandbox_matches {
let active_sandbox = config_snapshot.sandbox_policy();
mismatch_details.push(format!( mismatch_details.push(format!(
"sandbox requested={requested_sandbox:?} active={active_sandbox:?}" "sandbox requested={requested_sandbox:?} active={active_sandbox_label}"
)); ));
} }
} }
@@ -1191,7 +1191,6 @@ flag = false
async fn managed_preferences_expand_home_directory_in_workspace_write_roots() -> anyhow::Result<()> async fn managed_preferences_expand_home_directory_in_workspace_write_roots() -> anyhow::Result<()>
{ {
use base64::Engine; use base64::Engine;
use codex_protocol::protocol::SandboxPolicy;
let Some(home) = dirs::home_dir() else { let Some(home) = dirs::home_dir() else {
return Ok(()); return Ok(());
@@ -1230,23 +1229,15 @@ writable_roots = ["~/code"]
.await?; .await?;
let expected_root = AbsolutePathBuf::from_absolute_path(home.join("code"))?; let expected_root = AbsolutePathBuf::from_absolute_path(home.join("code"))?;
match &config.legacy_sandbox_policy() { assert_eq!(
SandboxPolicy::WorkspaceWrite { config.permissions.permission_profile(),
writable_roots, &codex_protocol::models::PermissionProfile::workspace_write_with(
network_access: true, &[expected_root],
exclude_tmpdir_env_var: true, codex_protocol::permissions::NetworkSandboxPolicy::Enabled,
exclude_slash_tmp: false, /*exclude_tmpdir_env_var*/ true,
} => { /*exclude_slash_tmp*/ false,
assert_eq!( )
writable_roots );
.iter()
.filter(|root| **root == expected_root)
.count(),
1,
);
}
other => panic!("expected workspace-write policy, got {other:?}"),
}
assert_eq!(config.permissions.active_permission_profile(), None); assert_eq!(config.permissions.active_permission_profile(), None);
Ok(()) Ok(())
+92 -151
View File
@@ -101,7 +101,6 @@ use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::FileSystemSpecialPath; use codex_protocol::permissions::FileSystemSpecialPath;
use codex_protocol::permissions::NetworkSandboxPolicy; use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::MultiAgentVersion; use codex_protocol::protocol::MultiAgentVersion;
use codex_protocol::protocol::NetworkAccess;
use codex_protocol::protocol::RealtimeVoice; use codex_protocol::protocol::RealtimeVoice;
use codex_protocol::protocol::SandboxPolicy; use codex_protocol::protocol::SandboxPolicy;
use codex_protocol_test_support::ContentItemTestExt; use codex_protocol_test_support::ContentItemTestExt;
@@ -2718,7 +2717,7 @@ async fn permissions_profiles_network_disabled_by_default_does_not_start_proxy()
} }
#[tokio::test] #[tokio::test]
async fn default_permissions_profile_populates_runtime_sandbox_policy() -> std::io::Result<()> { async fn default_permissions_profile_populates_runtime_permissions() -> std::io::Result<()> {
let codex_home = TempDir::new()?; let codex_home = TempDir::new()?;
let cwd = TempDir::new()?; let cwd = TempDir::new()?;
std::fs::create_dir_all(cwd.path().join("docs"))?; std::fs::create_dir_all(cwd.path().join("docs"))?;
@@ -2793,15 +2792,6 @@ async fn default_permissions_profile_populates_runtime_sandbox_policy() -> std::
}, },
]), ]),
); );
assert_eq!(
&config.legacy_sandbox_policy(),
&SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
}
);
assert!( assert!(
!config !config
.permissions .permissions
@@ -2904,10 +2894,6 @@ async fn permission_profile_override_populates_runtime_permissions() -> std::io:
permission_profile permission_profile
); );
assert_eq!(config.permissions.active_permission_profile(), None); assert_eq!(config.permissions.active_permission_profile(), None);
assert_eq!(
&config.legacy_sandbox_policy(),
&SandboxPolicy::DangerFullAccess
);
Ok(()) Ok(())
} }
@@ -3008,9 +2994,11 @@ extends = ":workspace"
extends: Some(BUILT_IN_PERMISSION_PROFILE_WORKSPACE.to_string()), extends: Some(BUILT_IN_PERMISSION_PROFILE_WORKSPACE.to_string()),
}) })
); );
assert_eq!( assert!(
config.legacy_sandbox_policy(), config
SandboxPolicy::new_workspace_write_policy() .permissions
.file_system_sandbox_policy()
.can_write_local_path_with_cwd(cwd.path(), cwd.path())
); );
Ok(()) Ok(())
} }
@@ -3215,12 +3203,6 @@ async fn permission_profile_override_preserves_managed_unrestricted_filesystem()
config.permissions.effective_permission_profile(), config.permissions.effective_permission_profile(),
permission_profile permission_profile
); );
assert_eq!(
&config.legacy_sandbox_policy(),
&SandboxPolicy::ExternalSandbox {
network_access: NetworkAccess::Restricted,
}
);
Ok(()) Ok(())
} }
@@ -3238,16 +3220,16 @@ async fn managed_unrestricted_permission_profile_still_enables_network_requireme
ConfigToml::default(), ConfigToml::default(),
ConfigOverrides { ConfigOverrides {
cwd: Some(cwd.path().to_path_buf()), cwd: Some(cwd.path().to_path_buf()),
permission_profile: Some(permission_profile), permission_profile: Some(permission_profile.clone()),
..Default::default() ..Default::default()
}, },
codex_home.abs(), codex_home.abs(),
) )
.await?; .await?;
assert_eq!( assert_eq!(
&config.legacy_sandbox_policy(), config.permissions.effective_permission_profile(),
&SandboxPolicy::DangerFullAccess, permission_profile
"the legacy projection is intentionally lossy for managed unrestricted profiles"
); );
let layers = config let layers = config
@@ -3276,8 +3258,7 @@ async fn managed_unrestricted_permission_profile_still_enables_network_requireme
} }
#[tokio::test] #[tokio::test]
async fn permission_profile_override_keeps_memories_root_out_of_legacy_projection() async fn permission_profile_override_keeps_memories_root_read_only() -> std::io::Result<()> {
-> std::io::Result<()> {
let codex_home = TempDir::new()?; let codex_home = TempDir::new()?;
let cwd = TempDir::new()?; let cwd = TempDir::new()?;
let permission_profile = PermissionProfile::from_runtime_permissions( let permission_profile = PermissionProfile::from_runtime_permissions(
@@ -3318,15 +3299,6 @@ async fn permission_profile_override_keeps_memories_root_out_of_legacy_projectio
.file_system_sandbox_policy() .file_system_sandbox_policy()
.can_write_local_path_with_cwd(memories_root.as_path(), cwd.path()) .can_write_local_path_with_cwd(memories_root.as_path(), cwd.path())
); );
assert_eq!(
&config.legacy_sandbox_policy(),
&SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
}
);
Ok(()) Ok(())
} }
@@ -4072,20 +4044,24 @@ async fn implicit_builtin_workspace_profile_preserves_sandbox_workspace_write_se
"implicit :workspace cannot be faithfully re-selected when it includes \ "implicit :workspace cannot be faithfully re-selected when it includes \
legacy sandbox_workspace_write settings" legacy sandbox_workspace_write settings"
); );
match config.legacy_sandbox_policy() { assert_eq!(
SandboxPolicy::WorkspaceWrite { policy
writable_roots, .entries
network_access, .into_iter()
exclude_tmpdir_env_var, .filter(|entry| matches!(
exclude_slash_tmp, &entry.path,
} => { FileSystemPath::Special {
assert!(writable_roots.contains(&extra_root)); value: FileSystemSpecialPath::Tmpdir | FileSystemSpecialPath::SlashTmp
assert!(network_access); }
assert!(exclude_tmpdir_env_var); ))
assert!(!exclude_slash_tmp); .collect::<Vec<_>>(),
} vec![FileSystemSandboxEntry::new(
sandbox_policy => panic!("expected workspace-write projection, got {sandbox_policy:?}"), FileSystemPath::Special {
} value: FileSystemSpecialPath::SlashTmp
},
FileSystemAccessMode::Write,
)]
);
Ok(()) Ok(())
} }
@@ -4341,15 +4317,6 @@ async fn permissions_profiles_allow_direct_write_roots_outside_workspace_root()
.file_system_sandbox_policy() .file_system_sandbox_policy()
.can_write_local_path_with_cwd(external_write_path.as_path(), cwd.path()) .can_write_local_path_with_cwd(external_write_path.as_path(), cwd.path())
); );
assert_eq!(
&config.legacy_sandbox_policy(),
&SandboxPolicy::WorkspaceWrite {
writable_roots: vec![external_write_path],
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
}
);
Ok(()) Ok(())
} }
@@ -4457,12 +4424,6 @@ async fn permissions_profiles_allow_unknown_special_paths() -> std::io::Result<(
missing_path_behavior: None, missing_path_behavior: None,
}]), }]),
); );
assert_eq!(
&config.legacy_sandbox_policy(),
&SandboxPolicy::ReadOnly {
network_access: false,
}
);
assert!( assert!(
config.startup_warnings.iter().any(|warning| warning.contains( config.startup_warnings.iter().any(|warning| warning.contains(
"Configured filesystem path `:future_special_path` is not recognized by this version of Codex and will be ignored." "Configured filesystem path `:future_special_path` is not recognized by this version of Codex and will be ignored."
@@ -4529,12 +4490,6 @@ async fn permissions_profiles_allow_missing_filesystem_with_warning() -> std::io
config.permissions.file_system_sandbox_policy(), config.permissions.file_system_sandbox_policy(),
FileSystemSandboxPolicy::restricted(Vec::new()) FileSystemSandboxPolicy::restricted(Vec::new())
); );
assert_eq!(
&config.legacy_sandbox_policy(),
&SandboxPolicy::ReadOnly {
network_access: false,
}
);
assert!( assert!(
config.startup_warnings.iter().any(|warning| warning.contains( config.startup_warnings.iter().any(|warning| warning.contains(
"Permissions profile `dev` does not define any recognized filesystem entries for this version of Codex." "Permissions profile `dev` does not define any recognized filesystem entries for this version of Codex."
@@ -4666,7 +4621,6 @@ async fn permissions_profiles_allow_network_enablement() -> std::io::Result<()>
config.permissions.network_sandbox_policy().is_enabled(), config.permissions.network_sandbox_policy().is_enabled(),
"expected network sandbox policy to be enabled", "expected network sandbox policy to be enabled",
); );
assert!(config.legacy_sandbox_policy().has_full_network_access());
Ok(()) Ok(())
} }
@@ -5241,7 +5195,10 @@ exclude_slash_tmp = true
) )
.await?; .await?;
let sandbox_policy = config.legacy_sandbox_policy(); let sandbox_policy = config
.permissions
.effective_permission_profile()
.to_legacy_sandbox_policy(cwd.path())?;
let file_system_policy = config.permissions.file_system_sandbox_policy(); let file_system_policy = config.permissions.file_system_sandbox_policy();
let network_policy = config.permissions.network_sandbox_policy(); let network_policy = config.permissions.network_sandbox_policy();
@@ -6406,25 +6363,27 @@ async fn add_dir_override_extends_workspace_writable_roots() -> std::io::Result<
let expected_backend = backend.abs(); let expected_backend = backend.abs();
if cfg!(target_os = "windows") { if cfg!(target_os = "windows") {
match &config.legacy_sandbox_policy() { assert_eq!(
SandboxPolicy::ReadOnly { .. } => {} config.permissions.permission_profile(),
other => panic!("expected read-only policy on Windows, got {other:?}"), &PermissionProfile::read_only()
} );
} else { } else {
match &config.legacy_sandbox_policy() { assert!(
SandboxPolicy::WorkspaceWrite { writable_roots, .. } => { config
assert_eq!( .permissions
writable_roots .file_system_sandbox_policy()
.iter() .can_write_local_path_with_cwd(expected_backend.as_path(), config.cwd.as_path())
.filter(|root| **root == expected_backend) );
.count(), let writable_roots = config.permissions.user_visible_workspace_roots();
1, assert_eq!(
"expected single writable root entry for {}", writable_roots
expected_backend.display() .iter()
); .filter(|root| **root == expected_backend)
} .count(),
other => panic!("expected workspace-write policy, got {other:?}"), 1,
} "expected single writable root entry for {}",
expected_backend.display()
);
} }
Ok(()) Ok(())
@@ -6546,10 +6505,10 @@ async fn workspace_write_includes_configured_writable_root_once_without_memories
.await?; .await?;
if cfg!(target_os = "windows") { if cfg!(target_os = "windows") {
match &config.legacy_sandbox_policy() { assert_eq!(
SandboxPolicy::ReadOnly { .. } => {} config.permissions.permission_profile(),
other => panic!("expected read-only policy on Windows, got {other:?}"), &PermissionProfile::read_only()
} );
} else { } else {
assert!( assert!(
!memories_root.exists(), !memories_root.exists(),
@@ -6557,21 +6516,23 @@ async fn workspace_write_includes_configured_writable_root_once_without_memories
memories_root.display() memories_root.display()
); );
let expected_memories_root = memories_root.abs(); let expected_memories_root = memories_root.abs();
match &config.legacy_sandbox_policy() { assert!(
SandboxPolicy::WorkspaceWrite { writable_roots, .. } => { config
assert!(!writable_roots.contains(&expected_memories_root)); .permissions
assert_eq!( .file_system_sandbox_policy()
writable_roots .can_write_local_path_with_cwd(writable_root.as_path(), config.cwd.as_path())
.iter() );
.filter(|root| **root == writable_root) let writable_roots = config.permissions.user_visible_workspace_roots();
.count(), assert!(!writable_roots.contains(&expected_memories_root));
1, assert_eq!(
"expected single writable root entry for {}", writable_roots
writable_root.display() .iter()
); .filter(|root| **root == writable_root)
} .count(),
other => panic!("expected workspace-write policy, got {other:?}"), 1,
} "expected single writable root entry for {}",
writable_root.display()
);
} }
Ok(()) Ok(())
@@ -6621,17 +6582,13 @@ async fn memory_tool_makes_memories_root_readable_without_creating_or_widening_w
); );
if cfg!(target_os = "windows") { if cfg!(target_os = "windows") {
match &config.legacy_sandbox_policy() { assert_eq!(
SandboxPolicy::ReadOnly { .. } => {} config.permissions.permission_profile(),
other => panic!("expected read-only policy on Windows, got {other:?}"), &PermissionProfile::read_only()
} );
} else { } else {
match &config.legacy_sandbox_policy() { let writable_roots = config.permissions.user_visible_workspace_roots();
SandboxPolicy::WorkspaceWrite { writable_roots, .. } => { assert!(!writable_roots.contains(&memories_root_abs));
assert!(!writable_roots.contains(&memories_root_abs));
}
other => panic!("expected workspace-write policy, got {other:?}"),
}
} }
Ok(()) Ok(())
@@ -11601,24 +11558,14 @@ async fn test_untrusted_project_gets_unless_trusted_approval_policy() -> anyhow:
"Expected UnlessTrusted approval policy for untrusted project" "Expected UnlessTrusted approval policy for untrusted project"
); );
// Verify that untrusted projects still get WorkspaceWrite sandbox (or ReadOnly on Windows) assert_eq!(
if cfg!(target_os = "windows") { config.permissions.permission_profile(),
assert!( &if cfg!(target_os = "windows") {
matches!( PermissionProfile::read_only()
&config.legacy_sandbox_policy(), } else {
SandboxPolicy::ReadOnly { .. } PermissionProfile::workspace_write()
), }
"Expected ReadOnly on Windows" );
);
} else {
assert!(
matches!(
&config.legacy_sandbox_policy(),
SandboxPolicy::WorkspaceWrite { .. }
),
"Expected WorkspaceWrite sandbox for untrusted project"
);
}
Ok(()) Ok(())
} }
@@ -11670,8 +11617,8 @@ async fn requirements_disallowing_default_sandbox_falls_back_to_required_default
.build() .build()
.await?; .await?;
assert_eq!( assert_eq!(
config.legacy_sandbox_policy(), config.permissions.effective_permission_profile(),
SandboxPolicy::new_read_only_policy() PermissionProfile::read_only()
); );
Ok(()) Ok(())
} }
@@ -11696,8 +11643,8 @@ async fn explicit_sandbox_mode_falls_back_when_disallowed_by_requirements() -> s
.build() .build()
.await?; .await?;
assert_eq!( assert_eq!(
config.legacy_sandbox_policy(), config.permissions.effective_permission_profile(),
SandboxPolicy::new_read_only_policy() PermissionProfile::read_only()
); );
Ok(()) Ok(())
} }
@@ -11974,8 +11921,6 @@ async fn permission_profile_override_falls_back_when_disallowed_by_requirements(
.build() .build()
.await?; .await?;
let expected_sandbox_policy = SandboxPolicy::new_read_only_policy();
assert_eq!(config.legacy_sandbox_policy(), expected_sandbox_policy);
assert_eq!( assert_eq!(
config.permissions.effective_permission_profile(), config.permissions.effective_permission_profile(),
PermissionProfile::read_only() PermissionProfile::read_only()
@@ -12110,10 +12055,6 @@ async fn permission_profile_override_preserves_split_write_roots() -> std::io::R
.file_system_sandbox_policy() .file_system_sandbox_policy()
.can_write_local_path_with_cwd(outside_root.as_path(), config.cwd.as_path()) .can_write_local_path_with_cwd(outside_root.as_path(), config.cwd.as_path())
); );
assert!(matches!(
&config.legacy_sandbox_policy(),
SandboxPolicy::WorkspaceWrite { .. }
));
assert_eq!( assert_eq!(
config.permissions.network_sandbox_policy(), config.permissions.network_sandbox_policy(),
NetworkSandboxPolicy::Restricted NetworkSandboxPolicy::Restricted
-16
View File
@@ -204,7 +204,6 @@ pub use codex_config::ConstraintError;
pub use codex_config::ConstraintResult; pub use codex_config::ConstraintResult;
pub use codex_config::LoaderOverrides; pub use codex_config::LoaderOverrides;
pub use codex_network_proxy::NetworkProxyAuditMetadata; pub use codex_network_proxy::NetworkProxyAuditMetadata;
use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile;
pub use codex_sandboxing::system_bwrap_warning; pub use codex_sandboxing::system_bwrap_warning;
pub use managed_features::ManagedFeatures; pub use managed_features::ManagedFeatures;
pub(crate) use metrics::emit_session_start_metrics; pub(crate) use metrics::emit_session_start_metrics;
@@ -497,12 +496,6 @@ impl Permissions {
self.permission_profile().network_sandbox_policy() self.permission_profile().network_sandbox_policy()
} }
/// Legacy compatibility projection derived from the canonical profile.
pub fn legacy_sandbox_policy(&self, cwd: &Path) -> SandboxPolicy {
let permission_profile = self.effective_permission_profile();
compatibility_sandbox_policy_for_permission_profile(&permission_profile, cwd)
}
/// Check whether a legacy sandbox policy can be applied to this permission /// Check whether a legacy sandbox policy can be applied to this permission
/// set after projecting it into the canonical permission profile. /// set after projecting it into the canonical permission profile.
pub fn can_set_legacy_sandbox_policy( pub fn can_set_legacy_sandbox_policy(
@@ -1794,10 +1787,6 @@ impl Config {
} }
} }
pub fn legacy_sandbox_policy(&self) -> SandboxPolicy {
self.permissions.legacy_sandbox_policy(self.cwd.as_path())
}
/// Combine runtime and profile roots without interpreting them on the current host. /// Combine runtime and profile roots without interpreting them on the current host.
pub fn effective_workspace_roots(&self) -> Vec<PathUri> { pub fn effective_workspace_roots(&self) -> Vec<PathUri> {
let mut workspace_roots = self let mut workspace_roots = self
@@ -3914,11 +3903,6 @@ impl Config {
) )
} else { } else {
let configured_network_proxy_config = NetworkProxyConfig::default(); let configured_network_proxy_config = NetworkProxyConfig::default();
// No named `[permissions]` profile is active, but permissions
// should still flow through the canonical profile representation.
// Derive the old `sandbox_mode` defaults as a profile first, then
// keep a legacy-compatible projection only for the remaining code
// paths that still speak `SandboxPolicy`.
let mut permission_profile = if let Some(PermissionConfig::Legacy { mode, workspace_write }) = permission_config { let mut permission_profile = if let Some(PermissionConfig::Legacy { mode, workspace_write }) = permission_config {
translate_legacy_permissions( translate_legacy_permissions(
mode, mode,
@@ -185,7 +185,7 @@ async fn accepted_input_applies_thread_settings() {
environments: Some(local_selections(config.cwd.clone()).into_requests()), environments: Some(local_selections(config.cwd.clone()).into_requests()),
approval_policy: Some(config.permissions.approval_policy.value()), approval_policy: Some(config.permissions.approval_policy.value()),
approvals_reviewer: Some(codex_config::types::ApprovalsReviewer::AutoReview), approvals_reviewer: Some(codex_config::types::ApprovalsReviewer::AutoReview),
sandbox_policy: Some(config.legacy_sandbox_policy()), permission_profile: Some(config.permissions.effective_permission_profile()),
summary: config.model_reasoning_summary, summary: config.model_reasoning_summary,
personality: config.personality, personality: config.personality,
collaboration_mode: Some(CollaborationMode { collaboration_mode: Some(CollaborationMode {
@@ -57,7 +57,6 @@ use codex_protocol::models::FunctionCallOutputBody;
use codex_protocol::models::PermissionProfile; use codex_protocol::models::PermissionProfile;
use codex_protocol::models::ResponseInputItem; use codex_protocol::models::ResponseInputItem;
use codex_protocol::models::ResponseItem; use codex_protocol::models::ResponseItem;
use codex_protocol::models::SandboxEnforcement;
use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::protocol::AgentStatus; use codex_protocol::protocol::AgentStatus;
use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::AskForApproval;
@@ -65,12 +64,9 @@ use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::FileSystemAccessMode; use codex_protocol::protocol::FileSystemAccessMode;
use codex_protocol::protocol::FileSystemPath; use codex_protocol::protocol::FileSystemPath;
use codex_protocol::protocol::FileSystemSandboxEntry; use codex_protocol::protocol::FileSystemSandboxEntry;
use codex_protocol::protocol::FileSystemSandboxPolicy;
use codex_protocol::protocol::InterAgentCommunication; use codex_protocol::protocol::InterAgentCommunication;
use codex_protocol::protocol::ItemCompletedEvent; use codex_protocol::protocol::ItemCompletedEvent;
use codex_protocol::protocol::NetworkSandboxPolicy;
use codex_protocol::protocol::Op; use codex_protocol::protocol::Op;
use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::SubAgentSource; use codex_protocol::protocol::SubAgentSource;
use codex_protocol::protocol::TurnAbortReason; use codex_protocol::protocol::TurnAbortReason;
@@ -2289,10 +2285,9 @@ async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() {
Arc::clone(&environment_manager), Arc::clone(&environment_manager),
); );
set_agent_control(&mut session, manager.agent_control()); set_agent_control(&mut session, manager.agent_control());
let expected_sandbox = turn.config.legacy_sandbox_policy(); let expected_profile = turn.config.permissions.effective_permission_profile();
#[allow(deprecated)] let (mut expected_file_system_sandbox_policy, expected_network_sandbox_policy) =
let mut expected_file_system_sandbox_policy = expected_profile.to_runtime_permissions();
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(&expected_sandbox, &turn.cwd);
expected_file_system_sandbox_policy expected_file_system_sandbox_policy
.entries .entries
.push(FileSystemSandboxEntry { .push(FileSystemSandboxEntry {
@@ -2308,9 +2303,8 @@ async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() {
}, },
missing_path_behavior: None, missing_path_behavior: None,
}); });
let expected_network_sandbox_policy = NetworkSandboxPolicy::from(&expected_sandbox);
let expected_permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement( let expected_permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(&expected_sandbox), expected_profile.enforcement(),
&expected_file_system_sandbox_policy, &expected_file_system_sandbox_policy,
expected_network_sandbox_policy, expected_network_sandbox_policy,
); );
@@ -2390,7 +2384,6 @@ async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() {
.expect("spawned agent thread should exist") .expect("spawned agent thread should exist")
.config_snapshot() .config_snapshot()
.await; .await;
assert_eq!(snapshot.sandbox_policy(), expected_sandbox);
assert_eq!(snapshot.approval_policy, AskForApproval::OnRequest); assert_eq!(snapshot.approval_policy, AskForApproval::OnRequest);
assert_eq!(snapshot.approvals_reviewer, ApprovalsReviewer::AutoReview); assert_eq!(snapshot.approvals_reviewer, ApprovalsReviewer::AutoReview);
assert_eq!(snapshot.permission_profile, expected_permission_profile); assert_eq!(snapshot.permission_profile, expected_permission_profile);
@@ -4555,29 +4548,6 @@ async fn tool_handlers_cascade_close_and_resume_and_keep_explicitly_closed_subtr
async fn build_agent_spawn_config_uses_captured_step_settings_and_turn_context_values( async fn build_agent_spawn_config_uses_captured_step_settings_and_turn_context_values(
parent_enabled: bool, parent_enabled: bool,
) { ) {
fn pick_allowed_sandbox_policy(
permissions: &crate::config::Permissions,
base: SandboxPolicy,
cwd: &std::path::Path,
) -> SandboxPolicy {
let candidates = [
SandboxPolicy::new_read_only_policy(),
SandboxPolicy::new_workspace_write_policy(),
SandboxPolicy::DangerFullAccess,
];
candidates
.into_iter()
.find(|candidate| {
if *candidate == base {
return false;
}
permissions
.can_set_legacy_sandbox_policy(candidate, cwd)
.is_ok()
})
.unwrap_or(base)
}
let (_session, mut turn) = make_session_and_context().await; let (_session, mut turn) = make_session_and_context().await;
update_turn_settings_for_test(&mut turn, |settings| { update_turn_settings_for_test(&mut turn, |settings| {
update_selected_settings_for_test(settings, |selected| { update_selected_settings_for_test(settings, |selected| {
@@ -4609,19 +4579,23 @@ async fn build_agent_spawn_config_uses_captured_step_settings_and_turn_context_v
} }
#[allow(deprecated)] #[allow(deprecated)]
let turn_cwd = turn.cwd.clone(); let turn_cwd = turn.cwd.clone();
let sandbox_policy = pick_allowed_sandbox_policy( let base_profile = turn.config.permissions.effective_permission_profile();
&turn.config.permissions, let permission_profile = [
turn.config.legacy_sandbox_policy(), PermissionProfile::read_only(),
turn_cwd.as_path(), PermissionProfile::workspace_write()
); .materialize_project_roots_with_workspace_roots(&[turn_cwd]),
let file_system_sandbox_policy = PermissionProfile::Disabled,
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(&sandbox_policy, &turn_cwd); ]
let network_sandbox_policy = NetworkSandboxPolicy::from(&sandbox_policy); .into_iter()
let permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement( .find(|candidate| {
SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy), *candidate != base_profile
&file_system_sandbox_policy, && turn
network_sandbox_policy, .config
); .permissions
.can_set_permission_profile(candidate)
.is_ok()
})
.unwrap_or(base_profile);
turn.initial_environments.environments.clear(); turn.initial_environments.environments.clear();
Arc::make_mut(&mut turn.config) Arc::make_mut(&mut turn.config)
.permissions .permissions
+20 -8
View File
@@ -781,8 +781,11 @@ async fn websocket_test_codex_shell_chain() -> Result<()> {
let mut builder = test_codex().with_windows_cmd_shell(); let mut builder = test_codex().with_windows_cmd_shell();
let test = builder.build_with_websocket_server(&server).await?; let test = builder.build_with_websocket_server(&server).await?;
test.submit_turn_with_policy("run the echo command", test.config.legacy_sandbox_policy()) test.submit_turn_with_permission_profile(
.await?; "run the echo command",
test.config.permissions.effective_permission_profile(),
)
.await?;
let connection = server.single_connection(); let connection = server.single_connection();
assert_eq!(connection.len(), 2); assert_eq!(connection.len(), 2);
@@ -847,8 +850,11 @@ async fn websocket_first_turn_uses_startup_prewarm_and_create(
}); });
} }
let test = builder.build_with_websocket_server(&server).await?; let test = builder.build_with_websocket_server(&server).await?;
test.submit_turn_with_policy("hello", test.config.legacy_sandbox_policy()) test.submit_turn_with_permission_profile(
.await?; "hello",
test.config.permissions.effective_permission_profile(),
)
.await?;
assert_eq!(server.handshakes().len(), 1); assert_eq!(server.handshakes().len(), 1);
let connection = server.single_connection(); let connection = server.single_connection();
@@ -942,8 +948,11 @@ async fn websocket_first_turn_handles_handshake_delay_with_startup_prewarm() ->
let mut builder = test_codex(); let mut builder = test_codex();
let test = builder.build_with_websocket_server(&server).await?; let test = builder.build_with_websocket_server(&server).await?;
test.submit_turn_with_policy("hello", test.config.legacy_sandbox_policy()) test.submit_turn_with_permission_profile(
.await?; "hello",
test.config.permissions.effective_permission_profile(),
)
.await?;
assert_eq!(server.handshakes().len(), 1); assert_eq!(server.handshakes().len(), 1);
let connection = server.single_connection(); let connection = server.single_connection();
@@ -999,8 +1008,11 @@ async fn websocket_v2_test_codex_shell_chain() -> Result<()> {
}); });
let test = builder.build_with_websocket_server(&server).await?; let test = builder.build_with_websocket_server(&server).await?;
test.submit_turn_with_policy("run the echo command", test.config.legacy_sandbox_policy()) test.submit_turn_with_permission_profile(
.await?; "run the echo command",
test.config.permissions.effective_permission_profile(),
)
.await?;
let connection = server.single_connection(); let connection = server.single_connection();
assert_eq!(connection.len(), 3); assert_eq!(connection.len(), 3);
+2 -2
View File
@@ -2843,7 +2843,7 @@ async fn user_turn_collaboration_mode_overrides_model_and_effort() -> anyhow::Re
.with_thread_settings(ThreadSettingsOverrides { .with_thread_settings(ThreadSettingsOverrides {
environments: Some(local_requests(config.cwd.clone())), environments: Some(local_requests(config.cwd.clone())),
approval_policy: Some(config.permissions.approval_policy.value()), approval_policy: Some(config.permissions.approval_policy.value()),
sandbox_policy: Some(config.legacy_sandbox_policy()), permission_profile: Some(config.permissions.effective_permission_profile()),
summary: Some( summary: Some(
config config
.model_reasoning_summary .model_reasoning_summary
@@ -3101,7 +3101,7 @@ async fn user_turn_explicit_reasoning_summary_overrides_model_catalog_default()
.with_thread_settings(ThreadSettingsOverrides { .with_thread_settings(ThreadSettingsOverrides {
environments: Some(local_requests(config.cwd.clone())), environments: Some(local_requests(config.cwd.clone())),
approval_policy: Some(config.permissions.approval_policy.value()), approval_policy: Some(config.permissions.approval_policy.value()),
sandbox_policy: Some(config.legacy_sandbox_policy()), permission_profile: Some(config.permissions.effective_permission_profile()),
summary: Some(ReasoningSummary::Concise), summary: Some(ReasoningSummary::Concise),
collaboration_mode: Some(CollaborationMode { collaboration_mode: Some(CollaborationMode {
mode: ModeKind::Default, mode: ModeKind::Default,
@@ -546,7 +546,7 @@ async fn collaboration_instructions_added_on_user_turn() -> Result<()> {
.with_thread_settings(ThreadSettingsOverrides { .with_thread_settings(ThreadSettingsOverrides {
environments: Some(local_requests(test.config.cwd.clone())), environments: Some(local_requests(test.config.cwd.clone())),
approval_policy: Some(test.config.permissions.approval_policy.value()), approval_policy: Some(test.config.permissions.approval_policy.value()),
sandbox_policy: Some(test.config.legacy_sandbox_policy()), permission_profile: Some(test.config.permissions.effective_permission_profile()),
summary: Some( summary: Some(
test.config test.config
.model_reasoning_summary .model_reasoning_summary
@@ -593,7 +593,7 @@ async fn collaboration_instructions_omitted_when_disabled() -> Result<()> {
.with_thread_settings(ThreadSettingsOverrides { .with_thread_settings(ThreadSettingsOverrides {
environments: Some(local_requests(test.config.cwd.clone())), environments: Some(local_requests(test.config.cwd.clone())),
approval_policy: Some(test.config.permissions.approval_policy.value()), approval_policy: Some(test.config.permissions.approval_policy.value()),
sandbox_policy: Some(test.config.legacy_sandbox_policy()), permission_profile: Some(test.config.permissions.effective_permission_profile()),
summary: Some( summary: Some(
test.config test.config
.model_reasoning_summary .model_reasoning_summary
@@ -651,7 +651,7 @@ async fn user_turn_overrides_collaboration_instructions_after_override() -> Resu
.with_thread_settings(ThreadSettingsOverrides { .with_thread_settings(ThreadSettingsOverrides {
environments: Some(local_requests(test.config.cwd.clone())), environments: Some(local_requests(test.config.cwd.clone())),
approval_policy: Some(test.config.permissions.approval_policy.value()), approval_policy: Some(test.config.permissions.approval_policy.value()),
sandbox_policy: Some(test.config.legacy_sandbox_policy()), permission_profile: Some(test.config.permissions.effective_permission_profile()),
summary: Some( summary: Some(
test.config test.config
.model_reasoning_summary .model_reasoning_summary
@@ -260,7 +260,7 @@ approvals_reviewer = "user"
"model": session.model, "modelProvider": session.model_provider_id, "cwd": session.cwd, "model": session.model, "modelProvider": session.model_provider_id, "cwd": session.cwd,
"approvalPolicy": app::AskForApproval::from(session.approval_policy), "approvalPolicy": app::AskForApproval::from(session.approval_policy),
"approvalsReviewer": app::ApprovalsReviewer::from(session.approvals_reviewer), "approvalsReviewer": app::ApprovalsReviewer::from(session.approvals_reviewer),
"sandbox": app::SandboxPolicy::from(test.config.legacy_sandbox_policy()), "sandbox": app::SandboxPolicy::from(test.config.permissions.effective_permission_profile().to_legacy_sandbox_policy(test.config.cwd.as_path())?),
}))?; }))?;
client.track_response( client.track_response(
/*connection_id*/ 1, /*connection_id*/ 1,
+5 -5
View File
@@ -916,7 +916,7 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a
let default_cwd = config.cwd.clone(); let default_cwd = config.cwd.clone();
let default_approval_policy = config.permissions.approval_policy.value(); let default_approval_policy = config.permissions.approval_policy.value();
let default_sandbox_policy = &config.legacy_sandbox_policy(); let default_permission_profile = config.permissions.effective_permission_profile();
let default_model = session_configured.model; let default_model = session_configured.model;
let default_effort = config.model_reasoning_effort.clone(); let default_effort = config.model_reasoning_effort.clone();
let default_summary = config.model_reasoning_summary; let default_summary = config.model_reasoning_summary;
@@ -930,7 +930,7 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a
.with_thread_settings(ThreadSettingsOverrides { .with_thread_settings(ThreadSettingsOverrides {
environments: Some(local_requests(default_cwd.clone())), environments: Some(local_requests(default_cwd.clone())),
approval_policy: Some(default_approval_policy), approval_policy: Some(default_approval_policy),
sandbox_policy: Some(default_sandbox_policy.clone()), permission_profile: Some(default_permission_profile.clone()),
summary: Some(default_summary.unwrap_or(ReasoningSummary::Auto)), summary: Some(default_summary.unwrap_or(ReasoningSummary::Auto)),
collaboration_mode: Some(CollaborationMode { collaboration_mode: Some(CollaborationMode {
mode: ModeKind::Default, mode: ModeKind::Default,
@@ -955,7 +955,7 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a
.with_thread_settings(ThreadSettingsOverrides { .with_thread_settings(ThreadSettingsOverrides {
environments: Some(local_requests(default_cwd.clone())), environments: Some(local_requests(default_cwd.clone())),
approval_policy: Some(default_approval_policy), approval_policy: Some(default_approval_policy),
sandbox_policy: Some(default_sandbox_policy.clone()), permission_profile: Some(default_permission_profile.clone()),
summary: Some(default_summary.unwrap_or(ReasoningSummary::Auto)), summary: Some(default_summary.unwrap_or(ReasoningSummary::Auto)),
collaboration_mode: Some(CollaborationMode { collaboration_mode: Some(CollaborationMode {
mode: ModeKind::Default, mode: ModeKind::Default,
@@ -1053,7 +1053,7 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu
let default_cwd = config.cwd.clone(); let default_cwd = config.cwd.clone();
let default_approval_policy = config.permissions.approval_policy.value(); let default_approval_policy = config.permissions.approval_policy.value();
let default_sandbox_policy = &config.legacy_sandbox_policy(); let default_permission_profile = config.permissions.effective_permission_profile();
let default_model = session_configured.model; let default_model = session_configured.model;
let default_effort = config.model_reasoning_effort.clone(); let default_effort = config.model_reasoning_effort.clone();
let default_summary = config.model_reasoning_summary; let default_summary = config.model_reasoning_summary;
@@ -1067,7 +1067,7 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu
.with_thread_settings(ThreadSettingsOverrides { .with_thread_settings(ThreadSettingsOverrides {
environments: Some(local_requests(default_cwd.clone())), environments: Some(local_requests(default_cwd.clone())),
approval_policy: Some(default_approval_policy), approval_policy: Some(default_approval_policy),
sandbox_policy: Some(default_sandbox_policy.clone()), permission_profile: Some(default_permission_profile.clone()),
summary: Some(default_summary.unwrap_or(ReasoningSummary::Auto)), summary: Some(default_summary.unwrap_or(ReasoningSummary::Auto)),
collaboration_mode: Some(CollaborationMode { collaboration_mode: Some(CollaborationMode {
mode: ModeKind::Default, mode: ModeKind::Default,
+4 -6
View File
@@ -5,9 +5,9 @@ use anyhow::Result;
use codex_config::LoaderOverrides; use codex_config::LoaderOverrides;
use codex_core::config::ConfigBuilder; use codex_core::config::ConfigBuilder;
use codex_core::config::ConfigOverrides; use codex_core::config::ConfigOverrides;
use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::SandboxPolicy;
use codex_utils_cargo_bin::cargo_bin; use codex_utils_cargo_bin::cargo_bin;
use core_test_support::responses; use core_test_support::responses;
use core_test_support::skip_if_remote; use core_test_support::skip_if_remote;
@@ -107,13 +107,11 @@ async fn forged_worktree_project_config_cannot_start_host_mcp() -> Result<()> {
} }
); );
assert_eq!( assert_eq!(
loaded.permissions.legacy_sandbox_policy(&checkout), loaded.permissions.effective_permission_profile(),
if registered { if registered {
SandboxPolicy::DangerFullAccess PermissionProfile::Disabled
} else { } else {
SandboxPolicy::ReadOnly { PermissionProfile::read_only()
network_access: false,
}
} }
); );
+5 -2
View File
@@ -7809,7 +7809,7 @@ fn agent_picker_item_name_snapshot() {
async fn side_fork_config_is_ephemeral_and_appends_developer_guardrails() { async fn side_fork_config_is_ephemeral_and_appends_developer_guardrails() {
let app = make_test_app().await; let app = make_test_app().await;
let original_approval_policy = app.config.permissions.approval_policy.value(); let original_approval_policy = app.config.permissions.approval_policy.value();
let original_sandbox_policy = app.config.legacy_sandbox_policy(); let original_permission_profile = app.config.permissions.effective_permission_profile();
let mut fork_config = app.side_fork_config(); let mut fork_config = app.side_fork_config();
crate::app_server_session::SideChatStorage::Temporary.configure_fork(&mut fork_config); crate::app_server_session::SideChatStorage::Temporary.configure_fork(&mut fork_config);
@@ -7819,7 +7819,10 @@ async fn side_fork_config_is_ephemeral_and_appends_developer_guardrails() {
fork_config.permissions.approval_policy.value(), fork_config.permissions.approval_policy.value(),
original_approval_policy original_approval_policy
); );
assert_eq!(fork_config.legacy_sandbox_policy(), original_sandbox_policy); assert_eq!(
fork_config.permissions.effective_permission_profile(),
original_permission_profile
);
let developer_instructions = fork_config let developer_instructions = fork_config
.developer_instructions .developer_instructions
.as_deref() .as_deref()
@@ -253,8 +253,12 @@ async fn misalignment_continuation_requires_current_review_and_submits_once() ->
.then(|| config.permissions.user_visible_workspace_roots().to_vec()), .then(|| config.permissions.user_visible_workspace_roots().to_vec()),
approval_policy: (!server_owned).then_some(AskForApproval::OnRequest), approval_policy: (!server_owned).then_some(AskForApproval::OnRequest),
approvals_reviewer: None, approvals_reviewer: None,
sandbox_policy: (!server_owned && !preserve) sandbox_policy: (!server_owned && !preserve).then(|| config
.then(|| config.legacy_sandbox_policy().into()), .permissions
.effective_permission_profile()
.to_legacy_sandbox_policy(config.cwd.as_path())
.expect("test permissions fit the legacy wire format")
.into()),
cyber_access_program: rollout_enabled.then_some(if daybreak { cyber_access_program: rollout_enabled.then_some(if daybreak {
CyberAccessProgram::DaybreakBlue.into() CyberAccessProgram::DaybreakBlue.into()
} else { } else {
@@ -2,8 +2,6 @@ use super::helpers::drain_insert_history_transcript;
use super::*; use super::*;
use crate::app_event::HistoryLookupResponse; use crate::app_event::HistoryLookupResponse;
use codex_app_server_protocol::ImageReference; use codex_app_server_protocol::ImageReference;
use codex_app_server_protocol::NetworkAccess;
use codex_app_server_protocol::SandboxPolicy;
use codex_app_server_protocol::ThreadItem; use codex_app_server_protocol::ThreadItem;
use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::models::FunctionCallOutputBody;
use codex_protocol::models::ManagedFileSystemPermissions; use codex_protocol::models::ManagedFileSystemPermissions;
@@ -686,10 +684,6 @@ async fn session_configured_syncs_widget_config_permissions_and_cwd() {
}, },
}; };
let expected_permission_profile = expected_app_server_permission_profile.clone(); let expected_permission_profile = expected_app_server_permission_profile.clone();
let expected_core_sandbox = expected_permission_profile
.to_legacy_sandbox_policy(expected_cwd.as_path())
.expect("permission profile should project to legacy sandbox policy");
let expected_sandbox = SandboxPolicy::from(expected_core_sandbox);
let configured = crate::session_state::ThreadSessionState { let configured = crate::session_state::ThreadSessionState {
read_only_history: false, read_only_history: false,
daybreak_enabled: false, daybreak_enabled: false,
@@ -721,8 +715,6 @@ async fn session_configured_syncs_widget_config_permissions_and_cwd() {
AskForApproval::from(chat.config_ref().permissions.approval_policy.value()), AskForApproval::from(chat.config_ref().permissions.approval_policy.value()),
AskForApproval::Never AskForApproval::Never
); );
let actual_sandbox = SandboxPolicy::from(chat.config_ref().legacy_sandbox_policy());
assert_eq!(&actual_sandbox, &expected_sandbox);
assert_eq!( assert_eq!(
chat.config_ref().permissions.effective_permission_profile(), chat.config_ref().permissions.effective_permission_profile(),
expected_app_server_permission_profile expected_app_server_permission_profile
@@ -818,9 +810,6 @@ async fn session_configured_external_sandbox_keeps_external_runtime_policy() {
network: NetworkSandboxPolicy::Restricted, network: NetworkSandboxPolicy::Restricted,
}; };
let expected_permission_profile = expected_app_server_permission_profile.clone(); let expected_permission_profile = expected_app_server_permission_profile.clone();
let expected_sandbox = SandboxPolicy::ExternalSandbox {
network_access: NetworkAccess::Restricted,
};
let configured = crate::session_state::ThreadSessionState { let configured = crate::session_state::ThreadSessionState {
read_only_history: false, read_only_history: false,
daybreak_enabled: false, daybreak_enabled: false,
@@ -848,8 +837,6 @@ async fn session_configured_external_sandbox_keeps_external_runtime_policy() {
chat.handle_thread_session(configured); chat.handle_thread_session(configured);
let actual_sandbox = SandboxPolicy::from(chat.config_ref().legacy_sandbox_policy());
assert_eq!(&actual_sandbox, &expected_sandbox);
assert_eq!( assert_eq!(
chat.config_ref().permissions.effective_permission_profile(), chat.config_ref().permissions.effective_permission_profile(),
expected_app_server_permission_profile expected_app_server_permission_profile