remove sandbox globals. (#9797)

Threads sandbox updates through OverrideTurnContext for active turn
Passes computed sandbox type into safety/exec
This commit is contained in:
iceweasel-oai
2026-01-27 11:04:23 -08:00
committed by GitHub
parent 894923ed5d
commit c40ad65bd8
35 changed files with 339 additions and 132 deletions
+50 -4
View File
@@ -42,7 +42,6 @@ use codex_core::config::ConfigOverrides;
use codex_core::config::edit::ConfigEdit;
use codex_core::config::edit::ConfigEditsBuilder;
use codex_core::config_loader::ConfigLayerStackOrdering;
#[cfg(target_os = "windows")]
use codex_core::features::Feature;
use codex_core::models_manager::manager::RefreshStrategy;
use codex_core::models_manager::model_presets::HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG;
@@ -58,9 +57,13 @@ use codex_core::protocol::SandboxPolicy;
use codex_core::protocol::SessionSource;
use codex_core::protocol::SkillErrorInfo;
use codex_core::protocol::TokenUsage;
#[cfg(target_os = "windows")]
use codex_core::windows_sandbox::WindowsSandboxLevelExt;
use codex_otel::OtelManager;
use codex_protocol::ThreadId;
use codex_protocol::config_types::Personality;
#[cfg(target_os = "windows")]
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::items::TurnItem;
use codex_protocol::openai_models::ModelPreset;
use codex_protocol::openai_models::ModelUpgrade;
@@ -1088,7 +1091,8 @@ 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 should_check = codex_core::get_platform_sandbox().is_some()
let should_check = WindowsSandboxLevel::from_config(&app.config)
!= WindowsSandboxLevel::Disabled
&& matches!(
app.config.sandbox_policy.get(),
codex_core::protocol::SandboxPolicy::WorkspaceWrite { .. }
@@ -1684,9 +1688,24 @@ impl App {
elevated_enabled,
);
self.chat_widget.clear_forced_auto_mode_downgrade();
let windows_sandbox_level =
WindowsSandboxLevel::from_config(&self.config);
if let Some((sample_paths, extra_count, failed_scan)) =
self.chat_widget.world_writable_warning_details()
{
self.app_event_tx.send(AppEvent::CodexOp(
Op::OverrideTurnContext {
cwd: None,
approval_policy: None,
sandbox_policy: None,
windows_sandbox_level: Some(windows_sandbox_level),
model: None,
effort: None,
summary: None,
collaboration_mode: None,
personality: None,
},
));
self.app_event_tx.send(
AppEvent::OpenWorldWritableWarningConfirmation {
preset: Some(preset.clone()),
@@ -1701,6 +1720,7 @@ impl App {
cwd: None,
approval_policy: Some(preset.approval),
sandbox_policy: Some(preset.sandbox.clone()),
windows_sandbox_level: Some(windows_sandbox_level),
model: None,
effort: None,
summary: None,
@@ -1839,7 +1859,8 @@ impl App {
}
#[cfg(target_os = "windows")]
if !matches!(&policy, codex_core::protocol::SandboxPolicy::ReadOnly)
|| codex_core::get_platform_sandbox().is_some()
|| WindowsSandboxLevel::from_config(&self.config)
!= WindowsSandboxLevel::Disabled
{
self.config.forced_auto_mode_downgraded_on_windows = false;
}
@@ -1861,7 +1882,8 @@ impl App {
return Ok(AppRunControl::Continue);
}
let should_check = codex_core::get_platform_sandbox().is_some()
let should_check = WindowsSandboxLevel::from_config(&self.config)
!= WindowsSandboxLevel::Disabled
&& policy_is_workspace_write_or_ro
&& !self.chat_widget.world_writable_warning_hidden();
if should_check {
@@ -1885,6 +1907,12 @@ impl App {
if updates.is_empty() {
return Ok(AppRunControl::Continue);
}
let windows_sandbox_changed = updates.iter().any(|(feature, _)| {
matches!(
feature,
Feature::WindowsSandbox | Feature::WindowsSandboxElevated
)
});
let mut builder = ConfigEditsBuilder::new(&self.config.codex_home)
.with_profile(self.active_profile.as_deref());
for (feature, enabled) in &updates {
@@ -1910,6 +1938,24 @@ impl App {
}
}
}
if windows_sandbox_changed {
#[cfg(target_os = "windows")]
{
let windows_sandbox_level = WindowsSandboxLevel::from_config(&self.config);
self.app_event_tx
.send(AppEvent::CodexOp(Op::OverrideTurnContext {
cwd: None,
approval_policy: None,
sandbox_policy: None,
windows_sandbox_level: Some(windows_sandbox_level),
model: None,
effort: None,
summary: None,
collaboration_mode: None,
personality: None,
}));
}
}
if let Err(err) = builder.apply().await {
tracing::error!(error = %err, "failed to persist feature flags");
self.chat_widget.add_error_message(format!(
+3
View File
@@ -178,6 +178,9 @@ pub(crate) enum AppEvent {
mode: WindowsSandboxEnableMode,
},
/// Update the Windows sandbox feature mode without changing approval presets.
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
/// Update the current approval policy in the running app and widget.
UpdateAskForApprovalPolicy(AskForApproval),
@@ -273,6 +273,7 @@ pub(crate) struct ChatComposer {
config: ChatComposerConfig,
collaboration_mode_indicator: Option<CollaborationModeIndicator>,
personality_command_enabled: bool,
windows_degraded_sandbox_active: bool,
}
#[derive(Clone, Debug)]
@@ -358,6 +359,7 @@ impl ChatComposer {
config,
collaboration_mode_indicator: None,
personality_command_enabled: false,
windows_degraded_sandbox_active: false,
};
// Apply configuration via the setter to keep side-effects centralized.
this.set_disable_paste_burst(disable_paste_burst);
@@ -405,6 +407,10 @@ impl ChatComposer {
fn image_paste_enabled(&self) -> bool {
self.config.image_paste_enabled
}
#[cfg(target_os = "windows")]
pub fn set_windows_degraded_sandbox_active(&mut self, enabled: bool) {
self.windows_degraded_sandbox_active = enabled;
}
fn layout_areas(&self, area: Rect) -> [Rect; 3] {
let footer_props = self.footer_props();
let footer_hint_height = self
@@ -1728,6 +1734,7 @@ impl ChatComposer {
name,
self.collaboration_modes_enabled,
self.personality_command_enabled,
self.windows_degraded_sandbox_active,
)
.is_some();
let prompt_prefix = format!("{PROMPTS_CMD_PREFIX}:");
@@ -1904,6 +1911,7 @@ impl ChatComposer {
name,
self.collaboration_modes_enabled,
self.personality_command_enabled,
self.windows_degraded_sandbox_active,
)
{
self.textarea.set_text_clearing_elements("");
@@ -1931,6 +1939,7 @@ impl ChatComposer {
name,
self.collaboration_modes_enabled,
self.personality_command_enabled,
self.windows_degraded_sandbox_active,
)
&& cmd == SlashCommand::Review
{
@@ -2391,6 +2400,7 @@ impl ChatComposer {
name,
self.collaboration_modes_enabled,
self.personality_command_enabled,
self.windows_degraded_sandbox_active,
) {
return true;
}
@@ -2447,6 +2457,7 @@ impl ChatComposer {
CommandPopupFlags {
collaboration_modes_enabled,
personality_command_enabled,
windows_degraded_sandbox_active: self.windows_degraded_sandbox_active,
},
);
command_popup.on_composer_text_change(first_line.to_string());
@@ -33,6 +33,7 @@ pub(crate) struct CommandPopup {
pub(crate) struct CommandPopupFlags {
pub(crate) collaboration_modes_enabled: bool,
pub(crate) personality_command_enabled: bool,
pub(crate) windows_degraded_sandbox_active: bool,
}
impl CommandPopup {
@@ -41,6 +42,7 @@ impl CommandPopup {
let builtins = slash_commands::builtins_for_input(
flags.collaboration_modes_enabled,
flags.personality_command_enabled,
flags.windows_degraded_sandbox_active,
);
// Exclude prompts that collide with builtin command names and sort by name.
let exclude: HashSet<String> = builtins.iter().map(|(n, _)| (*n).to_string()).collect();
@@ -461,6 +463,7 @@ mod tests {
CommandPopupFlags {
collaboration_modes_enabled: true,
personality_command_enabled: true,
windows_degraded_sandbox_active: false,
},
);
popup.on_composer_text_change("/collab".to_string());
@@ -478,6 +481,7 @@ mod tests {
CommandPopupFlags {
collaboration_modes_enabled: true,
personality_command_enabled: false,
windows_degraded_sandbox_active: false,
},
);
popup.on_composer_text_change("/pers".to_string());
@@ -503,6 +507,7 @@ mod tests {
CommandPopupFlags {
collaboration_modes_enabled: true,
personality_command_enabled: true,
windows_degraded_sandbox_active: false,
},
);
popup.on_composer_text_change("/personality".to_string());
+6
View File
@@ -213,6 +213,12 @@ impl BottomPane {
self.request_redraw();
}
#[cfg(target_os = "windows")]
pub fn set_windows_degraded_sandbox_active(&mut self, enabled: bool) {
self.composer.set_windows_degraded_sandbox_active(enabled);
self.request_redraw();
}
pub fn set_collaboration_mode_indicator(
&mut self,
indicator: Option<CollaborationModeIndicator>,
+18 -16
View File
@@ -8,20 +8,12 @@ use codex_common::fuzzy_match::fuzzy_match;
use crate::slash_command::SlashCommand;
use crate::slash_command::built_in_slash_commands;
/// Whether the Windows degraded-sandbox elevation flow is currently allowed.
pub(crate) fn windows_degraded_sandbox_active() -> bool {
cfg!(target_os = "windows")
&& codex_core::windows_sandbox::ELEVATED_SANDBOX_NUX_ENABLED
&& codex_core::get_platform_sandbox().is_some()
&& !codex_core::is_windows_elevated_sandbox_enabled()
}
/// Return the built-ins that should be visible/usable for the current input.
pub(crate) fn builtins_for_input(
collaboration_modes_enabled: bool,
personality_command_enabled: bool,
allow_elevate_sandbox: bool,
) -> Vec<(&'static str, SlashCommand)> {
let allow_elevate_sandbox = windows_degraded_sandbox_active();
built_in_slash_commands()
.into_iter()
.filter(|(_, cmd)| allow_elevate_sandbox || *cmd != SlashCommand::ElevateSandbox)
@@ -35,11 +27,16 @@ pub(crate) fn find_builtin_command(
name: &str,
collaboration_modes_enabled: bool,
personality_command_enabled: bool,
allow_elevate_sandbox: bool,
) -> Option<SlashCommand> {
builtins_for_input(collaboration_modes_enabled, personality_command_enabled)
.into_iter()
.find(|(command_name, _)| *command_name == name)
.map(|(_, cmd)| cmd)
builtins_for_input(
collaboration_modes_enabled,
personality_command_enabled,
allow_elevate_sandbox,
)
.into_iter()
.find(|(command_name, _)| *command_name == name)
.map(|(_, cmd)| cmd)
}
/// Whether any visible built-in fuzzily matches the provided prefix.
@@ -47,8 +44,13 @@ pub(crate) fn has_builtin_prefix(
name: &str,
collaboration_modes_enabled: bool,
personality_command_enabled: bool,
allow_elevate_sandbox: bool,
) -> bool {
builtins_for_input(collaboration_modes_enabled, personality_command_enabled)
.into_iter()
.any(|(command_name, _)| fuzzy_match(command_name, name).is_some())
builtins_for_input(
collaboration_modes_enabled,
personality_command_enabled,
allow_elevate_sandbox,
)
.into_iter()
.any(|(command_name, _)| fuzzy_match(command_name, name).is_some())
}
+50 -8
View File
@@ -88,6 +88,8 @@ use codex_core::protocol::WarningEvent;
use codex_core::protocol::WebSearchBeginEvent;
use codex_core::protocol::WebSearchEndEvent;
use codex_core::skills::model::SkillMetadata;
#[cfg(target_os = "windows")]
use codex_core::windows_sandbox::WindowsSandboxLevelExt;
use codex_otel::OtelManager;
use codex_protocol::ThreadId;
use codex_protocol::account::PlanType;
@@ -97,6 +99,8 @@ use codex_protocol::config_types::CollaborationModeMask;
use codex_protocol::config_types::ModeKind;
use codex_protocol::config_types::Personality;
use codex_protocol::config_types::Settings;
#[cfg(target_os = "windows")]
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::local_image_label_text;
use codex_protocol::parse_command::ParsedCommand;
use codex_protocol::request_user_input::RequestUserInputEvent;
@@ -2057,6 +2061,14 @@ impl ChatWidget {
widget.config.features.enabled(Feature::CollaborationModes),
);
widget.sync_personality_command_enabled();
#[cfg(target_os = "windows")]
widget.bottom_pane.set_windows_degraded_sandbox_active(
codex_core::windows_sandbox::ELEVATED_SANDBOX_NUX_ENABLED
&& matches!(
WindowsSandboxLevel::from_config(&widget.config),
WindowsSandboxLevel::RestrictedToken
),
);
widget.update_collaboration_mode_indicator();
widget
@@ -2308,6 +2320,14 @@ impl ChatWidget {
widget.config.features.enabled(Feature::CollaborationModes),
);
widget.sync_personality_command_enabled();
#[cfg(target_os = "windows")]
widget.bottom_pane.set_windows_degraded_sandbox_active(
codex_core::windows_sandbox::ELEVATED_SANDBOX_NUX_ENABLED
&& matches!(
WindowsSandboxLevel::from_config(&widget.config),
WindowsSandboxLevel::RestrictedToken
),
);
widget.update_collaboration_mode_indicator();
widget
@@ -2563,9 +2583,9 @@ impl ChatWidget {
SlashCommand::ElevateSandbox => {
#[cfg(target_os = "windows")]
{
let windows_degraded_sandbox_enabled = codex_core::get_platform_sandbox()
.is_some()
&& !codex_core::is_windows_elevated_sandbox_enabled();
let windows_sandbox_level = WindowsSandboxLevel::from_config(&self.config);
let windows_degraded_sandbox_enabled =
matches!(windows_sandbox_level, WindowsSandboxLevel::RestrictedToken);
if !windows_degraded_sandbox_enabled
|| !codex_core::windows_sandbox::ELEVATED_SANDBOX_NUX_ENABLED
{
@@ -3340,6 +3360,7 @@ impl ChatWidget {
cwd: None,
approval_policy: None,
sandbox_policy: None,
windows_sandbox_level: None,
model: Some(switch_model.clone()),
effort: Some(Some(default_effort)),
summary: None,
@@ -3462,6 +3483,7 @@ impl ChatWidget {
effort: None,
summary: None,
collaboration_mode: None,
windows_sandbox_level: None,
personality: Some(personality),
}));
tx.send(AppEvent::UpdatePersonality(personality));
@@ -3731,6 +3753,7 @@ impl ChatWidget {
cwd: None,
approval_policy: None,
sandbox_policy: None,
windows_sandbox_level: None,
model: Some(model_for_action.clone()),
effort: Some(effort_for_action),
summary: None,
@@ -3904,6 +3927,7 @@ impl ChatWidget {
cwd: None,
approval_policy: None,
sandbox_policy: None,
windows_sandbox_level: None,
model: Some(model.clone()),
effort: Some(effort),
summary: None,
@@ -3944,8 +3968,10 @@ impl ChatWidget {
let presets: Vec<ApprovalPreset> = builtin_approval_presets();
#[cfg(target_os = "windows")]
let windows_degraded_sandbox_enabled = codex_core::get_platform_sandbox().is_some()
&& !codex_core::is_windows_elevated_sandbox_enabled();
let windows_sandbox_level = WindowsSandboxLevel::from_config(&self.config);
#[cfg(target_os = "windows")]
let windows_degraded_sandbox_enabled =
matches!(windows_sandbox_level, WindowsSandboxLevel::RestrictedToken);
#[cfg(not(target_os = "windows"))]
let windows_degraded_sandbox_enabled = false;
@@ -3986,7 +4012,9 @@ impl ChatWidget {
} else if preset.id == "auto" {
#[cfg(target_os = "windows")]
{
if codex_core::get_platform_sandbox().is_none() {
if WindowsSandboxLevel::from_config(&self.config)
== WindowsSandboxLevel::Disabled
{
let preset_clone = preset.clone();
if codex_core::windows_sandbox::ELEVATED_SANDBOX_NUX_ENABLED
&& codex_core::windows_sandbox::sandbox_setup_is_complete(
@@ -4088,6 +4116,7 @@ impl ChatWidget {
cwd: None,
approval_policy: Some(approval),
sandbox_policy: Some(sandbox_clone.clone()),
windows_sandbox_level: None,
model: None,
effort: None,
summary: None,
@@ -4591,7 +4620,7 @@ impl ChatWidget {
#[cfg(target_os = "windows")]
pub(crate) fn maybe_prompt_windows_sandbox_enable(&mut self) {
if self.config.forced_auto_mode_downgraded_on_windows
&& codex_core::get_platform_sandbox().is_none()
&& WindowsSandboxLevel::from_config(&self.config) == WindowsSandboxLevel::Disabled
&& let Some(preset) = builtin_approval_presets()
.into_iter()
.find(|preset| preset.id == "auto")
@@ -4651,7 +4680,7 @@ impl ChatWidget {
pub(crate) fn set_sandbox_policy(&mut self, policy: SandboxPolicy) -> ConstraintResult<()> {
#[cfg(target_os = "windows")]
let should_clear_downgrade = !matches!(&policy, SandboxPolicy::ReadOnly)
|| codex_core::get_platform_sandbox().is_some();
|| WindowsSandboxLevel::from_config(&self.config) != WindowsSandboxLevel::Disabled;
self.config.sandbox_policy.set(policy)?;
@@ -4685,6 +4714,19 @@ impl ChatWidget {
self.refresh_model_display();
self.request_redraw();
}
#[cfg(target_os = "windows")]
if matches!(
feature,
Feature::WindowsSandbox | Feature::WindowsSandboxElevated
) {
self.bottom_pane.set_windows_degraded_sandbox_active(
codex_core::windows_sandbox::ELEVATED_SANDBOX_NUX_ENABLED
&& matches!(
WindowsSandboxLevel::from_config(&self.config),
WindowsSandboxLevel::RestrictedToken
),
);
}
}
pub(crate) fn set_full_access_warning_acknowledged(&mut self, acknowledged: bool) {
+4 -26
View File
@@ -92,16 +92,6 @@ use tokio::sync::mpsc::error::TryRecvError;
use tokio::sync::mpsc::unbounded_channel;
use toml::Value as TomlValue;
#[cfg(target_os = "windows")]
fn set_windows_sandbox_enabled(enabled: bool) {
codex_core::set_windows_sandbox_enabled(enabled);
}
#[cfg(target_os = "windows")]
fn set_windows_elevated_sandbox_enabled(enabled: bool) {
codex_core::set_windows_elevated_sandbox_enabled(enabled);
}
async fn test_config() -> Config {
// Use base defaults to avoid depending on host state.
let codex_home = std::env::temp_dir();
@@ -3050,16 +3040,9 @@ async fn approvals_selection_popup_snapshot() {
async fn approvals_selection_popup_snapshot_windows_degraded_sandbox() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await;
let was_sandbox_enabled = codex_core::get_platform_sandbox().is_some();
let was_elevated_enabled = codex_core::is_windows_elevated_sandbox_enabled();
chat.config.notices.hide_full_access_warning = None;
chat.config.features.enable(Feature::WindowsSandbox);
chat.config
.features
.disable(Feature::WindowsSandboxElevated);
set_windows_sandbox_enabled(true);
set_windows_elevated_sandbox_enabled(false);
chat.set_feature_enabled(Feature::WindowsSandbox, true);
chat.set_feature_enabled(Feature::WindowsSandboxElevated, false);
chat.open_approvals_popup();
@@ -3067,10 +3050,6 @@ async fn approvals_selection_popup_snapshot_windows_degraded_sandbox() {
insta::with_settings!({ snapshot_suffix => "windows_degraded" }, {
assert_snapshot!("approvals_selection_popup", popup);
});
// Avoid leaking sandbox global state into other tests.
set_windows_sandbox_enabled(was_sandbox_enabled);
set_windows_elevated_sandbox_enabled(was_elevated_enabled);
}
#[tokio::test]
@@ -3133,7 +3112,8 @@ async fn windows_auto_mode_prompt_requests_enabling_sandbox_feature() {
async fn startup_prompts_for_windows_sandbox_when_agent_requested() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await;
set_windows_sandbox_enabled(false);
chat.set_feature_enabled(Feature::WindowsSandbox, false);
chat.set_feature_enabled(Feature::WindowsSandboxElevated, false);
chat.config.forced_auto_mode_downgraded_on_windows = true;
chat.maybe_prompt_windows_sandbox_enable();
@@ -3151,8 +3131,6 @@ async fn startup_prompts_for_windows_sandbox_when_agent_requested() {
popup.contains("Stay in"),
"expected startup prompt to offer staying in current mode: {popup}"
);
set_windows_sandbox_enabled(true);
}
#[tokio::test]
+5 -2
View File
@@ -26,13 +26,14 @@ use codex_core::config::resolve_oss_provider;
use codex_core::config_loader::ConfigLoadError;
use codex_core::config_loader::format_config_error_with_source;
use codex_core::find_thread_path_by_id_str;
use codex_core::get_platform_sandbox;
use codex_core::path_utils;
use codex_core::protocol::AskForApproval;
use codex_core::read_session_meta_line;
use codex_core::terminal::Multiplexer;
use codex_core::windows_sandbox::WindowsSandboxLevelExt;
use codex_protocol::config_types::AltScreenMode;
use codex_protocol::config_types::SandboxMode;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::protocol::RolloutItem;
use codex_protocol::protocol::RolloutLine;
use codex_utils_absolute_path::AbsolutePathBuf;
@@ -816,7 +817,9 @@ async fn load_config_or_exit_with_fallback_cwd(
/// or if the current cwd project is already trusted. If not, we need to
/// show the trust screen.
fn should_show_trust_screen(config: &Config) -> bool {
if cfg!(target_os = "windows") && get_platform_sandbox().is_none() {
if cfg!(target_os = "windows")
&& WindowsSandboxLevel::from_config(config) == WindowsSandboxLevel::Disabled
{
// If the experimental sandbox is not enabled, Native Windows cannot enforce sandboxed write access; skip the trust prompt entirely.
return false;
}