permissions: centralize legacy sandbox projection (#19734)

## Why

The remaining migration work still needs `SandboxPolicy` at a few
compatibility boundaries, but those projections should come from one
canonical path. Keeping ad hoc legacy projections scattered through
app-server, CLI, and config code makes it easy for behavior to drift as
`PermissionProfile` gains fidelity that the legacy enum cannot
represent.

## What Changed

- Adds `Permissions::legacy_sandbox_policy(cwd)` and
`Config::legacy_sandbox_policy()` as the compatibility projection from
the canonical `PermissionProfile`.
- Adds `Permissions::can_set_legacy_sandbox_policy()` so legacy inputs
are checked after they are converted into profile semantics.
- Updates app-server command handling, Windows sandbox setup, session
configuration, and sandbox summaries to use the centralized projection
helper.
- Leaves `SandboxPolicy` in place only for boundary inputs/outputs that
still speak the legacy abstraction.

## Verification

- `cargo check -p codex-config -p codex-core -p codex-sandboxing -p
codex-app-server -p codex-cli -p codex-tui`
- `cargo test -p codex-tui
permissions_selection_history_snapshot_full_access_to_default --
--nocapture`
- `cargo test -p codex-tui
permissions_selection_sends_approvals_reviewer_in_override_turn_context
-- --nocapture`
- `bazel test //codex-rs/tui:tui-unit-tests-bin
--test_arg=permissions_selection_history_snapshot_full_access_to_default
--test_output=errors`
- `bazel test //codex-rs/tui:tui-unit-tests-bin
--test_arg=permissions_selection_sends_approvals_reviewer_in_override_turn_context
--test_output=errors`


---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19734).
* #19737
* #19736
* #19735
* __->__ #19734
This commit is contained in:
Michael Bolin authored and GitHub committed 2026-04-26 20:31:23 -07:00
1 parent c3e60849e5
commit 0d8cdc0510
16 files changed
+210 -86

No files matched your search

+10 -5
View File
@@ -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,
@@ -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()
+13 -4
View File
@@ -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> =
@@ -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;
@@ -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,
+12 -4
View File
@@ -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()
@@ -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()
@@ -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;