mirror of
https://github.com/openai/codex.git
synced 2026-05-05 05:42:33 +03:00
Promote Windows Sandbox (#11341)
1. Move Windows Sandbox NUX to right after trust directory screen 2. Don't offer read-only as an option in Sandbox NUX. Elevated/Legacy/Quit 3. Don't allow new untrusted directories. It's trust or quit 4. move experimental sandbox features to `[windows] sandbox="elevated|unelevatd"` 5. Copy tweaks = elevated -> default, non-elevated -> non-admin
This commit is contained in:
@@ -51,6 +51,7 @@ use codex_chatgpt::connectors;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config::ConstraintResult;
|
||||
use codex_core::config::types::Notifications;
|
||||
use codex_core::config::types::WindowsSandboxModeToml;
|
||||
use codex_core::config_loader::ConfigLayerStackOrdering;
|
||||
use codex_core::features::FEATURES;
|
||||
use codex_core::features::Feature;
|
||||
@@ -5286,7 +5287,7 @@ impl ChatWidget {
|
||||
let is_current =
|
||||
Self::preset_matches_current(current_approval, current_sandbox, &preset);
|
||||
let name = if preset.id == "auto" && windows_degraded_sandbox_enabled {
|
||||
"Default (non-elevated sandbox)".to_string()
|
||||
"Default (non-admin sandbox)".to_string()
|
||||
} else {
|
||||
preset.label.to_string()
|
||||
};
|
||||
@@ -5370,8 +5371,8 @@ impl ChatWidget {
|
||||
|
||||
let footer_note = show_elevate_sandbox_hint.then(|| {
|
||||
vec![
|
||||
"The non-elevated sandbox protects your files and prevents network access under most circumstances. However, it carries greater risk if prompt injected. To upgrade to the elevated sandbox, run ".dim(),
|
||||
"/setup-elevated-sandbox".cyan(),
|
||||
"The non-admin sandbox protects your files and prevents network access under most circumstances. However, it carries greater risk if prompt injected. To upgrade to the default sandbox, run ".dim(),
|
||||
"/setup-default-sandbox".cyan(),
|
||||
".".dim(),
|
||||
]
|
||||
.into()
|
||||
@@ -5714,59 +5715,24 @@ impl ChatWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
let current_approval = self.config.approval_policy.value();
|
||||
let current_sandbox = self.config.sandbox_policy.get();
|
||||
let presets = builtin_approval_presets();
|
||||
let stay_full_access = presets
|
||||
.iter()
|
||||
.find(|preset| preset.id == "full-access")
|
||||
.is_some_and(|preset| {
|
||||
Self::preset_matches_current(current_approval, current_sandbox, preset)
|
||||
});
|
||||
self.otel_manager
|
||||
.counter("codex.windows_sandbox.elevated_prompt_shown", 1, &[]);
|
||||
|
||||
let mut header = ColumnRenderable::new();
|
||||
header.push(*Box::new(
|
||||
Paragraph::new(vec![
|
||||
line!["Set Up Agent Sandbox".bold()],
|
||||
line![""],
|
||||
line!["Agent mode uses an experimental Windows sandbox that protects your files and prevents network access by default."],
|
||||
line!["Learn more: https://developers.openai.com/codex/windows"],
|
||||
line!["Set up the Codex agent sandbox to protect your files and control network access. Learn more <https://developers.openai.com/codex/windows>"],
|
||||
])
|
||||
.wrap(Wrap { trim: false }),
|
||||
));
|
||||
|
||||
let stay_label = if stay_full_access {
|
||||
"Stay in Agent Full Access".to_string()
|
||||
} else {
|
||||
"Stay in Read-Only".to_string()
|
||||
};
|
||||
let mut stay_actions = if stay_full_access {
|
||||
Vec::new()
|
||||
} else {
|
||||
presets
|
||||
.iter()
|
||||
.find(|preset| preset.id == "read-only")
|
||||
.map(|preset| {
|
||||
Self::approval_preset_actions(preset.approval, preset.sandbox.clone())
|
||||
})
|
||||
.unwrap_or_default()
|
||||
};
|
||||
stay_actions.insert(
|
||||
0,
|
||||
Box::new({
|
||||
let otel = self.otel_manager.clone();
|
||||
move |_tx| {
|
||||
otel.counter("codex.windows_sandbox.elevated_prompt_decline", 1, &[]);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let accept_otel = self.otel_manager.clone();
|
||||
let legacy_otel = self.otel_manager.clone();
|
||||
let legacy_preset = preset.clone();
|
||||
let quit_otel = self.otel_manager.clone();
|
||||
let items = vec![
|
||||
SelectionItem {
|
||||
name: "Set up agent sandbox (requires elevation)".to_string(),
|
||||
name: "Set up default sandbox (requires Administrator permissions)".to_string(),
|
||||
description: None,
|
||||
actions: vec![Box::new(move |tx| {
|
||||
accept_otel.counter("codex.windows_sandbox.elevated_prompt_accept", 1, &[]);
|
||||
@@ -5778,9 +5744,24 @@ impl ChatWidget {
|
||||
..Default::default()
|
||||
},
|
||||
SelectionItem {
|
||||
name: stay_label,
|
||||
name: "Use non-admin sandbox (higher risk if prompt injected)".to_string(),
|
||||
description: None,
|
||||
actions: stay_actions,
|
||||
actions: vec![Box::new(move |tx| {
|
||||
legacy_otel.counter("codex.windows_sandbox.fallback_use_legacy", 1, &[]);
|
||||
tx.send(AppEvent::BeginWindowsSandboxLegacySetup {
|
||||
preset: legacy_preset.clone(),
|
||||
});
|
||||
})],
|
||||
dismiss_on_select: true,
|
||||
..Default::default()
|
||||
},
|
||||
SelectionItem {
|
||||
name: "Quit".to_string(),
|
||||
description: None,
|
||||
actions: vec![Box::new(move |tx| {
|
||||
quit_otel.counter("codex.windows_sandbox.elevated_prompt_decline", 1, &[]);
|
||||
tx.send(AppEvent::Exit(ExitMode::ShutdownFirst));
|
||||
})],
|
||||
dismiss_on_select: true,
|
||||
..Default::default()
|
||||
},
|
||||
@@ -5808,23 +5789,16 @@ impl ChatWidget {
|
||||
|
||||
let _ = reason;
|
||||
|
||||
let current_approval = self.config.approval_policy.value();
|
||||
let current_sandbox = self.config.sandbox_policy.get();
|
||||
let presets = builtin_approval_presets();
|
||||
let stay_full_access = presets
|
||||
.iter()
|
||||
.find(|preset| preset.id == "full-access")
|
||||
.is_some_and(|preset| {
|
||||
Self::preset_matches_current(current_approval, current_sandbox, preset)
|
||||
});
|
||||
let mut lines = Vec::new();
|
||||
lines.push(line!["Use Non-Elevated Sandbox?".bold()]);
|
||||
lines.push(line![
|
||||
"Couldn't set up your sandbox with Administrator permissions".bold()
|
||||
]);
|
||||
lines.push(line![""]);
|
||||
lines.push(line![
|
||||
"Elevation failed. You can also use a non-elevated sandbox, which protects your files and prevents network access under most circumstances. However, it carries greater risk if prompt injected."
|
||||
"You can still use Codex in a non-admin sandbox. It carries greater risk if prompt injected."
|
||||
]);
|
||||
lines.push(line![
|
||||
"Learn more: https://developers.openai.com/codex/windows"
|
||||
"Learn more <https://developers.openai.com/codex/windows>"
|
||||
]);
|
||||
|
||||
let mut header = ColumnRenderable::new();
|
||||
@@ -5832,34 +5806,10 @@ impl ChatWidget {
|
||||
|
||||
let elevated_preset = preset.clone();
|
||||
let legacy_preset = preset;
|
||||
let stay_label = if stay_full_access {
|
||||
"Stay in Agent Full Access".to_string()
|
||||
} else {
|
||||
"Stay in Read-Only".to_string()
|
||||
};
|
||||
let mut stay_actions = if stay_full_access {
|
||||
Vec::new()
|
||||
} else {
|
||||
presets
|
||||
.iter()
|
||||
.find(|preset| preset.id == "read-only")
|
||||
.map(|preset| {
|
||||
Self::approval_preset_actions(preset.approval, preset.sandbox.clone())
|
||||
})
|
||||
.unwrap_or_default()
|
||||
};
|
||||
stay_actions.insert(
|
||||
0,
|
||||
Box::new({
|
||||
let otel = self.otel_manager.clone();
|
||||
move |_tx| {
|
||||
otel.counter("codex.windows_sandbox.fallback_stay_current", 1, &[]);
|
||||
}
|
||||
}),
|
||||
);
|
||||
let quit_otel = self.otel_manager.clone();
|
||||
let items = vec![
|
||||
SelectionItem {
|
||||
name: "Try elevated agent sandbox setup again".to_string(),
|
||||
name: "Try setting up admin sandbox again".to_string(),
|
||||
description: None,
|
||||
actions: vec![Box::new({
|
||||
let otel = self.otel_manager.clone();
|
||||
@@ -5875,16 +5825,15 @@ impl ChatWidget {
|
||||
..Default::default()
|
||||
},
|
||||
SelectionItem {
|
||||
name: "Use non-elevated agent sandbox".to_string(),
|
||||
name: "Use Codex with non-admin sandbox".to_string(),
|
||||
description: None,
|
||||
actions: vec![Box::new({
|
||||
let otel = self.otel_manager.clone();
|
||||
let preset = legacy_preset;
|
||||
move |tx| {
|
||||
otel.counter("codex.windows_sandbox.fallback_use_legacy", 1, &[]);
|
||||
tx.send(AppEvent::EnableWindowsSandboxForAgentMode {
|
||||
tx.send(AppEvent::BeginWindowsSandboxLegacySetup {
|
||||
preset: preset.clone(),
|
||||
mode: WindowsSandboxEnableMode::Legacy,
|
||||
});
|
||||
}
|
||||
})],
|
||||
@@ -5892,9 +5841,12 @@ impl ChatWidget {
|
||||
..Default::default()
|
||||
},
|
||||
SelectionItem {
|
||||
name: stay_label,
|
||||
name: "Quit".to_string(),
|
||||
description: None,
|
||||
actions: stay_actions,
|
||||
actions: vec![Box::new(move |tx| {
|
||||
quit_otel.counter("codex.windows_sandbox.fallback_stay_current", 1, &[]);
|
||||
tx.send(AppEvent::Exit(ExitMode::ShutdownFirst));
|
||||
})],
|
||||
dismiss_on_select: true,
|
||||
..Default::default()
|
||||
},
|
||||
@@ -5918,8 +5870,8 @@ 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
|
||||
pub(crate) fn maybe_prompt_windows_sandbox_enable(&mut self, show_now: bool) {
|
||||
if show_now
|
||||
&& WindowsSandboxLevel::from_config(&self.config) == WindowsSandboxLevel::Disabled
|
||||
&& let Some(preset) = builtin_approval_presets()
|
||||
.into_iter()
|
||||
@@ -5930,7 +5882,7 @@ impl ChatWidget {
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub(crate) fn maybe_prompt_windows_sandbox_enable(&mut self) {}
|
||||
pub(crate) fn maybe_prompt_windows_sandbox_enable(&mut self, _show_now: bool) {}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) fn show_windows_sandbox_setup_status(&mut self) {
|
||||
@@ -5942,7 +5894,10 @@ impl ChatWidget {
|
||||
);
|
||||
self.bottom_pane.ensure_status_indicator();
|
||||
self.bottom_pane.set_interrupt_hint_visible(false);
|
||||
self.set_status_header("Setting up agent sandbox. This can take a minute.".to_string());
|
||||
self.set_status(
|
||||
"Setting up sandbox...".to_string(),
|
||||
Some("Hang tight, this may take a few minutes".to_string()),
|
||||
);
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
@@ -5960,15 +5915,6 @@ impl ChatWidget {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub(crate) fn clear_windows_sandbox_setup_status(&mut self) {}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) fn clear_forced_auto_mode_downgrade(&mut self) {
|
||||
self.config.forced_auto_mode_downgraded_on_windows = false;
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn clear_forced_auto_mode_downgrade(&mut self) {}
|
||||
|
||||
/// Set the approval policy in the widget's config copy.
|
||||
pub(crate) fn set_approval_policy(&mut self, policy: AskForApproval) {
|
||||
if let Err(err) = self.config.approval_policy.set(policy) {
|
||||
@@ -5977,21 +5923,25 @@ impl ChatWidget {
|
||||
}
|
||||
|
||||
/// Set the sandbox policy in the widget's config copy.
|
||||
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
||||
pub(crate) fn set_sandbox_policy(&mut self, policy: SandboxPolicy) -> ConstraintResult<()> {
|
||||
#[cfg(target_os = "windows")]
|
||||
let should_clear_downgrade = !matches!(&policy, SandboxPolicy::ReadOnly)
|
||||
|| WindowsSandboxLevel::from_config(&self.config) != WindowsSandboxLevel::Disabled;
|
||||
|
||||
self.config.sandbox_policy.set(policy)?;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
if should_clear_downgrade {
|
||||
self.config.forced_auto_mode_downgraded_on_windows = false;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
||||
pub(crate) fn set_windows_sandbox_mode(&mut self, mode: Option<WindowsSandboxModeToml>) {
|
||||
self.config.windows_sandbox_mode = mode;
|
||||
#[cfg(target_os = "windows")]
|
||||
self.bottom_pane.set_windows_degraded_sandbox_active(
|
||||
codex_core::windows_sandbox::ELEVATED_SANDBOX_NUX_ENABLED
|
||||
&& matches!(
|
||||
WindowsSandboxLevel::from_config(&self.config),
|
||||
WindowsSandboxLevel::RestrictedToken
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
||||
pub(crate) fn set_feature_enabled(&mut self, feature: Feature, enabled: bool) {
|
||||
if enabled {
|
||||
|
||||
Reference in New Issue
Block a user