Skip to content

Commit 6372ba9

Browse files
Elevated sandbox NUX (#8789)
Elevated Sandbox NUX: * prompt for elevated sandbox setup when agent mode is selected (via /approvals or at startup) * prompt for degraded sandbox if elevated setup is declined or fails * introduce /elevate-sandbox command to upgrade from degraded experience.
1 parent bdfdebc commit 6372ba9

22 files changed

+1110
-184
lines changed

codex-rs/core/src/config/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1504,6 +1504,15 @@ impl Config {
15041504
}
15051505
self.forced_auto_mode_downgraded_on_windows = !value;
15061506
}
1507+
1508+
pub fn set_windows_elevated_sandbox_globally(&mut self, value: bool) {
1509+
crate::safety::set_windows_elevated_sandbox_enabled(value);
1510+
if value {
1511+
self.features.enable(Feature::WindowsSandboxElevated);
1512+
} else {
1513+
self.features.disable(Feature::WindowsSandboxElevated);
1514+
}
1515+
}
15071516
}
15081517

15091518
fn default_review_model() -> String {

codex-rs/core/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ pub mod token_data;
5151
mod truncate;
5252
mod unified_exec;
5353
mod user_instructions;
54+
pub mod windows_sandbox;
5455
pub use model_provider_info::CHAT_WIRE_API_DEPRECATION_SUMMARY;
5556
pub use model_provider_info::DEFAULT_LMSTUDIO_PORT;
5657
pub use model_provider_info::DEFAULT_OLLAMA_PORT;
@@ -114,6 +115,8 @@ pub use command_safety::is_safe_command;
114115
pub use exec_policy::ExecPolicyError;
115116
pub use exec_policy::load_exec_policy;
116117
pub use safety::get_platform_sandbox;
118+
pub use safety::is_windows_elevated_sandbox_enabled;
119+
pub use safety::set_windows_elevated_sandbox_enabled;
117120
pub use safety::set_windows_sandbox_enabled;
118121
// Re-export the protocol types from the standalone `codex-protocol` crate so existing
119122
// `codex_core::protocol::...` references continue to work across the workspace.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use crate::protocol::SandboxPolicy;
2+
use std::collections::HashMap;
3+
use std::path::Path;
4+
5+
/// Kill switch for the elevated sandbox NUX on Windows.
6+
///
7+
/// When false, revert to the previous sandbox NUX, which only
8+
/// prompts users to enable the legacy sandbox feature.
9+
pub const ELEVATED_SANDBOX_NUX_ENABLED: bool = true;
10+
11+
#[cfg(target_os = "windows")]
12+
pub fn sandbox_setup_is_complete(codex_home: &Path) -> bool {
13+
codex_windows_sandbox::sandbox_setup_is_complete(codex_home)
14+
}
15+
16+
#[cfg(not(target_os = "windows"))]
17+
pub fn sandbox_setup_is_complete(_codex_home: &Path) -> bool {
18+
false
19+
}
20+
21+
#[cfg(target_os = "windows")]
22+
pub fn run_elevated_setup(
23+
policy: &SandboxPolicy,
24+
policy_cwd: &Path,
25+
command_cwd: &Path,
26+
env_map: &HashMap<String, String>,
27+
codex_home: &Path,
28+
) -> anyhow::Result<()> {
29+
codex_windows_sandbox::run_elevated_setup(
30+
policy,
31+
policy_cwd,
32+
command_cwd,
33+
env_map,
34+
codex_home,
35+
None,
36+
None,
37+
)
38+
}
39+
40+
#[cfg(not(target_os = "windows"))]
41+
pub fn run_elevated_setup(
42+
_policy: &SandboxPolicy,
43+
_policy_cwd: &Path,
44+
_command_cwd: &Path,
45+
_env_map: &HashMap<String, String>,
46+
_codex_home: &Path,
47+
) -> anyhow::Result<()> {
48+
anyhow::bail!("elevated Windows sandbox setup is only supported on Windows")
49+
}

codex-rs/tui/src/app.rs

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
use crate::app_backtrack::BacktrackState;
22
use crate::app_event::AppEvent;
3+
#[cfg(target_os = "windows")]
4+
use crate::app_event::WindowsSandboxEnableMode;
5+
#[cfg(target_os = "windows")]
6+
use crate::app_event::WindowsSandboxFallbackReason;
37
use crate::app_event_sender::AppEventSender;
48
use crate::bottom_pane::ApprovalRequest;
59
use crate::chatwidget::ChatWidget;
@@ -792,19 +796,91 @@ impl App {
792796
AppEvent::OpenWindowsSandboxEnablePrompt { preset } => {
793797
self.chat_widget.open_windows_sandbox_enable_prompt(preset);
794798
}
795-
AppEvent::EnableWindowsSandboxForAgentMode { preset } => {
799+
AppEvent::OpenWindowsSandboxFallbackPrompt { preset, reason } => {
800+
self.chat_widget.clear_windows_sandbox_setup_status();
801+
self.chat_widget
802+
.open_windows_sandbox_fallback_prompt(preset, reason);
803+
}
804+
AppEvent::BeginWindowsSandboxElevatedSetup { preset } => {
805+
#[cfg(target_os = "windows")]
806+
{
807+
let policy = preset.sandbox.clone();
808+
let policy_cwd = self.config.cwd.clone();
809+
let command_cwd = policy_cwd.clone();
810+
let env_map: std::collections::HashMap<String, String> =
811+
std::env::vars().collect();
812+
let codex_home = self.config.codex_home.clone();
813+
let tx = self.app_event_tx.clone();
814+
815+
// If the elevated setup already ran on this machine, don't prompt for
816+
// elevation again - just flip the config to use the elevated path.
817+
if codex_core::windows_sandbox::sandbox_setup_is_complete(codex_home.as_path())
818+
{
819+
tx.send(AppEvent::EnableWindowsSandboxForAgentMode {
820+
preset,
821+
mode: WindowsSandboxEnableMode::Elevated,
822+
});
823+
return Ok(true);
824+
}
825+
826+
self.chat_widget.show_windows_sandbox_setup_status();
827+
tokio::task::spawn_blocking(move || {
828+
let result = codex_core::windows_sandbox::run_elevated_setup(
829+
&policy,
830+
policy_cwd.as_path(),
831+
command_cwd.as_path(),
832+
&env_map,
833+
codex_home.as_path(),
834+
);
835+
let event = match result {
836+
Ok(()) => AppEvent::EnableWindowsSandboxForAgentMode {
837+
preset: preset.clone(),
838+
mode: WindowsSandboxEnableMode::Elevated,
839+
},
840+
Err(err) => {
841+
tracing::error!(
842+
error = %err,
843+
"failed to run elevated Windows sandbox setup"
844+
);
845+
AppEvent::OpenWindowsSandboxFallbackPrompt {
846+
preset,
847+
reason: WindowsSandboxFallbackReason::ElevationFailed,
848+
}
849+
}
850+
};
851+
tx.send(event);
852+
});
853+
}
854+
#[cfg(not(target_os = "windows"))]
855+
{
856+
let _ = preset;
857+
}
858+
}
859+
AppEvent::EnableWindowsSandboxForAgentMode { preset, mode } => {
796860
#[cfg(target_os = "windows")]
797861
{
862+
self.chat_widget.clear_windows_sandbox_setup_status();
798863
let profile = self.active_profile.as_deref();
799864
let feature_key = Feature::WindowsSandbox.key();
865+
let elevated_key = Feature::WindowsSandboxElevated.key();
866+
let elevated_enabled = matches!(mode, WindowsSandboxEnableMode::Elevated);
800867
match ConfigEditsBuilder::new(&self.config.codex_home)
801868
.with_profile(profile)
802869
.set_feature_enabled(feature_key, true)
870+
.set_feature_enabled(elevated_key, elevated_enabled)
803871
.apply()
804872
.await
805873
{
806874
Ok(()) => {
807875
self.config.set_windows_sandbox_globally(true);
876+
self.config
877+
.set_windows_elevated_sandbox_globally(elevated_enabled);
878+
self.chat_widget
879+
.set_feature_enabled(Feature::WindowsSandbox, true);
880+
self.chat_widget.set_feature_enabled(
881+
Feature::WindowsSandboxElevated,
882+
elevated_enabled,
883+
);
808884
self.chat_widget.clear_forced_auto_mode_downgrade();
809885
if let Some((sample_paths, extra_count, failed_scan)) =
810886
self.chat_widget.world_writable_warning_details()
@@ -833,7 +909,14 @@ impl App {
833909
self.app_event_tx
834910
.send(AppEvent::UpdateSandboxPolicy(preset.sandbox.clone()));
835911
self.chat_widget.add_info_message(
836-
"Enabled experimental Windows sandbox.".to_string(),
912+
match mode {
913+
WindowsSandboxEnableMode::Elevated => {
914+
"Enabled elevated agent sandbox.".to_string()
915+
}
916+
WindowsSandboxEnableMode::Legacy => {
917+
"Enabled non-elevated agent sandbox.".to_string()
918+
}
919+
},
837920
None,
838921
);
839922
}
@@ -851,7 +934,7 @@ impl App {
851934
}
852935
#[cfg(not(target_os = "windows"))]
853936
{
854-
let _ = preset;
937+
let _ = (preset, mode);
855938
}
856939
}
857940
AppEvent::PersistModelSelection { model, effort } => {

codex-rs/tui/src/app_event.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,19 @@ use codex_core::protocol::AskForApproval;
1515
use codex_core::protocol::SandboxPolicy;
1616
use codex_protocol::openai_models::ReasoningEffort;
1717

18+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19+
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
20+
pub(crate) enum WindowsSandboxEnableMode {
21+
Elevated,
22+
Legacy,
23+
}
24+
25+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26+
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
27+
pub(crate) enum WindowsSandboxFallbackReason {
28+
ElevationFailed,
29+
}
30+
1831
#[allow(clippy::large_enum_variant)]
1932
#[derive(Debug)]
2033
pub(crate) enum AppEvent {
@@ -106,10 +119,24 @@ pub(crate) enum AppEvent {
106119
preset: ApprovalPreset,
107120
},
108121

122+
/// Open the Windows sandbox fallback prompt after declining or failing elevation.
123+
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
124+
OpenWindowsSandboxFallbackPrompt {
125+
preset: ApprovalPreset,
126+
reason: WindowsSandboxFallbackReason,
127+
},
128+
129+
/// Begin the elevated Windows sandbox setup flow.
130+
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
131+
BeginWindowsSandboxElevatedSetup {
132+
preset: ApprovalPreset,
133+
},
134+
109135
/// Enable the Windows sandbox feature and switch to Agent mode.
110136
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
111137
EnableWindowsSandboxForAgentMode {
112138
preset: ApprovalPreset,
139+
mode: WindowsSandboxEnableMode,
113140
},
114141

115142
/// Update the current approval policy in the running app and widget.

0 commit comments

Comments
 (0)