Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions codex-rs/app-server/src/codex_message_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2307,7 +2307,11 @@ impl CodexMessageProcessor {
}
}
} else if let Some(policy) = sandbox_policy.map(|policy| policy.to_core()) {
match self.config.permissions.sandbox_policy.can_set(&policy) {
match self
.config
.permissions
.can_set_legacy_sandbox_policy(&policy, &sandbox_cwd)
{
Ok(()) => {
let file_system_sandbox_policy =
codex_protocol::permissions::FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(&policy, &sandbox_cwd);
Expand Down Expand Up @@ -8705,7 +8709,9 @@ impl CodexMessageProcessor {
Ok(config) => {
let setup_request = WindowsSandboxSetupRequest {
mode,
policy: config.permissions.sandbox_policy.get().clone(),
policy: config
.permissions
.legacy_sandbox_policy(config.cwd.as_path()),
policy_cwd: config.cwd.to_path_buf(),
command_cwd,
env_map: std::env::vars().collect(),
Expand Down
9 changes: 7 additions & 2 deletions codex-rs/cli/src/debug_sandbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,9 @@ async fn run_command_under_sandbox(
let args = create_linux_sandbox_command_args_for_policies(
command,
cwd.as_path(),
config.permissions.sandbox_policy.get(),
&config
.permissions
.legacy_sandbox_policy(sandbox_policy_cwd.as_path()),
&file_system_sandbox_policy,
network_sandbox_policy,
sandbox_policy_cwd.as_path(),
Expand Down Expand Up @@ -290,7 +292,10 @@ async fn run_command_under_windows_session(
use codex_windows_sandbox::spawn_windows_sandbox_session_elevated;
use codex_windows_sandbox::spawn_windows_sandbox_session_legacy;

let policy_str = match serde_json::to_string(config.permissions.sandbox_policy.get()) {
let sandbox_policy = config
.permissions
.legacy_sandbox_policy(sandbox_policy_cwd.as_path());
let policy_str = match serde_json::to_string(&sandbox_policy) {
Ok(policy_str) => policy_str,
Err(err) => {
eprintln!("windows sandbox failed to serialize policy: {err}");
Expand Down
34 changes: 32 additions & 2 deletions codex-rs/core/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,14 +237,45 @@ impl Permissions {
self.permission_profile.get().network_sandbox_policy()
}

/// Legacy compatibility projection derived from the canonical profile.
pub fn legacy_sandbox_policy(&self, cwd: &Path) -> SandboxPolicy {
let permission_profile = self.permission_profile.get();
let file_system_sandbox_policy = permission_profile.file_system_sandbox_policy();
compatibility_sandbox_policy_for_permission_profile(
permission_profile,
&file_system_sandbox_policy,
permission_profile.network_sandbox_policy(),
cwd,
)
}

/// Check whether a legacy sandbox policy can be applied to this permission
/// set under both legacy and canonical profile constraints.
pub fn can_set_legacy_sandbox_policy(
&self,
sandbox_policy: &SandboxPolicy,
cwd: &Path,
) -> ConstraintResult<()> {
self.sandbox_policy.can_set(sandbox_policy)?;
let file_system_sandbox_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(sandbox_policy, cwd);
let network_sandbox_policy = NetworkSandboxPolicy::from(sandbox_policy);
let permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(sandbox_policy),
&file_system_sandbox_policy,
network_sandbox_policy,
);
self.permission_profile.can_set(&permission_profile)
}

/// Replace permissions from a legacy sandbox policy and keep every
/// permission projection in sync.
pub fn set_legacy_sandbox_policy(
&mut self,
sandbox_policy: SandboxPolicy,
cwd: &Path,
) -> ConstraintResult<()> {
self.sandbox_policy.can_set(&sandbox_policy)?;
self.can_set_legacy_sandbox_policy(&sandbox_policy, cwd)?;
let file_system_sandbox_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(&sandbox_policy, cwd);
let network_sandbox_policy = NetworkSandboxPolicy::from(&sandbox_policy);
Expand All @@ -253,7 +284,6 @@ impl Permissions {
&file_system_sandbox_policy,
network_sandbox_policy,
);
self.permission_profile.can_set(&permission_profile)?;

self.sandbox_policy.set(sandbox_policy)?;
self.permission_profile.set(permission_profile)?;
Expand Down
4 changes: 3 additions & 1 deletion codex-rs/core/src/session/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,9 @@ impl Session {
config.model_context_window,
config.model_auto_compact_token_limit,
config.permissions.approval_policy.value(),
config.permissions.sandbox_policy.get().clone(),
config
.permissions
.legacy_sandbox_policy(session_configuration.cwd.as_path()),
mcp_servers.keys().map(String::as_str).collect(),
config.active_profile.clone(),
);
Expand Down
8 changes: 6 additions & 2 deletions codex-rs/tui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -939,10 +939,14 @@ impl App {
// On startup, if Agent mode (workspace-write) or ReadOnly is active, warn about world-writable dirs on Windows.
#[cfg(target_os = "windows")]
{
let startup_sandbox_policy = app
.config
.permissions
.legacy_sandbox_policy(app.config.cwd.as_path());
let should_check = WindowsSandboxLevel::from_config(&app.config)
!= WindowsSandboxLevel::Disabled
&& matches!(
app.config.permissions.sandbox_policy.get(),
&startup_sandbox_policy,
codex_protocol::protocol::SandboxPolicy::WorkspaceWrite { .. }
| codex_protocol::protocol::SandboxPolicy::ReadOnly { .. }
)
Expand All @@ -956,7 +960,7 @@ impl App {
let env_map: std::collections::HashMap<String, String> = std::env::vars().collect();
let tx = app.app_event_tx.clone();
let logs_base_dir = app.config.codex_home.clone();
let sandbox_policy = app.config.permissions.sandbox_policy.get().clone();
let sandbox_policy = startup_sandbox_policy;
Self::spawn_world_writable_scan(cwd, env_map, logs_base_dir, sandbox_policy, tx);
}
}
Expand Down
15 changes: 10 additions & 5 deletions codex-rs/tui/src/app/config_persistence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,9 +300,11 @@ impl App {
.set_approval_policy(self.config.permissions.approval_policy.value());
}
if sandbox_policy_override.is_some()
&& let Err(err) = self
.chat_widget
.set_sandbox_policy(self.config.permissions.sandbox_policy.get().clone())
&& let Err(err) = self.chat_widget.set_sandbox_policy(
self.config
.permissions
.legacy_sandbox_policy(self.config.cwd.as_path()),
)
{
tracing::error!(
error = %err,
Expand All @@ -312,8 +314,11 @@ impl App {
.add_error_message(format!("Failed to enable Auto-review: {err}"));
}
if sandbox_policy_override.is_some() {
self.runtime_sandbox_policy_override =
Some(self.config.permissions.sandbox_policy.get().clone());
self.runtime_sandbox_policy_override = Some(
self.config
.permissions
.legacy_sandbox_policy(self.config.cwd.as_path()),
);
}

if approval_policy_override.is_some()
Expand Down
17 changes: 13 additions & 4 deletions codex-rs/tui/src/app/event_dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -834,7 +834,10 @@ impl App {
/*hint*/ None,
));

let policy = self.config.permissions.sandbox_policy.get().clone();
let policy = self
.config
.permissions
.legacy_sandbox_policy(self.config.cwd.as_path());
let policy_cwd = self.config.cwd.clone();
let command_cwd = self.config.cwd.clone();
let env_map: std::collections::HashMap<String, String> =
Expand Down Expand Up @@ -1245,8 +1248,11 @@ impl App {
.add_error_message(format!("Failed to set sandbox policy: {err}"));
return Ok(AppRunControl::Continue);
}
self.runtime_sandbox_policy_override =
Some(self.config.permissions.sandbox_policy.get().clone());
self.runtime_sandbox_policy_override = Some(
self.config
.permissions
.legacy_sandbox_policy(self.config.cwd.as_path()),
);
self.sync_active_thread_permission_settings_to_cached_session()
.await;

Expand All @@ -1269,7 +1275,10 @@ impl App {
std::env::vars().collect();
let tx = self.app_event_tx.clone();
let logs_base_dir = self.config.codex_home.clone();
let sandbox_policy = self.config.permissions.sandbox_policy.get().clone();
let sandbox_policy = self
.config
.permissions
.legacy_sandbox_policy(self.config.cwd.as_path());
Self::spawn_world_writable_scan(
cwd,
env_map,
Expand Down
16 changes: 12 additions & 4 deletions codex-rs/tui/src/app/thread_session_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ impl App {

let approval_policy = self.config.permissions.approval_policy.value();
let approvals_reviewer = self.config.approvals_reviewer;
let sandbox_policy = self.config.permissions.sandbox_policy.get().clone();
let sandbox_policy = self
.config
.permissions
.legacy_sandbox_policy(self.config.cwd.as_path());
let permission_profile = Some(
self.chat_widget
.config_ref()
Expand Down Expand Up @@ -45,7 +48,10 @@ impl App {
thread_id: ThreadId,
thread: &Thread,
) -> ThreadSessionState {
let sandbox_policy = self.config.permissions.sandbox_policy.get().clone();
let sandbox_policy = self
.config
.permissions
.legacy_sandbox_policy(self.config.cwd.as_path());
let mut session = self
.primary_session_configured
.clone()
Expand Down Expand Up @@ -185,8 +191,10 @@ mod tests {
app.chat_widget
.set_sandbox_policy(expected_sandbox_policy.clone())
.expect("set widget sandbox policy");
app.config.permissions.sandbox_policy =
codex_config::Constrained::allow_any(expected_sandbox_policy.clone());
app.config
.permissions
.set_legacy_sandbox_policy(expected_sandbox_policy.clone(), app.config.cwd.as_path())
.expect("set app sandbox policy");

app.sync_active_thread_permission_settings_to_cached_session()
.await;
Expand Down
38 changes: 31 additions & 7 deletions codex-rs/tui/src/app_server_session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1143,7 +1143,13 @@ fn thread_start_params_from_config(
let permission_profile = permission_profile_override_from_config(config, thread_params_mode);
let sandbox = permission_profile
.is_none()
.then(|| sandbox_mode_from_policy(config.permissions.sandbox_policy.get().clone()))
.then(|| {
sandbox_mode_from_policy(
config
.permissions
.legacy_sandbox_policy(config.cwd.as_path()),
)
})
.flatten();
ThreadStartParams {
model: config.model.clone(),
Expand All @@ -1170,7 +1176,13 @@ fn thread_resume_params_from_config(
let permission_profile = permission_profile_override_from_config(&config, thread_params_mode);
let sandbox = permission_profile
.is_none()
.then(|| sandbox_mode_from_policy(config.permissions.sandbox_policy.get().clone()))
.then(|| {
sandbox_mode_from_policy(
config
.permissions
.legacy_sandbox_policy(config.cwd.as_path()),
)
})
.flatten();
ThreadResumeParams {
thread_id: thread_id.to_string(),
Expand All @@ -1196,7 +1208,13 @@ fn thread_fork_params_from_config(
let permission_profile = permission_profile_override_from_config(&config, thread_params_mode);
let sandbox = permission_profile
.is_none()
.then(|| sandbox_mode_from_policy(config.permissions.sandbox_policy.get().clone()))
.then(|| {
sandbox_mode_from_policy(
config
.permissions
.legacy_sandbox_policy(config.cwd.as_path()),
)
})
.flatten();
ThreadForkParams {
thread_id: thread_id.to_string(),
Expand Down Expand Up @@ -1522,8 +1540,11 @@ mod tests {
let temp_dir = tempfile::tempdir().expect("tempdir");
let config = build_config(&temp_dir).await;
let thread_id = ThreadId::new();
let expected_sandbox =
sandbox_mode_from_policy(config.permissions.sandbox_policy.get().clone());
let expected_sandbox = sandbox_mode_from_policy(
config
.permissions
.legacy_sandbox_policy(config.cwd.as_path()),
);

let start = thread_start_params_from_config(
&config,
Expand Down Expand Up @@ -1564,8 +1585,11 @@ mod tests {
let config = build_config(&temp_dir).await;
let thread_id = ThreadId::new();
let remote_cwd = PathBuf::from("repo/on/server");
let expected_sandbox =
sandbox_mode_from_policy(config.permissions.sandbox_policy.get().clone());
let expected_sandbox = sandbox_mode_from_policy(
config
.permissions
.legacy_sandbox_policy(config.cwd.as_path()),
);

let start = thread_start_params_from_config(
&config,
Expand Down
33 changes: 26 additions & 7 deletions codex-rs/tui/src/chatwidget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6415,7 +6415,9 @@ impl ChatWidget {
items,
self.config.cwd.to_path_buf(),
self.config.permissions.approval_policy.value(),
self.config.permissions.sandbox_policy.get().clone(),
self.config
.permissions
.legacy_sandbox_policy(self.config.cwd.as_path()),
permission_profile,
effective_mode.model().to_string(),
effective_mode.reasoning_effort(),
Expand Down Expand Up @@ -9466,7 +9468,10 @@ impl ChatWidget {
pub(crate) fn open_permissions_popup(&mut self) {
let include_read_only = cfg!(target_os = "windows");
let current_approval = self.config.permissions.approval_policy.value();
let current_sandbox = self.config.permissions.sandbox_policy.get();
let current_sandbox = self
.config
.permissions
.legacy_sandbox_policy(self.config.cwd.as_path());
let guardian_approval_enabled = self.config.features.enabled(Feature::GuardianApproval);
let current_review_policy = self.config.approvals_reviewer;
let mut items: Vec<SelectionItem> = Vec::new();
Expand Down Expand Up @@ -9600,7 +9605,11 @@ impl ChatWidget {
name: base_name.clone(),
description: base_description.clone(),
is_current: current_review_policy == ApprovalsReviewer::User
&& Self::preset_matches_current(current_approval, current_sandbox, &preset),
&& Self::preset_matches_current(
current_approval,
&current_sandbox,
&preset,
),
actions: default_actions,
dismiss_on_select: true,
disabled_reason: default_disabled_reason,
Expand All @@ -9617,7 +9626,7 @@ impl ChatWidget {
is_current: current_review_policy == ApprovalsReviewer::AutoReview
&& Self::preset_matches_current(
current_approval,
current_sandbox,
&current_sandbox,
&preset,
),
actions: Self::approval_preset_actions(
Expand All @@ -9638,7 +9647,7 @@ impl ChatWidget {
description: base_description,
is_current: Self::preset_matches_current(
current_approval,
current_sandbox,
&current_sandbox,
&preset,
),
actions: default_actions,
Expand Down Expand Up @@ -9774,7 +9783,10 @@ impl ChatWidget {
self.config.codex_home.as_path(),
cwd.as_path(),
&env_map,
self.config.permissions.sandbox_policy.get(),
&self
.config
.permissions
.legacy_sandbox_policy(self.config.cwd.as_path()),
Some(self.config.codex_home.as_path()),
) {
Ok(_) => None,
Expand Down Expand Up @@ -9892,7 +9904,14 @@ impl ChatWidget {
let mode_label = preset
.as_ref()
.map(|p| describe_policy(&p.sandbox))
.unwrap_or_else(|| describe_policy(self.config.permissions.sandbox_policy.get()));
.unwrap_or_else(|| {
describe_policy(
&self
.config
.permissions
.legacy_sandbox_policy(self.config.cwd.as_path()),
)
});
let info_line = if failed_scan {
Line::from(vec![
"We couldn't complete the world-writable scan, so protections cannot be verified. "
Expand Down
Loading
Loading