mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
permissions: derive compatibility policies from profiles (#19392)
## Why After #19391, `PermissionProfile` and the split filesystem/network policies could still be stored in parallel. That creates drift risk: a profile can preserve deny globs, external enforcement, or split filesystem entries while a cached projection silently loses those details. This PR makes the profile the runtime source and derives compatibility views from it. ## What Changed - Removes stored filesystem/network sandbox projections from `Permissions` and `SessionConfiguration`; their accessors now derive from the canonical `PermissionProfile`. - Derives legacy `SandboxPolicy` snapshots from profiles only where an older API still needs that field. - Updates MCP connection and elicitation state to track `PermissionProfile` instead of `SandboxPolicy` for auto-approval decisions. - Adds semantic filesystem-policy comparison so cwd changes can preserve richer profiles while still recognizing equivalent legacy projections independent of entry ordering. - Updates config/session tests to assert profile-derived projections instead of parallel stored fields. ## Verification - `cargo test -p codex-core direct_write_roots` - `cargo test -p codex-core runtime_roots_to_legacy_projection` - `cargo test -p codex-app-server requested_permissions_trust_project_uses_permission_profile_intent` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19392). * #19395 * #19394 * #19393 * __->__ #19392
This commit is contained in:
committed by
GitHub
Unverified
parent
4d7ce3447d
commit
deaa307fb2
@@ -2278,9 +2278,11 @@ impl CodexMessageProcessor {
|
||||
codex_protocol::models::PermissionProfile::from(permission_profile);
|
||||
let (mut file_system_sandbox_policy, network_sandbox_policy) =
|
||||
permission_profile.to_runtime_permissions();
|
||||
let configured_file_system_sandbox_policy =
|
||||
self.config.permissions.file_system_sandbox_policy();
|
||||
Self::preserve_configured_deny_read_restrictions(
|
||||
&mut file_system_sandbox_policy,
|
||||
&self.config.permissions.file_system_sandbox_policy,
|
||||
&configured_file_system_sandbox_policy,
|
||||
);
|
||||
let effective_permission_profile =
|
||||
codex_protocol::models::PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
|
||||
@@ -189,22 +189,23 @@ 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 args = create_seatbelt_command_args(CreateSeatbeltCommandArgsParams {
|
||||
command,
|
||||
file_system_sandbox_policy: &config.permissions.file_system_sandbox_policy,
|
||||
network_sandbox_policy: config.permissions.network_sandbox_policy,
|
||||
file_system_sandbox_policy: &file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
sandbox_policy_cwd: sandbox_policy_cwd.as_path(),
|
||||
enforce_managed_network: false,
|
||||
network: network.as_ref(),
|
||||
extra_allow_unix_sockets: allow_unix_sockets,
|
||||
});
|
||||
let network_policy = config.permissions.network_sandbox_policy;
|
||||
spawn_debug_sandbox_child(
|
||||
PathBuf::from("/usr/bin/sandbox-exec"),
|
||||
args,
|
||||
/*arg0*/ None,
|
||||
cwd.to_path_buf(),
|
||||
network_policy,
|
||||
network_sandbox_policy,
|
||||
env,
|
||||
|env_map| {
|
||||
env_map.insert(CODEX_SANDBOX_ENV_VAR.to_string(), "seatbelt".to_string());
|
||||
@@ -221,23 +222,24 @@ async fn run_command_under_sandbox(
|
||||
.codex_linux_sandbox_exe
|
||||
.expect("codex-linux-sandbox executable not found");
|
||||
let use_legacy_landlock = config.features.use_legacy_landlock();
|
||||
let file_system_sandbox_policy = config.permissions.file_system_sandbox_policy();
|
||||
let network_sandbox_policy = config.permissions.network_sandbox_policy();
|
||||
let args = create_linux_sandbox_command_args_for_policies(
|
||||
command,
|
||||
cwd.as_path(),
|
||||
config.permissions.sandbox_policy.get(),
|
||||
&config.permissions.file_system_sandbox_policy,
|
||||
config.permissions.network_sandbox_policy,
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
sandbox_policy_cwd.as_path(),
|
||||
use_legacy_landlock,
|
||||
/*allow_network_for_proxy*/ false,
|
||||
);
|
||||
let network_policy = config.permissions.network_sandbox_policy;
|
||||
spawn_debug_sandbox_child(
|
||||
codex_linux_sandbox_exe,
|
||||
args,
|
||||
Some("codex-linux-sandbox"),
|
||||
cwd.to_path_buf(),
|
||||
network_policy,
|
||||
network_sandbox_policy,
|
||||
env,
|
||||
|env_map| {
|
||||
if let Some(network) = network.as_ref() {
|
||||
@@ -715,17 +717,17 @@ mod tests {
|
||||
|
||||
assert!(config_uses_permission_profiles(&config));
|
||||
assert!(
|
||||
profile_config.permissions.file_system_sandbox_policy
|
||||
!= legacy_config.permissions.file_system_sandbox_policy,
|
||||
profile_config.permissions.file_system_sandbox_policy()
|
||||
!= legacy_config.permissions.file_system_sandbox_policy(),
|
||||
"test fixture should distinguish profile syntax from legacy sandbox_mode"
|
||||
);
|
||||
assert_eq!(
|
||||
config.permissions.file_system_sandbox_policy,
|
||||
profile_config.permissions.file_system_sandbox_policy,
|
||||
config.permissions.file_system_sandbox_policy(),
|
||||
profile_config.permissions.file_system_sandbox_policy(),
|
||||
);
|
||||
assert_ne!(
|
||||
config.permissions.file_system_sandbox_policy,
|
||||
legacy_config.permissions.file_system_sandbox_policy,
|
||||
config.permissions.file_system_sandbox_policy(),
|
||||
legacy_config.permissions.file_system_sandbox_policy(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -26,10 +26,10 @@ use codex_plugin::PluginCapabilitySummary;
|
||||
use codex_protocol::mcp::Resource;
|
||||
use codex_protocol::mcp::ResourceTemplate;
|
||||
use codex_protocol::mcp::Tool;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::McpAuthStatus;
|
||||
use codex_protocol::protocol::McpListToolsResponseEvent;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use rmcp::model::ReadResourceRequestParams;
|
||||
use rmcp::model::ReadResourceResult;
|
||||
use serde_json::Value;
|
||||
@@ -66,13 +66,18 @@ pub fn qualified_mcp_tool_name_prefix(server_name: &str) -> String {
|
||||
/// of being shown to the user.
|
||||
pub fn mcp_permission_prompt_is_auto_approved(
|
||||
approval_policy: AskForApproval,
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
permission_profile: &PermissionProfile,
|
||||
) -> bool {
|
||||
approval_policy == AskForApproval::Never
|
||||
&& matches!(
|
||||
sandbox_policy,
|
||||
SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. }
|
||||
)
|
||||
if approval_policy != AskForApproval::Never {
|
||||
return false;
|
||||
}
|
||||
|
||||
match permission_profile {
|
||||
PermissionProfile::Disabled | PermissionProfile::External { .. } => true,
|
||||
PermissionProfile::Managed { file_system, .. } => {
|
||||
file_system.to_sandbox_policy().has_full_disk_write_access()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// MCP runtime settings derived from `codex_core::config::Config`.
|
||||
@@ -229,7 +234,7 @@ pub async fn read_mcp_resource(
|
||||
&config.approval_policy,
|
||||
String::new(),
|
||||
tx_event,
|
||||
SandboxPolicy::new_read_only_policy(),
|
||||
PermissionProfile::default(),
|
||||
runtime_environment,
|
||||
config.codex_home.clone(),
|
||||
codex_apps_tools_cache_key(auth),
|
||||
@@ -294,7 +299,7 @@ pub async fn collect_mcp_server_status_snapshot_with_detail(
|
||||
&config.approval_policy,
|
||||
submit_id,
|
||||
tx_event,
|
||||
SandboxPolicy::new_read_only_policy(),
|
||||
PermissionProfile::default(),
|
||||
runtime_environment,
|
||||
config.codex_home.clone(),
|
||||
codex_apps_tools_cache_key(auth),
|
||||
|
||||
@@ -3,6 +3,9 @@ use codex_config::Constrained;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_plugin::AppConnectorId;
|
||||
use codex_plugin::PluginCapabilitySummary;
|
||||
use codex_protocol::models::ManagedFileSystemPermissions;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::HashMap;
|
||||
@@ -33,6 +36,34 @@ fn qualified_mcp_tool_name_prefix_sanitizes_server_names_without_lowercasing() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_prompt_auto_approval_honors_unrestricted_managed_profiles() {
|
||||
assert!(mcp_permission_prompt_is_auto_approved(
|
||||
AskForApproval::Never,
|
||||
&PermissionProfile::Managed {
|
||||
file_system: ManagedFileSystemPermissions::Unrestricted,
|
||||
network: NetworkSandboxPolicy::Enabled,
|
||||
},
|
||||
));
|
||||
assert!(mcp_permission_prompt_is_auto_approved(
|
||||
AskForApproval::Never,
|
||||
&PermissionProfile::Managed {
|
||||
file_system: ManagedFileSystemPermissions::Unrestricted,
|
||||
network: NetworkSandboxPolicy::Restricted,
|
||||
},
|
||||
));
|
||||
assert!(!mcp_permission_prompt_is_auto_approved(
|
||||
AskForApproval::Never,
|
||||
&PermissionProfile::read_only(),
|
||||
));
|
||||
assert!(!mcp_permission_prompt_is_auto_approved(
|
||||
AskForApproval::OnRequest,
|
||||
&PermissionProfile::Managed {
|
||||
file_system: ManagedFileSystemPermissions::Unrestricted,
|
||||
network: NetworkSandboxPolicy::Enabled,
|
||||
},
|
||||
));
|
||||
}
|
||||
#[test]
|
||||
fn tool_plugin_provenance_collects_app_and_mcp_sources() {
|
||||
let provenance = ToolPluginProvenance::from_capability_summaries(&[
|
||||
|
||||
@@ -334,15 +334,15 @@ fn can_auto_accept_elicitation(elicitation: &CreateElicitationRequestParams) ->
|
||||
struct ElicitationRequestManager {
|
||||
requests: Arc<Mutex<ResponderMap>>,
|
||||
approval_policy: Arc<StdMutex<AskForApproval>>,
|
||||
sandbox_policy: Arc<StdMutex<SandboxPolicy>>,
|
||||
permission_profile: Arc<StdMutex<PermissionProfile>>,
|
||||
}
|
||||
|
||||
impl ElicitationRequestManager {
|
||||
fn new(approval_policy: AskForApproval, sandbox_policy: SandboxPolicy) -> Self {
|
||||
fn new(approval_policy: AskForApproval, permission_profile: PermissionProfile) -> Self {
|
||||
Self {
|
||||
requests: Arc::new(Mutex::new(HashMap::new())),
|
||||
approval_policy: Arc::new(StdMutex::new(approval_policy)),
|
||||
sandbox_policy: Arc::new(StdMutex::new(sandbox_policy)),
|
||||
permission_profile: Arc::new(StdMutex::new(permission_profile)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,23 +364,23 @@ impl ElicitationRequestManager {
|
||||
fn make_sender(&self, server_name: String, tx_event: Sender<Event>) -> SendElicitation {
|
||||
let elicitation_requests = self.requests.clone();
|
||||
let approval_policy = self.approval_policy.clone();
|
||||
let sandbox_policy = self.sandbox_policy.clone();
|
||||
let permission_profile = self.permission_profile.clone();
|
||||
Box::new(move |id, elicitation| {
|
||||
let elicitation_requests = elicitation_requests.clone();
|
||||
let tx_event = tx_event.clone();
|
||||
let server_name = server_name.clone();
|
||||
let approval_policy = approval_policy.clone();
|
||||
let sandbox_policy = sandbox_policy.clone();
|
||||
let permission_profile = permission_profile.clone();
|
||||
async move {
|
||||
let approval_policy = approval_policy
|
||||
.lock()
|
||||
.map(|policy| *policy)
|
||||
.unwrap_or(AskForApproval::Never);
|
||||
let sandbox_policy = sandbox_policy
|
||||
let permission_profile = permission_profile
|
||||
.lock()
|
||||
.map(|policy| policy.clone())
|
||||
.unwrap_or_else(|_| SandboxPolicy::new_read_only_policy());
|
||||
if mcp_permission_prompt_is_auto_approved(approval_policy, &sandbox_policy)
|
||||
.map(|profile| profile.clone())
|
||||
.unwrap_or_default();
|
||||
if mcp_permission_prompt_is_auto_approved(approval_policy, &permission_profile)
|
||||
&& can_auto_accept_elicitation(&elicitation)
|
||||
{
|
||||
return Ok(ElicitationResponse {
|
||||
@@ -666,14 +666,14 @@ impl AsyncManagedClient {
|
||||
impl McpConnectionManager {
|
||||
pub fn new_uninitialized(
|
||||
approval_policy: &Constrained<AskForApproval>,
|
||||
sandbox_policy: &Constrained<SandboxPolicy>,
|
||||
permission_profile: &Constrained<PermissionProfile>,
|
||||
) -> Self {
|
||||
Self {
|
||||
clients: HashMap::new(),
|
||||
server_origins: HashMap::new(),
|
||||
elicitation_requests: ElicitationRequestManager::new(
|
||||
approval_policy.value(),
|
||||
sandbox_policy.get().clone(),
|
||||
permission_profile.get().clone(),
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -692,9 +692,9 @@ impl McpConnectionManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_sandbox_policy(&self, sandbox_policy: &SandboxPolicy) {
|
||||
if let Ok(mut policy) = self.elicitation_requests.sandbox_policy.lock() {
|
||||
*policy = sandbox_policy.clone();
|
||||
pub fn set_permission_profile(&self, permission_profile: PermissionProfile) {
|
||||
if let Ok(mut profile) = self.elicitation_requests.permission_profile.lock() {
|
||||
*profile = permission_profile;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -706,7 +706,7 @@ impl McpConnectionManager {
|
||||
approval_policy: &Constrained<AskForApproval>,
|
||||
submit_id: String,
|
||||
tx_event: Sender<Event>,
|
||||
initial_sandbox_policy: SandboxPolicy,
|
||||
initial_permission_profile: PermissionProfile,
|
||||
runtime_environment: McpRuntimeEnvironment,
|
||||
codex_home: PathBuf,
|
||||
codex_apps_tools_cache_key: CodexAppsToolsCacheKey,
|
||||
@@ -718,7 +718,7 @@ impl McpConnectionManager {
|
||||
let mut server_origins = HashMap::new();
|
||||
let mut join_set = JoinSet::new();
|
||||
let elicitation_requests =
|
||||
ElicitationRequestManager::new(approval_policy.value(), initial_sandbox_policy);
|
||||
ElicitationRequestManager::new(approval_policy.value(), initial_permission_profile);
|
||||
let tool_plugin_provenance = Arc::new(tool_plugin_provenance);
|
||||
let startup_submit_id = submit_id.clone();
|
||||
let codex_apps_auth_provider = auth
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::*;
|
||||
use codex_protocol::ToolName;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::protocol::GranularApprovalConfig;
|
||||
use codex_protocol::protocol::McpAuthStatus;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -179,9 +180,9 @@ fn elicitation_granular_policy_respects_never_and_config() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn full_access_auto_accepts_elicitation_with_empty_form_schema() {
|
||||
async fn disabled_permissions_auto_accept_elicitation_with_empty_form_schema() {
|
||||
let manager =
|
||||
ElicitationRequestManager::new(AskForApproval::Never, SandboxPolicy::DangerFullAccess);
|
||||
ElicitationRequestManager::new(AskForApproval::Never, PermissionProfile::Disabled);
|
||||
let (tx_event, _rx_event) = async_channel::bounded(1);
|
||||
let sender = manager.make_sender("server".to_string(), tx_event);
|
||||
|
||||
@@ -209,9 +210,9 @@ async fn full_access_auto_accepts_elicitation_with_empty_form_schema() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn full_access_does_not_auto_accept_elicitation_with_requested_fields() {
|
||||
async fn disabled_permissions_do_not_auto_accept_elicitation_with_requested_fields() {
|
||||
let manager =
|
||||
ElicitationRequestManager::new(AskForApproval::Never, SandboxPolicy::DangerFullAccess);
|
||||
ElicitationRequestManager::new(AskForApproval::Never, PermissionProfile::Disabled);
|
||||
let (tx_event, _rx_event) = async_channel::bounded(1);
|
||||
let sender = manager.make_sender("server".to_string(), tx_event);
|
||||
|
||||
@@ -627,8 +628,9 @@ async fn list_all_tools_uses_startup_snapshot_while_client_is_pending() {
|
||||
.boxed()
|
||||
.shared();
|
||||
let approval_policy = Constrained::allow_any(AskForApproval::OnFailure);
|
||||
let sandbox_policy = Constrained::allow_any(SandboxPolicy::new_read_only_policy());
|
||||
let mut manager = McpConnectionManager::new_uninitialized(&approval_policy, &sandbox_policy);
|
||||
let permission_profile = Constrained::allow_any(PermissionProfile::default());
|
||||
let mut manager =
|
||||
McpConnectionManager::new_uninitialized(&approval_policy, &permission_profile);
|
||||
manager.clients.insert(
|
||||
CODEX_APPS_MCP_SERVER_NAME.to_string(),
|
||||
AsyncManagedClient {
|
||||
@@ -654,8 +656,9 @@ async fn resolve_tool_info_accepts_canonical_namespaced_tool_names() {
|
||||
.boxed()
|
||||
.shared();
|
||||
let approval_policy = Constrained::allow_any(AskForApproval::OnFailure);
|
||||
let sandbox_policy = Constrained::allow_any(SandboxPolicy::new_read_only_policy());
|
||||
let mut manager = McpConnectionManager::new_uninitialized(&approval_policy, &sandbox_policy);
|
||||
let permission_profile = Constrained::allow_any(PermissionProfile::default());
|
||||
let mut manager =
|
||||
McpConnectionManager::new_uninitialized(&approval_policy, &permission_profile);
|
||||
manager.clients.insert(
|
||||
"rmcp".to_string(),
|
||||
AsyncManagedClient {
|
||||
@@ -689,8 +692,9 @@ async fn list_all_tools_blocks_while_client_is_pending_without_startup_snapshot(
|
||||
.boxed()
|
||||
.shared();
|
||||
let approval_policy = Constrained::allow_any(AskForApproval::OnFailure);
|
||||
let sandbox_policy = Constrained::allow_any(SandboxPolicy::new_read_only_policy());
|
||||
let mut manager = McpConnectionManager::new_uninitialized(&approval_policy, &sandbox_policy);
|
||||
let permission_profile = Constrained::allow_any(PermissionProfile::default());
|
||||
let mut manager =
|
||||
McpConnectionManager::new_uninitialized(&approval_policy, &permission_profile);
|
||||
manager.clients.insert(
|
||||
CODEX_APPS_MCP_SERVER_NAME.to_string(),
|
||||
AsyncManagedClient {
|
||||
@@ -712,8 +716,9 @@ async fn list_all_tools_does_not_block_when_startup_snapshot_cache_hit_is_empty(
|
||||
.boxed()
|
||||
.shared();
|
||||
let approval_policy = Constrained::allow_any(AskForApproval::OnFailure);
|
||||
let sandbox_policy = Constrained::allow_any(SandboxPolicy::new_read_only_policy());
|
||||
let mut manager = McpConnectionManager::new_uninitialized(&approval_policy, &sandbox_policy);
|
||||
let permission_profile = Constrained::allow_any(PermissionProfile::default());
|
||||
let mut manager =
|
||||
McpConnectionManager::new_uninitialized(&approval_policy, &permission_profile);
|
||||
manager.clients.insert(
|
||||
CODEX_APPS_MCP_SERVER_NAME.to_string(),
|
||||
AsyncManagedClient {
|
||||
@@ -744,8 +749,9 @@ async fn list_all_tools_uses_startup_snapshot_when_client_startup_fails() {
|
||||
.boxed()
|
||||
.shared();
|
||||
let approval_policy = Constrained::allow_any(AskForApproval::OnFailure);
|
||||
let sandbox_policy = Constrained::allow_any(SandboxPolicy::new_read_only_policy());
|
||||
let mut manager = McpConnectionManager::new_uninitialized(&approval_policy, &sandbox_policy);
|
||||
let permission_profile = Constrained::allow_any(PermissionProfile::default());
|
||||
let mut manager =
|
||||
McpConnectionManager::new_uninitialized(&approval_policy, &permission_profile);
|
||||
let startup_complete = Arc::new(std::sync::atomic::AtomicBool::new(true));
|
||||
manager.clients.insert(
|
||||
CODEX_APPS_MCP_SERVER_NAME.to_string(),
|
||||
|
||||
@@ -35,10 +35,11 @@ pub(crate) async fn apply_patch(
|
||||
file_system_sandbox_policy: &FileSystemSandboxPolicy,
|
||||
action: ApplyPatchAction,
|
||||
) -> InternalApplyPatchInvocation {
|
||||
let sandbox_policy = turn_context.sandbox_policy();
|
||||
match assess_patch_safety(
|
||||
&action,
|
||||
turn_context.approval_policy.value(),
|
||||
turn_context.sandbox_policy.get(),
|
||||
&sandbox_policy,
|
||||
file_system_sandbox_policy,
|
||||
&turn_context.cwd,
|
||||
turn_context.windows_sandbox_level,
|
||||
|
||||
@@ -776,7 +776,7 @@ async fn default_permissions_profile_populates_runtime_sandbox_policy() -> std::
|
||||
|
||||
let memories_root = codex_home.path().join("memories").abs();
|
||||
assert_eq!(
|
||||
config.permissions.file_system_sandbox_policy,
|
||||
config.permissions.file_system_sandbox_policy(),
|
||||
FileSystemSandboxPolicy::restricted(vec![
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
@@ -814,7 +814,7 @@ async fn default_permissions_profile_populates_runtime_sandbox_policy() -> std::
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
config.permissions.network_sandbox_policy,
|
||||
config.permissions.network_sandbox_policy(),
|
||||
NetworkSandboxPolicy::Restricted
|
||||
);
|
||||
Ok(())
|
||||
@@ -1082,7 +1082,7 @@ async fn project_root_glob_none_compiles_to_filesystem_pattern_entry() -> std::i
|
||||
assert_eq!(
|
||||
config
|
||||
.permissions
|
||||
.file_system_sandbox_policy
|
||||
.file_system_sandbox_policy()
|
||||
.glob_scan_max_depth,
|
||||
Some(2)
|
||||
);
|
||||
@@ -1092,7 +1092,7 @@ async fn project_root_glob_none_compiles_to_filesystem_pattern_entry() -> std::i
|
||||
assert!(
|
||||
config
|
||||
.permissions
|
||||
.file_system_sandbox_policy
|
||||
.file_system_sandbox_policy()
|
||||
.entries
|
||||
.contains(&FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
@@ -1104,7 +1104,7 @@ async fn project_root_glob_none_compiles_to_filesystem_pattern_entry() -> std::i
|
||||
assert!(
|
||||
!config
|
||||
.permissions
|
||||
.file_system_sandbox_policy
|
||||
.file_system_sandbox_policy()
|
||||
.entries
|
||||
.iter()
|
||||
.any(|entry| matches!(
|
||||
@@ -1304,7 +1304,7 @@ async fn permissions_profiles_allow_unknown_special_paths() -> std::io::Result<(
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
config.permissions.file_system_sandbox_policy,
|
||||
config.permissions.file_system_sandbox_policy(),
|
||||
FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::unknown(
|
||||
@@ -1350,7 +1350,7 @@ async fn permissions_profiles_allow_unknown_special_paths_with_nested_entries()
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
config.permissions.file_system_sandbox_policy,
|
||||
config.permissions.file_system_sandbox_policy(),
|
||||
FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::unknown(":future_special_path", Some("docs".into())),
|
||||
@@ -1377,7 +1377,7 @@ async fn permissions_profiles_allow_missing_filesystem_with_warning() -> std::io
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
config.permissions.file_system_sandbox_policy,
|
||||
config.permissions.file_system_sandbox_policy(),
|
||||
FileSystemSandboxPolicy::restricted(Vec::new())
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -1408,7 +1408,7 @@ async fn permissions_profiles_allow_empty_filesystem_with_warning() -> std::io::
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
config.permissions.file_system_sandbox_policy,
|
||||
config.permissions.file_system_sandbox_policy(),
|
||||
FileSystemSandboxPolicy::restricted(Vec::new())
|
||||
);
|
||||
assert!(
|
||||
@@ -1505,7 +1505,7 @@ async fn permissions_profiles_allow_network_enablement() -> std::io::Result<()>
|
||||
.await?;
|
||||
|
||||
assert!(
|
||||
config.permissions.network_sandbox_policy.is_enabled(),
|
||||
config.permissions.network_sandbox_policy().is_enabled(),
|
||||
"expected network sandbox policy to be enabled",
|
||||
);
|
||||
assert!(
|
||||
@@ -1800,20 +1800,20 @@ exclude_slash_tmp = true
|
||||
|
||||
let sandbox_policy = config.permissions.sandbox_policy.get();
|
||||
assert_eq!(
|
||||
config.permissions.file_system_sandbox_policy,
|
||||
config.permissions.file_system_sandbox_policy(),
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(sandbox_policy, cwd.path()),
|
||||
"case `{name}` should preserve filesystem semantics from legacy config"
|
||||
);
|
||||
assert_eq!(
|
||||
config.permissions.network_sandbox_policy,
|
||||
config.permissions.network_sandbox_policy(),
|
||||
NetworkSandboxPolicy::from(sandbox_policy),
|
||||
"case `{name}` should preserve network semantics from legacy config"
|
||||
);
|
||||
assert_eq!(
|
||||
config
|
||||
.permissions
|
||||
.file_system_sandbox_policy
|
||||
.to_legacy_sandbox_policy(config.permissions.network_sandbox_policy, cwd.path())
|
||||
.file_system_sandbox_policy()
|
||||
.to_legacy_sandbox_policy(config.permissions.network_sandbox_policy(), cwd.path())
|
||||
.unwrap_or_else(|err| panic!("case `{name}` should round-trip: {err}")),
|
||||
sandbox_policy.clone(),
|
||||
"case `{name}` should round-trip through split policies without drift"
|
||||
@@ -5449,10 +5449,6 @@ async fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {
|
||||
approval_policy: Constrained::allow_any(AskForApproval::Never),
|
||||
permission_profile: Constrained::allow_any(PermissionProfile::read_only()),
|
||||
sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
|
||||
file_system_sandbox_policy: FileSystemSandboxPolicy::from(
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
),
|
||||
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
|
||||
network: None,
|
||||
allow_login_shell: true,
|
||||
shell_environment_policy: ShellEnvironmentPolicy::default(),
|
||||
@@ -5647,10 +5643,6 @@ async fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
|
||||
approval_policy: Constrained::allow_any(AskForApproval::UnlessTrusted),
|
||||
permission_profile: Constrained::allow_any(PermissionProfile::read_only()),
|
||||
sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
|
||||
file_system_sandbox_policy: FileSystemSandboxPolicy::from(
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
),
|
||||
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
|
||||
network: None,
|
||||
allow_login_shell: true,
|
||||
shell_environment_policy: ShellEnvironmentPolicy::default(),
|
||||
@@ -5799,10 +5791,6 @@ async fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> {
|
||||
approval_policy: Constrained::allow_any(AskForApproval::OnFailure),
|
||||
permission_profile: Constrained::allow_any(PermissionProfile::read_only()),
|
||||
sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
|
||||
file_system_sandbox_policy: FileSystemSandboxPolicy::from(
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
),
|
||||
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
|
||||
network: None,
|
||||
allow_login_shell: true,
|
||||
shell_environment_policy: ShellEnvironmentPolicy::default(),
|
||||
@@ -5936,10 +5924,6 @@ async fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> {
|
||||
approval_policy: Constrained::allow_any(AskForApproval::OnFailure),
|
||||
permission_profile: Constrained::allow_any(PermissionProfile::read_only()),
|
||||
sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
|
||||
file_system_sandbox_policy: FileSystemSandboxPolicy::from(
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
),
|
||||
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
|
||||
network: None,
|
||||
allow_login_shell: true,
|
||||
shell_environment_policy: ShellEnvironmentPolicy::default(),
|
||||
|
||||
@@ -200,18 +200,6 @@ pub struct Permissions {
|
||||
/// Legacy projection retained while runtime call sites migrate to
|
||||
/// `permission_profile`.
|
||||
pub sandbox_policy: Constrained<SandboxPolicy>,
|
||||
/// Effective filesystem sandbox policy, including entries that cannot yet
|
||||
/// be fully represented by the legacy [`SandboxPolicy`] projection.
|
||||
///
|
||||
/// Runtime projection retained while callers migrate to
|
||||
/// `permission_profile`.
|
||||
pub file_system_sandbox_policy: FileSystemSandboxPolicy,
|
||||
/// Effective network sandbox policy split out from the legacy
|
||||
/// [`SandboxPolicy`] projection.
|
||||
///
|
||||
/// Runtime projection retained while callers migrate to
|
||||
/// `permission_profile`.
|
||||
pub network_sandbox_policy: NetworkSandboxPolicy,
|
||||
/// Effective network configuration applied to all spawned processes.
|
||||
pub network: Option<NetworkProxySpec>,
|
||||
/// Whether the model may request a login shell for shell-based tools.
|
||||
@@ -239,14 +227,14 @@ impl Permissions {
|
||||
self.permission_profile.get().clone()
|
||||
}
|
||||
|
||||
/// Effective filesystem sandbox policy projection.
|
||||
/// Effective filesystem sandbox policy derived from the canonical profile.
|
||||
pub fn file_system_sandbox_policy(&self) -> FileSystemSandboxPolicy {
|
||||
self.file_system_sandbox_policy.clone()
|
||||
self.permission_profile.get().file_system_sandbox_policy()
|
||||
}
|
||||
|
||||
/// Effective network sandbox policy projection.
|
||||
/// Effective network sandbox policy derived from the canonical profile.
|
||||
pub fn network_sandbox_policy(&self) -> NetworkSandboxPolicy {
|
||||
self.network_sandbox_policy
|
||||
self.permission_profile.get().network_sandbox_policy()
|
||||
}
|
||||
|
||||
/// Replace permissions from a legacy sandbox policy and keep every
|
||||
@@ -269,8 +257,6 @@ impl Permissions {
|
||||
|
||||
self.sandbox_policy.set(sandbox_policy)?;
|
||||
self.permission_profile.set(permission_profile)?;
|
||||
self.file_system_sandbox_policy = file_system_sandbox_policy;
|
||||
self.network_sandbox_policy = network_sandbox_policy;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -294,8 +280,6 @@ impl Permissions {
|
||||
|
||||
self.permission_profile.set(permission_profile)?;
|
||||
self.sandbox_policy.set(sandbox_policy)?;
|
||||
self.file_system_sandbox_policy = file_system_sandbox_policy;
|
||||
self.network_sandbox_policy = network_sandbox_policy;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -2493,8 +2477,6 @@ impl Config {
|
||||
approval_policy: constrained_approval_policy.value,
|
||||
permission_profile: constrained_permission_profile,
|
||||
sandbox_policy: constrained_sandbox_policy.value,
|
||||
file_system_sandbox_policy: effective_file_system_sandbox_policy,
|
||||
network_sandbox_policy: effective_network_sandbox_policy,
|
||||
network,
|
||||
allow_login_shell,
|
||||
shell_environment_policy,
|
||||
|
||||
@@ -89,7 +89,7 @@ async fn restricted_read_implicitly_allows_helper_executables() -> std::io::Resu
|
||||
let expected_zsh = AbsolutePathBuf::try_from(zsh_path)?;
|
||||
let expected_allowed_arg0_dir = AbsolutePathBuf::try_from(allowed_arg0_dir)?;
|
||||
let expected_sibling_arg0_dir = AbsolutePathBuf::try_from(sibling_arg0_dir)?;
|
||||
let policy = &config.permissions.file_system_sandbox_policy;
|
||||
let policy = config.permissions.file_system_sandbox_policy();
|
||||
|
||||
assert!(
|
||||
policy.can_read_path_with_cwd(expected_zsh.as_path(), &cwd),
|
||||
|
||||
@@ -17,7 +17,7 @@ use codex_connectors::DirectoryListResponse;
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
use codex_exec_server::EnvironmentManagerArgs;
|
||||
use codex_exec_server::ExecServerRuntimePaths;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_tools::DiscoverableTool;
|
||||
use rmcp::model::ToolAnnotations;
|
||||
use serde::Deserialize;
|
||||
@@ -274,7 +274,7 @@ pub async fn list_accessible_connectors_from_mcp_tools_with_environment_manager(
|
||||
&config.permissions.approval_policy,
|
||||
INITIAL_SUBMIT_ID.to_owned(),
|
||||
tx_event,
|
||||
SandboxPolicy::new_read_only_policy(),
|
||||
PermissionProfile::default(),
|
||||
McpRuntimeEnvironment::new(environment, config.cwd.to_path_buf()),
|
||||
config.codex_home.to_path_buf(),
|
||||
codex_apps_tools_cache_key(auth.as_ref()),
|
||||
|
||||
@@ -221,7 +221,7 @@ async fn should_install_mcp_dependencies(
|
||||
) -> bool {
|
||||
if mcp_permission_prompt_is_auto_approved(
|
||||
turn_context.approval_policy.value(),
|
||||
turn_context.sandbox_policy.get(),
|
||||
&turn_context.permission_profile(),
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -524,7 +524,7 @@ async fn augment_mcp_tool_request_meta_with_sandbox_state(
|
||||
|
||||
let sandbox_state = serde_json::to_value(SandboxState {
|
||||
permission_profile: Some(turn_context.permission_profile()),
|
||||
sandbox_policy: turn_context.sandbox_policy.get().clone(),
|
||||
sandbox_policy: turn_context.sandbox_policy(),
|
||||
codex_linux_sandbox_exe: turn_context.codex_linux_sandbox_exe.clone(),
|
||||
sandbox_cwd: turn_context.cwd.to_path_buf(),
|
||||
use_legacy_landlock: turn_context.features.use_legacy_landlock(),
|
||||
@@ -830,7 +830,7 @@ async fn maybe_request_mcp_tool_approval(
|
||||
) -> Option<McpToolApprovalDecision> {
|
||||
if mcp_permission_prompt_is_auto_approved(
|
||||
turn_context.approval_policy.value(),
|
||||
turn_context.sandbox_policy.get(),
|
||||
&turn_context.permission_profile(),
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ use codex_config::types::McpServerToolConfig;
|
||||
use codex_hooks::Hooks;
|
||||
use codex_hooks::HooksConfig;
|
||||
use codex_model_provider::create_model_provider;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use core_test_support::PathExt;
|
||||
use core_test_support::responses::ev_assistant_message;
|
||||
use core_test_support::responses::ev_completed;
|
||||
@@ -2162,10 +2162,7 @@ async fn full_access_mode_skips_arc_monitor_for_all_approval_modes() {
|
||||
.approval_policy
|
||||
.set(AskForApproval::Never)
|
||||
.expect("test setup should allow updating approval policy");
|
||||
turn_context
|
||||
.sandbox_policy
|
||||
.set(SandboxPolicy::DangerFullAccess)
|
||||
.expect("test setup should allow updating sandbox policy");
|
||||
turn_context.permission_profile = PermissionProfile::Disabled;
|
||||
let mut config = (*turn_context.config).clone();
|
||||
config.chatgpt_base_url = server.uri();
|
||||
turn_context.config = Arc::new(config);
|
||||
|
||||
@@ -432,6 +432,7 @@ mod phase2 {
|
||||
use codex_login::CodexAuth;
|
||||
use codex_protocol::AgentPath;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
@@ -482,8 +483,14 @@ mod phase2 {
|
||||
codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(codex_home.path())
|
||||
.expect("codex home is absolute");
|
||||
config.cwd = config.codex_home.clone();
|
||||
config.permissions.file_system_sandbox_policy = FileSystemSandboxPolicy::unrestricted();
|
||||
config.permissions.network_sandbox_policy = NetworkSandboxPolicy::Enabled;
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
&FileSystemSandboxPolicy::unrestricted(),
|
||||
NetworkSandboxPolicy::Enabled,
|
||||
);
|
||||
config
|
||||
.permissions
|
||||
.set_permission_profile(permission_profile, config.cwd.as_path())
|
||||
.expect("permissions are configurable");
|
||||
configure(&mut config);
|
||||
let config = Arc::new(config);
|
||||
|
||||
@@ -712,14 +719,16 @@ mod phase2 {
|
||||
memory_root(&harness.config.codex_home).as_path()
|
||||
);
|
||||
match &config_snapshot.sandbox_policy {
|
||||
SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots,
|
||||
network_access,
|
||||
..
|
||||
} => {
|
||||
SandboxPolicy::WorkspaceWrite { network_access, .. } => {
|
||||
assert!(!*network_access);
|
||||
let effective_writable_roots: Vec<_> = config_snapshot
|
||||
.sandbox_policy
|
||||
.get_writable_roots_with_cwd(config_snapshot.cwd.as_path())
|
||||
.into_iter()
|
||||
.map(|root| root.root)
|
||||
.collect();
|
||||
pretty_assertions::assert_eq!(
|
||||
writable_roots.as_slice(),
|
||||
effective_writable_roots.as_slice(),
|
||||
[memory_root(&harness.config.codex_home)],
|
||||
"consolidation subagent should only be able to write the memory root"
|
||||
);
|
||||
@@ -740,34 +749,35 @@ mod phase2 {
|
||||
"memory consolidation should not be registered in the root collab agent registry"
|
||||
);
|
||||
let turn_context = subagent.codex.session.new_default_turn().await;
|
||||
pretty_assertions::assert_eq!(
|
||||
turn_context.file_system_sandbox_policy,
|
||||
let file_system_sandbox_policy = turn_context.file_system_sandbox_policy();
|
||||
let legacy_file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
|
||||
&config_snapshot.sandbox_policy,
|
||||
config_snapshot.cwd.as_path(),
|
||||
);
|
||||
assert!(
|
||||
file_system_sandbox_policy.is_semantically_equivalent_to(
|
||||
&legacy_file_system_sandbox_policy,
|
||||
config_snapshot.cwd.as_path(),
|
||||
),
|
||||
"consolidation subagent split filesystem policy should match the memory-root legacy policy"
|
||||
);
|
||||
assert!(
|
||||
turn_context
|
||||
.file_system_sandbox_policy
|
||||
.can_write_path_with_cwd(
|
||||
memory_root(&harness.config.codex_home).as_path(),
|
||||
config_snapshot.cwd.as_path(),
|
||||
),
|
||||
file_system_sandbox_policy.can_write_path_with_cwd(
|
||||
memory_root(&harness.config.codex_home).as_path(),
|
||||
config_snapshot.cwd.as_path(),
|
||||
),
|
||||
"consolidation subagent should be able to write the memory root"
|
||||
);
|
||||
assert!(
|
||||
!turn_context
|
||||
.file_system_sandbox_policy
|
||||
.can_write_path_with_cwd(
|
||||
harness.config.codex_home.join("config.toml").as_path(),
|
||||
config_snapshot.cwd.as_path(),
|
||||
),
|
||||
!file_system_sandbox_policy.can_write_path_with_cwd(
|
||||
harness.config.codex_home.join("config.toml").as_path(),
|
||||
config_snapshot.cwd.as_path(),
|
||||
),
|
||||
"consolidation subagent should not inherit codex_home write access"
|
||||
);
|
||||
pretty_assertions::assert_eq!(
|
||||
turn_context.network_sandbox_policy,
|
||||
turn_context.network_sandbox_policy(),
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
"consolidation subagent split network policy should preserve no-network sandboxing"
|
||||
);
|
||||
|
||||
@@ -233,7 +233,7 @@ impl Session {
|
||||
&turn_context.approval_policy,
|
||||
turn_context.sub_id.clone(),
|
||||
self.get_tx_event(),
|
||||
turn_context.sandbox_policy.get().clone(),
|
||||
turn_context.permission_profile(),
|
||||
McpRuntimeEnvironment::new(
|
||||
turn_context
|
||||
.environment
|
||||
|
||||
@@ -125,7 +125,6 @@ use codex_rollout::state_db;
|
||||
use codex_rollout_trace::AgentResultTracePayload;
|
||||
use codex_rollout_trace::ThreadStartedTraceMetadata;
|
||||
use codex_rollout_trace::ThreadTraceContext;
|
||||
use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile;
|
||||
use codex_sandboxing::policy_transforms::intersect_permission_profiles;
|
||||
use codex_shell_command::parse_command::parse_command;
|
||||
use codex_terminal_detection::user_agent;
|
||||
@@ -606,9 +605,6 @@ impl Codex {
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: config.permissions.network_sandbox_policy,
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
@@ -939,8 +935,7 @@ impl Session {
|
||||
return;
|
||||
};
|
||||
|
||||
let spec = match spec
|
||||
.recompute_for_sandbox_policy(session_configuration.sandbox_policy.get())
|
||||
let spec = match spec.recompute_for_sandbox_policy(&session_configuration.sandbox_policy())
|
||||
{
|
||||
Ok(spec) => spec,
|
||||
Err(err) => {
|
||||
@@ -1301,8 +1296,9 @@ impl Session {
|
||||
};
|
||||
|
||||
let previous_cwd = state.session_configuration.cwd.clone();
|
||||
let sandbox_policy_changed =
|
||||
state.session_configuration.sandbox_policy != updated.sandbox_policy;
|
||||
let previous_sandbox_policy = state.session_configuration.sandbox_policy();
|
||||
let updated_sandbox_policy = updated.sandbox_policy();
|
||||
let sandbox_policy_changed = previous_sandbox_policy != updated_sandbox_policy;
|
||||
let next_cwd = updated.cwd.clone();
|
||||
let codex_home = updated.codex_home.clone();
|
||||
let session_source = updated.session_source.clone();
|
||||
|
||||
@@ -129,9 +129,6 @@ pub(super) async fn spawn_review_thread(
|
||||
personality: parent_turn_context.personality,
|
||||
approval_policy: parent_turn_context.approval_policy.clone(),
|
||||
permission_profile: parent_turn_context.permission_profile(),
|
||||
sandbox_policy: parent_turn_context.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: parent_turn_context.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: parent_turn_context.network_sandbox_policy,
|
||||
network: parent_turn_context.network.clone(),
|
||||
windows_sandbox_level: parent_turn_context.windows_sandbox_level,
|
||||
shell_environment_policy: parent_turn_context.shell_environment_policy.clone(),
|
||||
|
||||
@@ -67,7 +67,7 @@ async fn record_initial_history_resumed_bare_turn_context_does_not_hydrate_previ
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
sandbox_policy: turn_context.sandbox_policy.get().clone(),
|
||||
sandbox_policy: turn_context.sandbox_policy(),
|
||||
permission_profile: None,
|
||||
network: None,
|
||||
file_system_sandbox_policy: None,
|
||||
@@ -108,7 +108,7 @@ async fn record_initial_history_resumed_hydrates_previous_turn_settings_from_lif
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
sandbox_policy: turn_context.sandbox_policy.get().clone(),
|
||||
sandbox_policy: turn_context.sandbox_policy(),
|
||||
permission_profile: None,
|
||||
network: None,
|
||||
file_system_sandbox_policy: None,
|
||||
@@ -918,7 +918,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
sandbox_policy: turn_context.sandbox_policy.get().clone(),
|
||||
sandbox_policy: turn_context.sandbox_policy(),
|
||||
permission_profile: None,
|
||||
network: None,
|
||||
file_system_sandbox_policy: None,
|
||||
@@ -996,7 +996,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
sandbox_policy: turn_context.sandbox_policy.get().clone(),
|
||||
sandbox_policy: turn_context.sandbox_policy(),
|
||||
permission_profile: None,
|
||||
network: None,
|
||||
file_system_sandbox_policy: None,
|
||||
@@ -1027,7 +1027,7 @@ async fn record_initial_history_resumed_aborted_turn_without_id_clears_active_tu
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
sandbox_policy: turn_context.sandbox_policy.get().clone(),
|
||||
sandbox_policy: turn_context.sandbox_policy(),
|
||||
permission_profile: None,
|
||||
network: None,
|
||||
file_system_sandbox_policy: None,
|
||||
@@ -1142,7 +1142,7 @@ async fn record_initial_history_resumed_unmatched_abort_preserves_active_turn_fo
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
sandbox_policy: turn_context.sandbox_policy.get().clone(),
|
||||
sandbox_policy: turn_context.sandbox_policy(),
|
||||
permission_profile: None,
|
||||
network: None,
|
||||
file_system_sandbox_policy: None,
|
||||
@@ -1256,7 +1256,7 @@ async fn record_initial_history_resumed_trailing_incomplete_turn_compaction_clea
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
sandbox_policy: turn_context.sandbox_policy.get().clone(),
|
||||
sandbox_policy: turn_context.sandbox_policy(),
|
||||
permission_profile: None,
|
||||
network: None,
|
||||
file_system_sandbox_policy: None,
|
||||
@@ -1408,7 +1408,7 @@ async fn record_initial_history_resumed_replaced_incomplete_compacted_turn_clear
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
sandbox_policy: turn_context.sandbox_policy.get().clone(),
|
||||
sandbox_policy: turn_context.sandbox_policy(),
|
||||
permission_profile: None,
|
||||
network: None,
|
||||
file_system_sandbox_policy: None,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use super::*;
|
||||
use crate::goals::GoalRuntimeState;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSpecialPath;
|
||||
use tokio::sync::Semaphore;
|
||||
|
||||
/// Context for an initialized model agent
|
||||
@@ -58,12 +60,6 @@ pub(crate) struct SessionConfiguration {
|
||||
pub(super) approvals_reviewer: ApprovalsReviewer,
|
||||
/// Canonical permission profile for the session.
|
||||
pub(super) permission_profile: Constrained<PermissionProfile>,
|
||||
/// Legacy sandbox projection retained while lower-level callers migrate.
|
||||
pub(super) sandbox_policy: Constrained<SandboxPolicy>,
|
||||
/// Filesystem sandbox projection of `permission_profile`.
|
||||
pub(super) file_system_sandbox_policy: FileSystemSandboxPolicy,
|
||||
/// Network sandbox projection of `permission_profile`.
|
||||
pub(super) network_sandbox_policy: NetworkSandboxPolicy,
|
||||
pub(super) windows_sandbox_level: WindowsSandboxLevel,
|
||||
|
||||
/// Absolute working directory that should be treated as the *root* of the
|
||||
@@ -102,12 +98,25 @@ impl SessionConfiguration {
|
||||
}
|
||||
|
||||
pub(super) fn sandbox_policy(&self) -> SandboxPolicy {
|
||||
self.sandbox_policy.get().clone()
|
||||
self.permission_profile()
|
||||
.to_legacy_sandbox_policy(&self.cwd)
|
||||
.unwrap_or_else(|_| {
|
||||
let file_system_sandbox_policy = self.file_system_sandbox_policy();
|
||||
codex_sandboxing::compatibility_sandbox_policy_for_permission_profile(
|
||||
self.permission_profile.get(),
|
||||
&file_system_sandbox_policy,
|
||||
self.network_sandbox_policy(),
|
||||
&self.cwd,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(super) fn file_system_sandbox_policy(&self) -> FileSystemSandboxPolicy {
|
||||
self.file_system_sandbox_policy.clone()
|
||||
self.permission_profile.get().file_system_sandbox_policy()
|
||||
}
|
||||
|
||||
pub(super) fn network_sandbox_policy(&self) -> NetworkSandboxPolicy {
|
||||
self.permission_profile.get().network_sandbox_policy()
|
||||
}
|
||||
|
||||
pub(super) fn thread_config_snapshot(&self) -> ThreadConfigSnapshot {
|
||||
@@ -129,11 +138,30 @@ impl SessionConfiguration {
|
||||
|
||||
pub(crate) fn apply(&self, updates: &SessionSettingsUpdate) -> ConstraintResult<Self> {
|
||||
let mut next_configuration = self.clone();
|
||||
let file_system_policy_matches_legacy = self.file_system_sandbox_policy
|
||||
== FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
|
||||
self.sandbox_policy.get(),
|
||||
let current_sandbox_policy = self.sandbox_policy();
|
||||
let current_file_system_sandbox_policy = self.file_system_sandbox_policy();
|
||||
let current_network_sandbox_policy = self.network_sandbox_policy();
|
||||
let legacy_file_system_projection =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
|
||||
¤t_sandbox_policy,
|
||||
&self.cwd,
|
||||
¤t_file_system_sandbox_policy,
|
||||
);
|
||||
let file_system_policy_matches_legacy = current_file_system_sandbox_policy
|
||||
.is_semantically_equivalent_to(&legacy_file_system_projection, &self.cwd);
|
||||
let file_system_policy_has_rebindable_cwd_write = current_file_system_sandbox_policy
|
||||
.entries
|
||||
.iter()
|
||||
.any(|entry| {
|
||||
entry.access.can_write()
|
||||
&& matches!(
|
||||
&entry.path,
|
||||
FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::CurrentWorkingDirectory
|
||||
| FileSystemSpecialPath::ProjectRoots { subpath: None },
|
||||
}
|
||||
)
|
||||
});
|
||||
if let Some(collaboration_mode) = updates.collaboration_mode.clone() {
|
||||
next_configuration.collaboration_mode = collaboration_mode;
|
||||
}
|
||||
@@ -181,42 +209,41 @@ impl SessionConfiguration {
|
||||
if let Some(permission_profile) = updates.permission_profile.clone() {
|
||||
next_configuration.set_permission_profile_projection(
|
||||
permission_profile,
|
||||
Some(&self.file_system_sandbox_policy),
|
||||
Some(¤t_file_system_sandbox_policy),
|
||||
)?;
|
||||
} else if let Some(sandbox_policy) = updates.sandbox_policy.clone() {
|
||||
next_configuration.sandbox_policy.set(sandbox_policy)?;
|
||||
next_configuration.file_system_sandbox_policy =
|
||||
let file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
|
||||
next_configuration.sandbox_policy.get(),
|
||||
&sandbox_policy,
|
||||
&next_configuration.cwd,
|
||||
&self.file_system_sandbox_policy,
|
||||
¤t_file_system_sandbox_policy,
|
||||
);
|
||||
next_configuration.network_sandbox_policy =
|
||||
NetworkSandboxPolicy::from(next_configuration.sandbox_policy.get());
|
||||
let network_sandbox_policy = NetworkSandboxPolicy::from(&sandbox_policy);
|
||||
next_configuration.permission_profile.set(
|
||||
PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(
|
||||
next_configuration.sandbox_policy.get(),
|
||||
),
|
||||
&next_configuration.file_system_sandbox_policy,
|
||||
next_configuration.network_sandbox_policy,
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy),
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
),
|
||||
)?;
|
||||
} else if cwd_changed && file_system_policy_matches_legacy {
|
||||
} else if cwd_changed
|
||||
&& file_system_policy_matches_legacy
|
||||
&& file_system_policy_has_rebindable_cwd_write
|
||||
{
|
||||
// Preserve richer split policies across cwd-only updates; only
|
||||
// rederive when the session is already using the legacy bridge.
|
||||
next_configuration.file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
|
||||
next_configuration.sandbox_policy.get(),
|
||||
// rederive when the session is already using a structurally
|
||||
// cwd-bound legacy bridge.
|
||||
let file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
|
||||
¤t_sandbox_policy,
|
||||
&next_configuration.cwd,
|
||||
¤t_file_system_sandbox_policy,
|
||||
);
|
||||
next_configuration.permission_profile.set(
|
||||
PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(
|
||||
next_configuration.sandbox_policy.get(),
|
||||
),
|
||||
&next_configuration.file_system_sandbox_policy,
|
||||
next_configuration.network_sandbox_policy,
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(¤t_sandbox_policy),
|
||||
&file_system_sandbox_policy,
|
||||
current_network_sandbox_policy,
|
||||
),
|
||||
)?;
|
||||
}
|
||||
@@ -247,16 +274,7 @@ impl SessionConfiguration {
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
);
|
||||
let sandbox_policy = compatibility_sandbox_policy_for_permission_profile(
|
||||
&effective_permission_profile,
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
self.cwd.as_path(),
|
||||
);
|
||||
self.permission_profile.set(effective_permission_profile)?;
|
||||
self.sandbox_policy.set(sandbox_policy)?;
|
||||
self.file_system_sandbox_policy = file_system_sandbox_policy;
|
||||
self.network_sandbox_policy = network_sandbox_policy;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -487,7 +505,7 @@ impl Session {
|
||||
model: session_configuration.collaboration_mode.model().to_string(),
|
||||
provider_name: config.model_provider_id.clone(),
|
||||
approval_policy: session_configuration.approval_policy.value().to_string(),
|
||||
sandbox_policy: format!("{:?}", session_configuration.sandbox_policy.get()),
|
||||
sandbox_policy: format!("{:?}", session_configuration.sandbox_policy()),
|
||||
};
|
||||
let rollout_thread_trace = if matches!(
|
||||
session_configuration.session_source,
|
||||
@@ -768,7 +786,7 @@ impl Session {
|
||||
// setup is straightforward enough and performs well.
|
||||
mcp_connection_manager: Arc::new(RwLock::new(McpConnectionManager::new_uninitialized(
|
||||
&config.permissions.approval_policy,
|
||||
&config.permissions.sandbox_policy,
|
||||
&config.permissions.permission_profile,
|
||||
))),
|
||||
mcp_startup_cancellation_token: Mutex::new(CancellationToken::new()),
|
||||
unified_exec_manager: UnifiedExecProcessManager::new(
|
||||
@@ -847,6 +865,7 @@ impl Session {
|
||||
// Dispatch the SessionConfiguredEvent first and then report any errors.
|
||||
// If resuming, include converted initial messages in the payload so UIs can render them immediately.
|
||||
let initial_messages = initial_history.get_event_msgs();
|
||||
let session_sandbox_policy = session_configuration.sandbox_policy();
|
||||
let events = std::iter::once(Event {
|
||||
id: INITIAL_SUBMIT_ID.to_owned(),
|
||||
msg: EventMsg::SessionConfigured(SessionConfiguredEvent {
|
||||
@@ -858,7 +877,7 @@ impl Session {
|
||||
service_tier: session_configuration.service_tier,
|
||||
approval_policy: session_configuration.approval_policy.value(),
|
||||
approvals_reviewer: session_configuration.approvals_reviewer,
|
||||
sandbox_policy: session_configuration.sandbox_policy.get().clone(),
|
||||
sandbox_policy: session_sandbox_policy.clone(),
|
||||
permission_profile: Some(session_configuration.permission_profile()),
|
||||
cwd: session_configuration.cwd.clone(),
|
||||
reasoning_effort: session_configuration.collaboration_mode.reasoning_effort(),
|
||||
@@ -867,7 +886,7 @@ impl Session {
|
||||
initial_messages,
|
||||
network_proxy: session_network_proxy.filter(|_| {
|
||||
Self::managed_network_proxy_active_for_sandbox_policy(
|
||||
session_configuration.sandbox_policy.get(),
|
||||
&session_sandbox_policy,
|
||||
)
|
||||
}),
|
||||
rollout_path,
|
||||
@@ -901,7 +920,7 @@ impl Session {
|
||||
&session_configuration.approval_policy,
|
||||
INITIAL_SUBMIT_ID.to_owned(),
|
||||
tx_event.clone(),
|
||||
session_configuration.sandbox_policy.get().clone(),
|
||||
session_configuration.permission_profile(),
|
||||
McpRuntimeEnvironment::new(
|
||||
sess.services
|
||||
.environment_manager
|
||||
|
||||
@@ -38,6 +38,7 @@ use codex_protocol::models::FileSystemPermissions;
|
||||
use codex_protocol::models::FunctionCallOutputBody;
|
||||
use codex_protocol::models::FunctionCallOutputPayload;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::models::SandboxEnforcement;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
@@ -779,11 +780,19 @@ async fn new_turn_refreshes_managed_network_proxy_for_sandbox_change() -> anyhow
|
||||
let mut state = session.state.lock().await;
|
||||
let mut config = (*state.session_configuration.original_config_do_not_use).clone();
|
||||
config.permissions.network = Some(spec);
|
||||
config.permissions.sandbox_policy =
|
||||
codex_config::Constrained::allow_any(initial_policy.clone());
|
||||
let cwd = config.cwd.clone();
|
||||
config
|
||||
.permissions
|
||||
.set_legacy_sandbox_policy(initial_policy.clone(), cwd.as_path())
|
||||
.expect("test setup should allow sandbox policy");
|
||||
state.session_configuration.original_config_do_not_use = Arc::new(config);
|
||||
state.session_configuration.sandbox_policy =
|
||||
codex_config::Constrained::allow_any(initial_policy);
|
||||
state
|
||||
.session_configuration
|
||||
.permission_profile
|
||||
.set(PermissionProfile::from_legacy_sandbox_policy(
|
||||
&initial_policy,
|
||||
))
|
||||
.expect("test setup should allow permission profile");
|
||||
}
|
||||
session.services.network_proxy = Some(started_proxy);
|
||||
|
||||
@@ -829,6 +838,8 @@ async fn danger_full_access_turns_do_not_expose_managed_network_proxy() -> anyho
|
||||
let session = make_session_with_config(move |config| {
|
||||
config.permissions.sandbox_policy =
|
||||
codex_config::Constrained::allow_any(SandboxPolicy::DangerFullAccess);
|
||||
config.permissions.permission_profile =
|
||||
codex_config::Constrained::allow_any(PermissionProfile::Disabled);
|
||||
config.permissions.network = Some(network_spec);
|
||||
})
|
||||
.await?;
|
||||
@@ -892,6 +903,8 @@ async fn danger_full_access_tool_attempts_do_not_enforce_managed_network() -> an
|
||||
let session = make_session_with_config(move |config| {
|
||||
config.permissions.sandbox_policy =
|
||||
codex_config::Constrained::allow_any(SandboxPolicy::DangerFullAccess);
|
||||
config.permissions.permission_profile =
|
||||
codex_config::Constrained::allow_any(PermissionProfile::Disabled);
|
||||
config.permissions.network = Some(network_spec);
|
||||
|
||||
let layers = config
|
||||
@@ -1492,12 +1505,10 @@ async fn session_configured_reports_permission_profile_for_external_sandbox() ->
|
||||
let expected_sandbox_policy = sandbox_policy.clone();
|
||||
let mut builder = test_codex().with_config(move |config| {
|
||||
config.permissions.sandbox_policy = codex_config::Constrained::allow_any(sandbox_policy);
|
||||
config.permissions.file_system_sandbox_policy = FileSystemSandboxPolicy::external_sandbox();
|
||||
config.permissions.network_sandbox_policy = NetworkSandboxPolicy::Restricted;
|
||||
config.permissions.permission_profile =
|
||||
codex_config::Constrained::allow_any(PermissionProfile::from_runtime_permissions(
|
||||
&config.permissions.file_system_sandbox_policy,
|
||||
config.permissions.network_sandbox_policy,
|
||||
&FileSystemSandboxPolicy::external_sandbox(),
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
));
|
||||
});
|
||||
|
||||
@@ -1654,7 +1665,7 @@ async fn record_initial_history_forked_hydrates_previous_turn_settings() {
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
sandbox_policy: turn_context.sandbox_policy.get().clone(),
|
||||
sandbox_policy: turn_context.sandbox_policy(),
|
||||
permission_profile: None,
|
||||
network: None,
|
||||
file_system_sandbox_policy: None,
|
||||
@@ -2253,9 +2264,6 @@ async fn set_rate_limits_retains_previous_credits() {
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: config.permissions.network_sandbox_policy,
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
@@ -2358,9 +2366,6 @@ async fn set_rate_limits_updates_plan_type_when_present() {
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: config.permissions.network_sandbox_policy,
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
@@ -2808,9 +2813,6 @@ pub(crate) async fn make_session_configuration_for_tests() -> SessionConfigurati
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: config.permissions.network_sandbox_policy,
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
@@ -2840,7 +2842,7 @@ fn turn_environments_for_tests(
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn session_configuration_apply_preserves_split_file_system_policy_on_cwd_only_update() {
|
||||
async fn session_configuration_apply_preserves_profile_file_system_policy_on_cwd_only_update() {
|
||||
let mut session_configuration = make_session_configuration_for_tests().await;
|
||||
let workspace = tempfile::tempdir().expect("create temp dir");
|
||||
let project_root = workspace.path().join("project");
|
||||
@@ -2850,14 +2852,13 @@ async fn session_configuration_apply_preserves_split_file_system_policy_on_cwd_o
|
||||
let docs_dir = docs_dir.abs();
|
||||
|
||||
session_configuration.cwd = original_cwd.abs();
|
||||
session_configuration.sandbox_policy =
|
||||
codex_config::Constrained::allow_any(SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: Vec::new(),
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
});
|
||||
session_configuration.file_system_sandbox_policy = FileSystemSandboxPolicy::restricted(vec![
|
||||
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: Vec::new(),
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
};
|
||||
let file_system_sandbox_policy = FileSystemSandboxPolicy::restricted(vec![
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::CurrentWorkingDirectory,
|
||||
@@ -2869,6 +2870,14 @@ async fn session_configuration_apply_preserves_split_file_system_policy_on_cwd_o
|
||||
access: FileSystemAccessMode::Read,
|
||||
},
|
||||
]);
|
||||
let network_sandbox_policy = NetworkSandboxPolicy::from(&sandbox_policy);
|
||||
session_configuration.permission_profile = codex_config::Constrained::allow_any(
|
||||
PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy),
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
),
|
||||
);
|
||||
|
||||
let updated = session_configuration
|
||||
.apply(&SessionSettingsUpdate {
|
||||
@@ -2878,8 +2887,8 @@ async fn session_configuration_apply_preserves_split_file_system_policy_on_cwd_o
|
||||
.expect("cwd-only update should succeed");
|
||||
|
||||
assert_eq!(
|
||||
updated.file_system_sandbox_policy,
|
||||
session_configuration.file_system_sandbox_policy
|
||||
updated.file_system_sandbox_policy(),
|
||||
file_system_sandbox_policy
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2890,8 +2899,6 @@ async fn session_configuration_apply_permission_profile_preserves_existing_deny_
|
||||
session_configuration.cwd = cwd.path().abs();
|
||||
|
||||
let workspace_policy = SandboxPolicy::new_workspace_write_policy();
|
||||
session_configuration.sandbox_policy =
|
||||
codex_config::Constrained::allow_any(workspace_policy.clone());
|
||||
let deny_entry = FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: "**/*.env".to_string(),
|
||||
@@ -2905,7 +2912,13 @@ async fn session_configuration_apply_permission_profile_preserves_existing_deny_
|
||||
);
|
||||
existing_file_system_policy.glob_scan_max_depth = Some(2);
|
||||
existing_file_system_policy.entries.push(deny_entry.clone());
|
||||
session_configuration.file_system_sandbox_policy = existing_file_system_policy;
|
||||
session_configuration.permission_profile = codex_config::Constrained::allow_any(
|
||||
PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(&workspace_policy),
|
||||
&existing_file_system_policy,
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
),
|
||||
);
|
||||
|
||||
let requested_file_system_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
|
||||
&workspace_policy,
|
||||
@@ -2926,7 +2939,7 @@ async fn session_configuration_apply_permission_profile_preserves_existing_deny_
|
||||
expected_file_system_policy.glob_scan_max_depth = Some(2);
|
||||
expected_file_system_policy.entries.push(deny_entry);
|
||||
assert_eq!(
|
||||
updated.file_system_sandbox_policy,
|
||||
updated.file_system_sandbox_policy(),
|
||||
expected_file_system_policy
|
||||
);
|
||||
}
|
||||
@@ -3070,18 +3083,23 @@ async fn session_configuration_apply_rederives_legacy_file_system_policy_on_cwd_
|
||||
let project_root = workspace.path().join("project");
|
||||
let original_cwd = project_root.join("subdir");
|
||||
session_configuration.cwd = original_cwd.abs();
|
||||
session_configuration.sandbox_policy =
|
||||
codex_config::Constrained::allow_any(SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: Vec::new(),
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
});
|
||||
session_configuration.file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
|
||||
session_configuration.sandbox_policy.get(),
|
||||
&session_configuration.cwd,
|
||||
);
|
||||
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: Vec::new(),
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
};
|
||||
let file_system_sandbox_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
|
||||
&sandbox_policy,
|
||||
&session_configuration.cwd,
|
||||
);
|
||||
session_configuration.permission_profile = codex_config::Constrained::allow_any(
|
||||
PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy),
|
||||
&file_system_sandbox_policy,
|
||||
NetworkSandboxPolicy::from(&sandbox_policy),
|
||||
),
|
||||
);
|
||||
|
||||
let updated = session_configuration
|
||||
.apply(&SessionSettingsUpdate {
|
||||
@@ -3090,12 +3108,73 @@ async fn session_configuration_apply_rederives_legacy_file_system_policy_on_cwd_
|
||||
})
|
||||
.expect("cwd-only update should succeed");
|
||||
|
||||
let expected_file_system_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
|
||||
&updated.sandbox_policy(),
|
||||
&project_root,
|
||||
);
|
||||
assert!(
|
||||
updated
|
||||
.file_system_sandbox_policy()
|
||||
.is_semantically_equivalent_to(&expected_file_system_policy, &project_root),
|
||||
"cwd-only update should rederive the legacy filesystem policy for the new cwd"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn session_configuration_apply_preserves_absolute_cwd_write_root_on_cwd_update() {
|
||||
let mut session_configuration = make_session_configuration_for_tests().await;
|
||||
let workspace = tempfile::tempdir().expect("create temp dir");
|
||||
let original_cwd = workspace.path().join("repo-a");
|
||||
let next_cwd = workspace.path().join("repo-b");
|
||||
std::fs::create_dir_all(&original_cwd).expect("create original cwd");
|
||||
std::fs::create_dir_all(&next_cwd).expect("create next cwd");
|
||||
let original_cwd = original_cwd.abs();
|
||||
|
||||
session_configuration.cwd = original_cwd.clone();
|
||||
let file_system_sandbox_policy = FileSystemSandboxPolicy::restricted(vec![
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: original_cwd.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
},
|
||||
]);
|
||||
session_configuration.permission_profile = codex_config::Constrained::allow_any(
|
||||
PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::Managed,
|
||||
&file_system_sandbox_policy,
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
),
|
||||
);
|
||||
|
||||
let updated = session_configuration
|
||||
.apply(&SessionSettingsUpdate {
|
||||
cwd: Some(next_cwd.clone()),
|
||||
..Default::default()
|
||||
})
|
||||
.expect("cwd-only update should succeed");
|
||||
|
||||
assert_eq!(
|
||||
updated.file_system_sandbox_policy,
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
|
||||
updated.sandbox_policy.get(),
|
||||
&project_root,
|
||||
)
|
||||
updated.file_system_sandbox_policy(),
|
||||
file_system_sandbox_policy
|
||||
);
|
||||
assert!(
|
||||
updated
|
||||
.file_system_sandbox_policy()
|
||||
.can_write_path_with_cwd(original_cwd.as_path(), updated.cwd.as_path()),
|
||||
"absolute grant to the old cwd must remain writable"
|
||||
);
|
||||
assert!(
|
||||
!updated
|
||||
.file_system_sandbox_policy()
|
||||
.can_write_path_with_cwd(next_cwd.as_path(), updated.cwd.as_path()),
|
||||
"cwd-only update must not reinterpret an absolute old-cwd grant as :cwd"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3170,9 +3249,6 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: config.permissions.network_sandbox_policy,
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
@@ -3277,9 +3353,6 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: config.permissions.network_sandbox_policy,
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
@@ -3325,7 +3398,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
let services = SessionServices {
|
||||
mcp_connection_manager: Arc::new(RwLock::new(McpConnectionManager::new_uninitialized(
|
||||
&config.permissions.approval_policy,
|
||||
&config.permissions.sandbox_policy,
|
||||
&config.permissions.permission_profile,
|
||||
))),
|
||||
mcp_startup_cancellation_token: Mutex::new(CancellationToken::new()),
|
||||
unified_exec_manager: UnifiedExecProcessManager::new(
|
||||
@@ -3492,9 +3565,6 @@ async fn make_session_with_config_and_rx(
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: config.permissions.network_sandbox_policy,
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
@@ -4642,9 +4712,6 @@ where
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: config.permissions.network_sandbox_policy,
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
@@ -4690,7 +4757,7 @@ where
|
||||
let services = SessionServices {
|
||||
mcp_connection_manager: Arc::new(RwLock::new(McpConnectionManager::new_uninitialized(
|
||||
&config.permissions.approval_policy,
|
||||
&config.permissions.sandbox_policy,
|
||||
&config.permissions.permission_profile,
|
||||
))),
|
||||
mcp_startup_cancellation_token: Mutex::new(CancellationToken::new()),
|
||||
unified_exec_manager: UnifiedExecProcessManager::new(
|
||||
@@ -5572,7 +5639,7 @@ async fn build_initial_context_restates_realtime_start_when_reference_context_is
|
||||
|
||||
fn file_system_policy_with_unreadable_glob(turn_context: &TurnContext) -> FileSystemSandboxPolicy {
|
||||
let mut policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
|
||||
turn_context.sandbox_policy.get(),
|
||||
&turn_context.sandbox_policy(),
|
||||
&turn_context.cwd,
|
||||
);
|
||||
policy.entries.push(FileSystemSandboxEntry {
|
||||
@@ -5586,12 +5653,7 @@ fn file_system_policy_with_unreadable_glob(turn_context: &TurnContext) -> FileSy
|
||||
|
||||
#[tokio::test]
|
||||
async fn turn_context_item_omits_legacy_equivalent_file_system_sandbox_policy() {
|
||||
let (_session, mut turn_context) = make_session_and_context().await;
|
||||
turn_context.file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
|
||||
turn_context.sandbox_policy.get(),
|
||||
&turn_context.cwd,
|
||||
);
|
||||
let (_session, turn_context) = make_session_and_context().await;
|
||||
|
||||
let item = turn_context.to_turn_context_item();
|
||||
|
||||
@@ -5606,7 +5668,11 @@ async fn turn_context_item_omits_legacy_equivalent_file_system_sandbox_policy()
|
||||
async fn turn_context_item_stores_split_file_system_sandbox_policy_when_different() {
|
||||
let (_session, mut turn_context) = make_session_and_context().await;
|
||||
let file_system_sandbox_policy = file_system_policy_with_unreadable_glob(&turn_context);
|
||||
turn_context.file_system_sandbox_policy = file_system_sandbox_policy.clone();
|
||||
turn_context.permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
turn_context.permission_profile.enforcement(),
|
||||
&file_system_sandbox_policy,
|
||||
turn_context.network_sandbox_policy(),
|
||||
);
|
||||
|
||||
let item = turn_context.to_turn_context_item();
|
||||
|
||||
@@ -5743,7 +5809,11 @@ async fn record_context_updates_and_set_reference_context_item_persists_split_fi
|
||||
{
|
||||
let (mut session, mut turn_context) = make_session_and_context().await;
|
||||
let file_system_sandbox_policy = file_system_policy_with_unreadable_glob(&turn_context);
|
||||
turn_context.file_system_sandbox_policy = file_system_sandbox_policy.clone();
|
||||
turn_context.permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
turn_context.permission_profile.enforcement(),
|
||||
&file_system_sandbox_policy,
|
||||
turn_context.network_sandbox_policy(),
|
||||
);
|
||||
let rollout_path = attach_thread_persistence(&mut session).await;
|
||||
|
||||
session
|
||||
@@ -7674,7 +7744,6 @@ async fn rejects_escalated_permissions_when_policy_not_on_request() {
|
||||
use crate::tools::sandboxing::ExecApprovalRequirement;
|
||||
use crate::turn_diff_tracker::TurnDiffTracker;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use std::collections::HashMap;
|
||||
|
||||
let (session, mut turn_context_raw) = make_session_and_context().await;
|
||||
@@ -7761,23 +7830,18 @@ async fn rejects_escalated_permissions_when_policy_not_on_request() {
|
||||
// command. Force DangerFullAccess so this check stays focused on approval
|
||||
// policy rather than platform-specific sandbox behavior.
|
||||
let turn_context_mut = Arc::get_mut(&mut turn_context).expect("unique turn context Arc");
|
||||
turn_context_mut
|
||||
.sandbox_policy
|
||||
.set(SandboxPolicy::DangerFullAccess)
|
||||
.expect("test setup should allow updating sandbox policy");
|
||||
turn_context_mut.file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from(turn_context_mut.sandbox_policy.get());
|
||||
turn_context_mut.network_sandbox_policy =
|
||||
NetworkSandboxPolicy::from(turn_context_mut.sandbox_policy.get());
|
||||
turn_context_mut.permission_profile = PermissionProfile::Disabled;
|
||||
|
||||
let file_system_sandbox_policy = turn_context.file_system_sandbox_policy();
|
||||
let sandbox_policy = turn_context.sandbox_policy();
|
||||
let exec_approval_requirement = session
|
||||
.services
|
||||
.exec_policy
|
||||
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
|
||||
command: ¶ms.command,
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
sandbox_policy: turn_context.sandbox_policy.get(),
|
||||
file_system_sandbox_policy: &turn_context.file_system_sandbox_policy,
|
||||
sandbox_policy: &sandbox_policy,
|
||||
file_system_sandbox_policy: &file_system_sandbox_policy,
|
||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||
prefix_rule: None,
|
||||
})
|
||||
|
||||
@@ -25,8 +25,6 @@ use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::NetworkPermissions;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::models::function_call_output_content_items_to_text;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::request_permissions::PermissionGrantScope;
|
||||
use codex_protocol::request_permissions::RequestPermissionProfile;
|
||||
@@ -274,17 +272,7 @@ async fn guardian_allows_shell_additional_permissions_requests_past_policy_valid
|
||||
.features
|
||||
.enable(Feature::ExecPermissionApprovals)
|
||||
.expect("test setup should allow enabling request permissions");
|
||||
turn_context_raw
|
||||
.sandbox_policy
|
||||
.set(SandboxPolicy::DangerFullAccess)
|
||||
.expect("test setup should allow updating sandbox policy");
|
||||
// This test is about request-permissions validation, not managed sandbox
|
||||
// policy enforcement. Widen the derived sandbox policies directly so the
|
||||
// command runs without depending on a platform sandbox binary.
|
||||
turn_context_raw.file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from(turn_context_raw.sandbox_policy.get());
|
||||
turn_context_raw.network_sandbox_policy =
|
||||
NetworkSandboxPolicy::from(turn_context_raw.sandbox_policy.get());
|
||||
turn_context_raw.permission_profile = codex_protocol::models::PermissionProfile::Disabled;
|
||||
let mut config = (*turn_context_raw.config).clone();
|
||||
config.model_provider.base_url = Some(format!("{}/v1", server.uri()));
|
||||
let config = Arc::new(config);
|
||||
@@ -429,14 +417,7 @@ async fn strict_auto_review_turn_grant_forces_guardian_for_shell_policy_skip() {
|
||||
.approval_policy
|
||||
.set(AskForApproval::OnFailure)
|
||||
.expect("test setup should allow updating approval policy");
|
||||
turn_context_raw
|
||||
.sandbox_policy
|
||||
.set(SandboxPolicy::DangerFullAccess)
|
||||
.expect("test setup should allow updating sandbox policy");
|
||||
turn_context_raw.file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from(turn_context_raw.sandbox_policy.get());
|
||||
turn_context_raw.network_sandbox_policy =
|
||||
NetworkSandboxPolicy::from(turn_context_raw.sandbox_policy.get());
|
||||
turn_context_raw.permission_profile = codex_protocol::models::PermissionProfile::Disabled;
|
||||
let mut config = (*turn_context_raw.config).clone();
|
||||
config.approvals_reviewer = ApprovalsReviewer::User;
|
||||
config.model_provider.base_url = Some(format!("{}/v1", server.uri()));
|
||||
|
||||
@@ -692,13 +692,13 @@ async fn track_turn_resolved_config_analytics(
|
||||
session_source: thread_config.session_source,
|
||||
model: turn_context.model_info.slug.clone(),
|
||||
model_provider: turn_context.config.model_provider_id.clone(),
|
||||
sandbox_policy: turn_context.sandbox_policy.get().clone(),
|
||||
sandbox_policy: turn_context.sandbox_policy(),
|
||||
reasoning_effort: turn_context.reasoning_effort,
|
||||
reasoning_summary: Some(turn_context.reasoning_summary),
|
||||
service_tier: turn_context.config.service_tier,
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
approvals_reviewer: turn_context.config.approvals_reviewer,
|
||||
sandbox_network_access: turn_context.network_sandbox_policy.is_enabled(),
|
||||
sandbox_network_access: turn_context.network_sandbox_policy().is_enabled(),
|
||||
collaboration_mode: turn_context.collaboration_mode.mode,
|
||||
personality: turn_context.personality,
|
||||
is_first_turn,
|
||||
@@ -1871,7 +1871,7 @@ async fn try_run_sampling_request(
|
||||
feedback_tags!(
|
||||
model = turn_context.model_info.slug.clone(),
|
||||
approval_policy = turn_context.approval_policy.value(),
|
||||
sandbox_policy = turn_context.sandbox_policy.get(),
|
||||
sandbox_policy = &turn_context.sandbox_policy(),
|
||||
effort = turn_context.reasoning_effort,
|
||||
auth_mode = sess.services.auth_manager.auth_mode(),
|
||||
features = sess.features.enabled_features(),
|
||||
|
||||
@@ -3,6 +3,7 @@ use codex_model_provider::SharedModelProvider;
|
||||
use codex_model_provider::create_model_provider;
|
||||
use codex_protocol::models::AdditionalPermissionProfile;
|
||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||
use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile;
|
||||
use codex_sandboxing::policy_transforms::effective_file_system_sandbox_policy;
|
||||
use codex_sandboxing::policy_transforms::effective_network_sandbox_policy;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
@@ -73,9 +74,6 @@ pub(crate) struct TurnContext {
|
||||
pub(crate) personality: Option<Personality>,
|
||||
pub(crate) approval_policy: Constrained<AskForApproval>,
|
||||
pub(crate) permission_profile: PermissionProfile,
|
||||
pub(crate) sandbox_policy: Constrained<SandboxPolicy>,
|
||||
pub(crate) file_system_sandbox_policy: FileSystemSandboxPolicy,
|
||||
pub(crate) network_sandbox_policy: NetworkSandboxPolicy,
|
||||
pub(crate) network: Option<NetworkProxy>,
|
||||
pub(crate) windows_sandbox_level: WindowsSandboxLevel,
|
||||
pub(crate) shell_environment_policy: ShellEnvironmentPolicy,
|
||||
@@ -99,6 +97,25 @@ impl TurnContext {
|
||||
self.permission_profile.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn file_system_sandbox_policy(&self) -> FileSystemSandboxPolicy {
|
||||
self.permission_profile.file_system_sandbox_policy()
|
||||
}
|
||||
|
||||
pub(crate) fn network_sandbox_policy(&self) -> NetworkSandboxPolicy {
|
||||
self.permission_profile.network_sandbox_policy()
|
||||
}
|
||||
|
||||
pub(crate) fn sandbox_policy(&self) -> SandboxPolicy {
|
||||
let file_system_sandbox_policy = self.file_system_sandbox_policy();
|
||||
let network_sandbox_policy = self.network_sandbox_policy();
|
||||
compatibility_sandbox_policy_for_permission_profile(
|
||||
&self.permission_profile,
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
&self.cwd,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn model_context_window(&self) -> Option<i64> {
|
||||
let effective_context_window_percent = self.model_info.effective_context_window_percent;
|
||||
self.model_info
|
||||
@@ -210,9 +227,6 @@ impl TurnContext {
|
||||
personality: self.personality,
|
||||
approval_policy: self.approval_policy.clone(),
|
||||
permission_profile: self.permission_profile.clone(),
|
||||
sandbox_policy: self.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: self.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: self.network_sandbox_policy,
|
||||
network: self.network.clone(),
|
||||
windows_sandbox_level: self.windows_sandbox_level,
|
||||
shell_environment_policy: self.shell_environment_policy.clone(),
|
||||
@@ -246,12 +260,14 @@ impl TurnContext {
|
||||
&self,
|
||||
additional_permissions: Option<AdditionalPermissionProfile>,
|
||||
) -> FileSystemSandboxContext {
|
||||
let (base_file_system_sandbox_policy, base_network_sandbox_policy) =
|
||||
self.permission_profile.to_runtime_permissions();
|
||||
let file_system_sandbox_policy = effective_file_system_sandbox_policy(
|
||||
&self.file_system_sandbox_policy,
|
||||
&base_file_system_sandbox_policy,
|
||||
additional_permissions.as_ref(),
|
||||
);
|
||||
let network_sandbox_policy = effective_network_sandbox_policy(
|
||||
self.network_sandbox_policy,
|
||||
base_network_sandbox_policy,
|
||||
additional_permissions.as_ref(),
|
||||
);
|
||||
let permissions = PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
@@ -278,11 +294,12 @@ impl TurnContext {
|
||||
// this comparison and the legacy projection should go away.
|
||||
let legacy_file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
|
||||
self.sandbox_policy.get(),
|
||||
&self.sandbox_policy(),
|
||||
&self.cwd,
|
||||
);
|
||||
(self.file_system_sandbox_policy != legacy_file_system_sandbox_policy)
|
||||
.then(|| self.file_system_sandbox_policy.clone())
|
||||
let file_system_sandbox_policy = self.file_system_sandbox_policy();
|
||||
(file_system_sandbox_policy != legacy_file_system_sandbox_policy)
|
||||
.then_some(file_system_sandbox_policy)
|
||||
}
|
||||
|
||||
pub(crate) fn compact_prompt(&self) -> &str {
|
||||
@@ -299,7 +316,7 @@ impl TurnContext {
|
||||
current_date: self.current_date.clone(),
|
||||
timezone: self.timezone.clone(),
|
||||
approval_policy: self.approval_policy.value(),
|
||||
sandbox_policy: self.sandbox_policy.get().clone(),
|
||||
sandbox_policy: self.sandbox_policy(),
|
||||
permission_profile: Some(self.permission_profile()),
|
||||
network: self.turn_context_network_item(),
|
||||
file_system_sandbox_policy: self.non_legacy_file_system_sandbox_policy(),
|
||||
@@ -366,15 +383,11 @@ impl Session {
|
||||
per_turn_config.approvals_reviewer = session_configuration.approvals_reviewer;
|
||||
per_turn_config.permissions.permission_profile =
|
||||
session_configuration.permission_profile.clone();
|
||||
per_turn_config.permissions.sandbox_policy = session_configuration.sandbox_policy.clone();
|
||||
per_turn_config.permissions.file_system_sandbox_policy =
|
||||
session_configuration.file_system_sandbox_policy.clone();
|
||||
per_turn_config.permissions.network_sandbox_policy =
|
||||
session_configuration.network_sandbox_policy;
|
||||
let resolved_web_search_mode = resolve_web_search_mode_for_turn(
|
||||
&per_turn_config.web_search_mode,
|
||||
session_configuration.sandbox_policy.get(),
|
||||
);
|
||||
let sandbox_policy = session_configuration.sandbox_policy();
|
||||
per_turn_config.permissions.sandbox_policy =
|
||||
Constrained::allow_only(sandbox_policy.clone());
|
||||
let resolved_web_search_mode =
|
||||
resolve_web_search_mode_for_turn(&per_turn_config.web_search_mode, &sandbox_policy);
|
||||
if let Err(err) = per_turn_config
|
||||
.web_search_mode
|
||||
.set(resolved_web_search_mode)
|
||||
@@ -489,9 +502,6 @@ impl Session {
|
||||
personality: session_configuration.personality,
|
||||
approval_policy: session_configuration.approval_policy.clone(),
|
||||
permission_profile: session_configuration.permission_profile(),
|
||||
sandbox_policy: session_configuration.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: session_configuration.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: session_configuration.network_sandbox_policy,
|
||||
network,
|
||||
windows_sandbox_level: session_configuration.windows_sandbox_level,
|
||||
shell_environment_policy: per_turn_config.permissions.shell_environment_policy.clone(),
|
||||
@@ -528,8 +538,9 @@ impl Session {
|
||||
let turn_environments =
|
||||
self.resolve_turn_environments(&effective_environments)?;
|
||||
let previous_cwd = state.session_configuration.cwd.clone();
|
||||
let sandbox_policy_changed =
|
||||
state.session_configuration.sandbox_policy != next.sandbox_policy;
|
||||
let previous_sandbox_policy = state.session_configuration.sandbox_policy();
|
||||
let next_sandbox_policy = next.sandbox_policy();
|
||||
let sandbox_policy_changed = previous_sandbox_policy != next_sandbox_policy;
|
||||
let codex_home = next.codex_home.clone();
|
||||
let session_source = next.session_source.clone();
|
||||
state.session_configuration = next.clone();
|
||||
@@ -635,7 +646,8 @@ impl Session {
|
||||
{
|
||||
let mcp_connection_manager = self.services.mcp_connection_manager.read().await;
|
||||
mcp_connection_manager.set_approval_policy(&session_configuration.approval_policy);
|
||||
mcp_connection_manager.set_sandbox_policy(session_configuration.sandbox_policy.get());
|
||||
mcp_connection_manager
|
||||
.set_permission_profile(session_configuration.permission_profile());
|
||||
}
|
||||
|
||||
let model_info = self
|
||||
@@ -680,7 +692,7 @@ impl Session {
|
||||
.as_ref()
|
||||
.and_then(|started_proxy| {
|
||||
Self::managed_network_proxy_active_for_sandbox_policy(
|
||||
session_configuration.sandbox_policy.get(),
|
||||
&session_configuration.sandbox_policy(),
|
||||
)
|
||||
.then(|| started_proxy.proxy())
|
||||
}),
|
||||
|
||||
@@ -272,8 +272,9 @@ async fn effective_patch_permissions(
|
||||
session.granted_session_permissions().await.as_ref(),
|
||||
session.granted_turn_permissions().await.as_ref(),
|
||||
);
|
||||
let base_file_system_sandbox_policy = turn.file_system_sandbox_policy();
|
||||
let file_system_sandbox_policy = effective_file_system_sandbox_policy(
|
||||
&turn.file_system_sandbox_policy,
|
||||
&base_file_system_sandbox_policy,
|
||||
granted_permissions.as_ref(),
|
||||
);
|
||||
let effective_additional_permissions = apply_granted_turn_permissions(
|
||||
|
||||
@@ -99,7 +99,8 @@ impl ToolHandler for ListDirHandler {
|
||||
"dir_path must be an absolute path".to_string(),
|
||||
));
|
||||
}
|
||||
let read_deny_matcher = ReadDenyMatcher::new(&turn.file_system_sandbox_policy, &turn.cwd);
|
||||
let file_system_sandbox_policy = turn.file_system_sandbox_policy();
|
||||
let read_deny_matcher = ReadDenyMatcher::new(&file_system_sandbox_policy, &turn.cwd);
|
||||
if read_deny_matcher
|
||||
.as_ref()
|
||||
.is_some_and(|matcher| matcher.is_read_denied(&path))
|
||||
|
||||
@@ -37,6 +37,9 @@ use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_protocol::protocol::AgentStatus;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::FileSystemAccessMode;
|
||||
use codex_protocol::protocol::FileSystemPath;
|
||||
use codex_protocol::protocol::FileSystemSandboxEntry;
|
||||
use codex_protocol::protocol::FileSystemSandboxPolicy;
|
||||
use codex_protocol::protocol::InitialHistory;
|
||||
use codex_protocol::protocol::InterAgentCommunication;
|
||||
@@ -2074,21 +2077,6 @@ async fn multi_agent_v2_spawn_surfaces_task_name_validation_errors() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() {
|
||||
fn pick_allowed_sandbox_policy(
|
||||
constraint: &crate::config::Constrained<SandboxPolicy>,
|
||||
base: SandboxPolicy,
|
||||
) -> SandboxPolicy {
|
||||
let candidates = [
|
||||
SandboxPolicy::DangerFullAccess,
|
||||
SandboxPolicy::new_workspace_write_policy(),
|
||||
SandboxPolicy::new_read_only_policy(),
|
||||
];
|
||||
candidates
|
||||
.into_iter()
|
||||
.find(|candidate| *candidate != base && constraint.can_set(candidate).is_ok())
|
||||
.unwrap_or(base)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SpawnAgentResult {
|
||||
agent_id: String,
|
||||
@@ -2098,12 +2086,17 @@ async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() {
|
||||
let (mut session, mut turn) = make_session_and_context().await;
|
||||
let manager = thread_manager();
|
||||
session.services.agent_control = manager.agent_control();
|
||||
let expected_sandbox = pick_allowed_sandbox_policy(
|
||||
&turn.config.permissions.sandbox_policy,
|
||||
turn.config.permissions.sandbox_policy.get().clone(),
|
||||
);
|
||||
let expected_file_system_sandbox_policy =
|
||||
let expected_sandbox = turn.config.permissions.sandbox_policy.get().clone();
|
||||
let mut expected_file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(&expected_sandbox, &turn.cwd);
|
||||
expected_file_system_sandbox_policy
|
||||
.entries
|
||||
.push(FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: "**/.env".to_string(),
|
||||
},
|
||||
access: FileSystemAccessMode::None,
|
||||
});
|
||||
let expected_network_sandbox_policy = NetworkSandboxPolicy::from(&expected_sandbox);
|
||||
let expected_permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(&expected_sandbox),
|
||||
@@ -2113,16 +2106,11 @@ async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() {
|
||||
turn.approval_policy
|
||||
.set(AskForApproval::OnRequest)
|
||||
.expect("approval policy should be set");
|
||||
turn.sandbox_policy
|
||||
.set(expected_sandbox.clone())
|
||||
.expect("sandbox policy should be set");
|
||||
turn.file_system_sandbox_policy = expected_file_system_sandbox_policy.clone();
|
||||
turn.network_sandbox_policy = expected_network_sandbox_policy;
|
||||
turn.permission_profile = expected_permission_profile.clone();
|
||||
assert_ne!(
|
||||
expected_sandbox,
|
||||
turn.config.permissions.sandbox_policy.get().clone(),
|
||||
"test requires a runtime sandbox override that differs from base config"
|
||||
expected_permission_profile,
|
||||
turn.config.permissions.permission_profile(),
|
||||
"test requires a runtime profile override that differs from base config"
|
||||
);
|
||||
|
||||
let invocation = invocation(
|
||||
@@ -2164,11 +2152,11 @@ async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() {
|
||||
.expect("spawned agent thread should exist");
|
||||
let child_turn = child_thread.codex.session.new_default_turn().await;
|
||||
assert_eq!(
|
||||
child_turn.file_system_sandbox_policy,
|
||||
child_turn.file_system_sandbox_policy(),
|
||||
expected_file_system_sandbox_policy
|
||||
);
|
||||
assert_eq!(
|
||||
child_turn.network_sandbox_policy,
|
||||
child_turn.network_sandbox_policy(),
|
||||
expected_network_sandbox_policy
|
||||
);
|
||||
assert_eq!(child_turn.permission_profile(), expected_permission_profile);
|
||||
@@ -3637,11 +3625,6 @@ async fn build_agent_spawn_config_uses_turn_context_values() {
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
);
|
||||
turn.sandbox_policy
|
||||
.set(sandbox_policy)
|
||||
.expect("sandbox policy set");
|
||||
turn.file_system_sandbox_policy = file_system_sandbox_policy;
|
||||
turn.network_sandbox_policy = network_sandbox_policy;
|
||||
turn.permission_profile = permission_profile.clone();
|
||||
turn.approval_policy
|
||||
.set(AskForApproval::OnRequest)
|
||||
@@ -3718,7 +3701,7 @@ async fn build_agent_resume_config_clears_base_instructions() {
|
||||
expected
|
||||
.permissions
|
||||
.sandbox_policy
|
||||
.set(turn.sandbox_policy.get().clone())
|
||||
.set(turn.sandbox_policy())
|
||||
.expect("sandbox policy set");
|
||||
assert_eq!(config, expected);
|
||||
}
|
||||
|
||||
@@ -513,14 +513,16 @@ impl ShellHandler {
|
||||
);
|
||||
emitter.begin(event_ctx).await;
|
||||
|
||||
let file_system_sandbox_policy = turn.file_system_sandbox_policy();
|
||||
let sandbox_policy = turn.sandbox_policy();
|
||||
let exec_approval_requirement = session
|
||||
.services
|
||||
.exec_policy
|
||||
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
|
||||
command: &exec_params.command,
|
||||
approval_policy: turn.approval_policy.value(),
|
||||
sandbox_policy: turn.sandbox_policy.get(),
|
||||
file_system_sandbox_policy: &turn.file_system_sandbox_policy,
|
||||
sandbox_policy: &sandbox_policy,
|
||||
file_system_sandbox_policy: &file_system_sandbox_policy,
|
||||
sandbox_permissions: if effective_additional_permissions.permissions_preapproved {
|
||||
codex_protocol::models::SandboxPermissions::UseDefault
|
||||
} else {
|
||||
|
||||
@@ -359,7 +359,8 @@ impl NetworkApprovalService {
|
||||
.await;
|
||||
return NetworkDecision::deny(REASON_NOT_ALLOWED);
|
||||
};
|
||||
if !sandbox_policy_allows_network_approval_flow(turn_context.sandbox_policy.get()) {
|
||||
let sandbox_policy = turn_context.sandbox_policy();
|
||||
if !sandbox_policy_allows_network_approval_flow(&sandbox_policy) {
|
||||
pending.set_decision(PendingApprovalDecision::Deny).await;
|
||||
self.pending_host_approvals.lock().await.remove(&key);
|
||||
self.record_outcome_for_single_active_call(NetworkApprovalOutcome::DeniedByPolicy(
|
||||
|
||||
@@ -122,8 +122,10 @@ impl ToolOrchestrator {
|
||||
// 1) Approval
|
||||
let mut already_approved = false;
|
||||
|
||||
let file_system_sandbox_policy = turn_ctx.file_system_sandbox_policy();
|
||||
let network_sandbox_policy = turn_ctx.network_sandbox_policy();
|
||||
let requirement = tool.exec_approval_requirement(req).unwrap_or_else(|| {
|
||||
default_exec_approval_requirement(approval_policy, &turn_ctx.file_system_sandbox_policy)
|
||||
default_exec_approval_requirement(approval_policy, &file_system_sandbox_policy)
|
||||
});
|
||||
match requirement {
|
||||
ExecApprovalRequirement::Skip { .. } => {
|
||||
@@ -194,8 +196,8 @@ impl ToolOrchestrator {
|
||||
let initial_sandbox = match tool.sandbox_mode_for_first_attempt(req) {
|
||||
SandboxOverride::BypassSandboxFirstAttempt => SandboxType::None,
|
||||
SandboxOverride::NoOverride => self.sandbox.select_initial(
|
||||
&turn_ctx.file_system_sandbox_policy,
|
||||
turn_ctx.network_sandbox_policy,
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
tool.sandbox_preference(),
|
||||
turn_ctx.windows_sandbox_level,
|
||||
managed_network_active,
|
||||
@@ -268,7 +270,7 @@ impl ToolOrchestrator {
|
||||
&& matches!(
|
||||
default_exec_approval_requirement(
|
||||
approval_policy,
|
||||
&turn_ctx.file_system_sandbox_policy
|
||||
&file_system_sandbox_policy
|
||||
),
|
||||
ExecApprovalRequirement::NeedsApproval { .. }
|
||||
);
|
||||
|
||||
@@ -390,11 +390,12 @@ async fn execve_permission_request_hook_short_circuits_prompt() -> anyhow::Resul
|
||||
..HooksConfig::default()
|
||||
});
|
||||
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
turn_context.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
turn_context.sandbox_policy = Constrained::allow_any(sandbox_policy.clone());
|
||||
turn_context.file_system_sandbox_policy = read_only_file_system_sandbox_policy();
|
||||
turn_context.network_sandbox_policy = NetworkSandboxPolicy::Restricted;
|
||||
turn_context.permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
&read_only_file_system_sandbox_policy(),
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
);
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
|
||||
let workdir = AbsolutePathBuf::try_from(std::env::current_dir()?)?;
|
||||
let target = std::env::temp_dir().join("execve-hook-short-circuit.txt");
|
||||
|
||||
@@ -788,6 +788,8 @@ impl UnifiedExecProcessManager {
|
||||
self,
|
||||
context.turn.tools_config.unified_exec_shell_mode.clone(),
|
||||
);
|
||||
let file_system_sandbox_policy = context.turn.file_system_sandbox_policy();
|
||||
let sandbox_policy = context.turn.sandbox_policy();
|
||||
let exec_approval_requirement = context
|
||||
.session
|
||||
.services
|
||||
@@ -795,8 +797,8 @@ impl UnifiedExecProcessManager {
|
||||
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
|
||||
command: &request.command,
|
||||
approval_policy: context.turn.approval_policy.value(),
|
||||
sandbox_policy: context.turn.sandbox_policy.get(),
|
||||
file_system_sandbox_policy: &context.turn.file_system_sandbox_policy,
|
||||
sandbox_policy: &sandbox_policy,
|
||||
file_system_sandbox_policy: &file_system_sandbox_policy,
|
||||
sandbox_permissions: if request.additional_permissions_preapproved {
|
||||
crate::sandboxing::SandboxPermissions::UseDefault
|
||||
} else {
|
||||
|
||||
@@ -14,10 +14,12 @@ use codex_config::types::McpServerConfig;
|
||||
use codex_config::types::McpServerTransportConfig;
|
||||
use codex_core::sandboxing::SandboxPermissions;
|
||||
use codex_features::Feature;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||
@@ -560,7 +562,11 @@ async fn shell_enforces_glob_deny_read_policy() -> Result<()> {
|
||||
},
|
||||
access: FileSystemAccessMode::None,
|
||||
});
|
||||
config.permissions.file_system_sandbox_policy = file_system_sandbox_policy;
|
||||
config.permissions.permission_profile =
|
||||
Constrained::allow_any(PermissionProfile::from_runtime_permissions(
|
||||
&file_system_sandbox_policy,
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
));
|
||||
});
|
||||
let fixture = builder.build(&server).await?;
|
||||
|
||||
|
||||
@@ -2527,10 +2527,12 @@ async fn unified_exec_runs_under_sandbox() -> Result<()> {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn unified_exec_enforces_glob_deny_read_policy() -> Result<()> {
|
||||
use codex_config::Constrained;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
|
||||
skip_if_no_network!(Ok(()));
|
||||
skip_if_sandbox!(Ok(()));
|
||||
@@ -2553,7 +2555,11 @@ async fn unified_exec_enforces_glob_deny_read_policy() -> Result<()> {
|
||||
},
|
||||
access: FileSystemAccessMode::None,
|
||||
});
|
||||
config.permissions.file_system_sandbox_policy = file_system_sandbox_policy;
|
||||
config.permissions.permission_profile =
|
||||
Constrained::allow_any(PermissionProfile::from_runtime_permissions(
|
||||
&file_system_sandbox_policy,
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
));
|
||||
});
|
||||
let TestCodex {
|
||||
codex,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use anyhow::Context;
|
||||
use codex_features::Feature;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
@@ -338,7 +339,12 @@ async fn user_shell_command_history_is_persisted_and_shared_with_model() -> anyh
|
||||
async fn user_shell_command_does_not_set_network_sandbox_env_var() -> anyhow::Result<()> {
|
||||
let server = responses::start_mock_server().await;
|
||||
let mut builder = core_test_support::test_codex::test_codex().with_config(|config| {
|
||||
config.permissions.network_sandbox_policy = NetworkSandboxPolicy::Restricted;
|
||||
let file_system_sandbox_policy = config.permissions.file_system_sandbox_policy();
|
||||
config.permissions.permission_profile =
|
||||
codex_config::Constrained::allow_any(PermissionProfile::from_runtime_permissions(
|
||||
&file_system_sandbox_policy,
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
));
|
||||
});
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
|
||||
@@ -482,10 +482,7 @@ impl PermissionProfile {
|
||||
FileSystemSandboxKind::ExternalSandbox => Self::External {
|
||||
network: network_sandbox_policy,
|
||||
},
|
||||
FileSystemSandboxKind::Unrestricted
|
||||
if enforcement == SandboxEnforcement::Disabled
|
||||
&& network_sandbox_policy.is_enabled() =>
|
||||
{
|
||||
FileSystemSandboxKind::Unrestricted if enforcement == SandboxEnforcement::Disabled => {
|
||||
Self::Disabled
|
||||
}
|
||||
FileSystemSandboxKind::Restricted | FileSystemSandboxKind::Unrestricted => {
|
||||
@@ -1867,6 +1864,17 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disabled_permission_profile_ignores_runtime_network_policy() {
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::Disabled,
|
||||
&FileSystemSandboxPolicy::unrestricted(),
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
);
|
||||
|
||||
assert_eq!(permission_profile, PermissionProfile::Disabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn permission_profile_from_runtime_permissions_preserves_external_sandbox() {
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
|
||||
@@ -631,6 +631,12 @@ impl FileSystemSandboxPolicy {
|
||||
.semantic_signature(cwd)
|
||||
}
|
||||
|
||||
/// Returns true when two policies resolve to the same filesystem access
|
||||
/// model for `cwd`, ignoring incidental entry ordering.
|
||||
pub fn is_semantically_equivalent_to(&self, other: &Self, cwd: &Path) -> bool {
|
||||
self.semantic_signature(cwd) == other.semantic_signature(cwd)
|
||||
}
|
||||
|
||||
/// Returns the explicit readable roots resolved against the provided cwd.
|
||||
pub fn get_readable_roots_with_cwd(&self, cwd: &Path) -> Vec<AbsolutePathBuf> {
|
||||
if self.has_full_disk_read_access() {
|
||||
@@ -949,9 +955,9 @@ impl FileSystemSandboxPolicy {
|
||||
has_full_disk_read_access: self.has_full_disk_read_access(),
|
||||
has_full_disk_write_access: self.has_full_disk_write_access(),
|
||||
include_platform_defaults: self.include_platform_defaults(),
|
||||
readable_roots: self.get_readable_roots_with_cwd(cwd),
|
||||
writable_roots: self.get_writable_roots_with_cwd(cwd),
|
||||
unreadable_roots: self.get_unreadable_roots_with_cwd(cwd),
|
||||
readable_roots: sorted_absolute_paths(self.get_readable_roots_with_cwd(cwd)),
|
||||
writable_roots: sorted_writable_roots(self.get_writable_roots_with_cwd(cwd)),
|
||||
unreadable_roots: sorted_absolute_paths(self.get_unreadable_roots_with_cwd(cwd)),
|
||||
unreadable_globs: self.get_unreadable_globs_with_cwd(cwd),
|
||||
}
|
||||
}
|
||||
@@ -1257,6 +1263,20 @@ fn dedup_absolute_paths(
|
||||
deduped
|
||||
}
|
||||
|
||||
fn sorted_absolute_paths(mut paths: Vec<AbsolutePathBuf>) -> Vec<AbsolutePathBuf> {
|
||||
paths.sort_by(|left, right| left.as_path().cmp(right.as_path()));
|
||||
paths
|
||||
}
|
||||
|
||||
fn sorted_writable_roots(mut roots: Vec<WritableRoot>) -> Vec<WritableRoot> {
|
||||
for root in &mut roots {
|
||||
root.read_only_subpaths =
|
||||
sorted_absolute_paths(std::mem::take(&mut root.read_only_subpaths));
|
||||
}
|
||||
roots.sort_by(|left, right| left.root.as_path().cmp(right.root.as_path()));
|
||||
roots
|
||||
}
|
||||
|
||||
fn normalize_effective_absolute_path(path: AbsolutePathBuf) -> AbsolutePathBuf {
|
||||
let raw_path = path.to_path_buf();
|
||||
for ancestor in raw_path.ancestors() {
|
||||
@@ -2145,6 +2165,31 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_projection_runtime_enforcement_ignores_entry_order() {
|
||||
let cwd = TempDir::new().expect("tempdir");
|
||||
let legacy_policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: Vec::new(),
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
};
|
||||
let legacy_order =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(&legacy_policy, cwd.path());
|
||||
let mut reordered_entries = legacy_order.entries.clone();
|
||||
reordered_entries.reverse();
|
||||
let reordered = FileSystemSandboxPolicy::restricted(reordered_entries);
|
||||
|
||||
assert!(
|
||||
legacy_order.is_semantically_equivalent_to(&reordered, cwd.path()),
|
||||
"entry order should not affect filesystem semantics"
|
||||
);
|
||||
assert!(
|
||||
!reordered
|
||||
.needs_direct_runtime_enforcement(NetworkSandboxPolicy::Restricted, cwd.path())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn root_write_with_read_only_child_is_not_full_disk_write() {
|
||||
let cwd = TempDir::new().expect("tempdir");
|
||||
|
||||
@@ -2379,37 +2379,13 @@ impl ChatWidget {
|
||||
tracing::warn!(%err, "failed to sync permissions from SessionConfigured");
|
||||
self.config.permissions.sandbox_policy =
|
||||
Constrained::allow_only(event.sandbox_policy.clone());
|
||||
match event.permission_profile.clone() {
|
||||
Some(permission_profile) => {
|
||||
let (file_system_sandbox_policy, network_sandbox_policy) =
|
||||
permission_profile.to_runtime_permissions();
|
||||
self.config.permissions.permission_profile =
|
||||
Constrained::allow_only(permission_profile);
|
||||
self.config.permissions.file_system_sandbox_policy = file_system_sandbox_policy;
|
||||
self.config.permissions.network_sandbox_policy = network_sandbox_policy;
|
||||
}
|
||||
None => {
|
||||
self.config.permissions.file_system_sandbox_policy =
|
||||
codex_protocol::permissions::FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
|
||||
&event.sandbox_policy,
|
||||
&event.cwd,
|
||||
);
|
||||
self.config.permissions.network_sandbox_policy =
|
||||
codex_protocol::permissions::NetworkSandboxPolicy::from(
|
||||
&event.sandbox_policy,
|
||||
);
|
||||
let permission_profile =
|
||||
codex_protocol::models::PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
codex_protocol::models::SandboxEnforcement::from_legacy_sandbox_policy(
|
||||
&event.sandbox_policy,
|
||||
),
|
||||
&self.config.permissions.file_system_sandbox_policy,
|
||||
self.config.permissions.network_sandbox_policy,
|
||||
);
|
||||
self.config.permissions.permission_profile =
|
||||
Constrained::allow_only(permission_profile);
|
||||
}
|
||||
}
|
||||
let permission_profile = event.permission_profile.clone().unwrap_or_else(|| {
|
||||
codex_protocol::models::PermissionProfile::from_legacy_sandbox_policy(
|
||||
&event.sandbox_policy,
|
||||
)
|
||||
});
|
||||
self.config.permissions.permission_profile =
|
||||
Constrained::allow_only(permission_profile);
|
||||
}
|
||||
self.config.approvals_reviewer = event.approvals_reviewer;
|
||||
self.status_line_project_root_name_cache = None;
|
||||
|
||||
@@ -380,12 +380,12 @@ async fn session_configured_external_sandbox_keeps_external_runtime_policy() {
|
||||
assert_eq!(
|
||||
chat.config_ref()
|
||||
.permissions
|
||||
.file_system_sandbox_policy
|
||||
.file_system_sandbox_policy()
|
||||
.kind,
|
||||
FileSystemSandboxKind::ExternalSandbox,
|
||||
);
|
||||
assert_eq!(
|
||||
chat.config_ref().permissions.network_sandbox_policy,
|
||||
chat.config_ref().permissions.network_sandbox_policy(),
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user