fix(tui) Consolidate Permissions logic

This commit is contained in:
Dylan Hurd
2026-02-15 15:17:29 -08:00
parent bb21e87ecb
commit 79468fc872
7 changed files with 313 additions and 184 deletions

View File

@@ -1747,12 +1747,8 @@ impl App {
AppEvent::OpenAllModelsPopup { models } => {
self.chat_widget.open_all_models_popup(models);
}
AppEvent::OpenFullAccessConfirmation {
preset,
return_to_permissions,
} => {
self.chat_widget
.open_full_access_confirmation(preset, return_to_permissions);
AppEvent::OpenFullAccessConfirmation { preset } => {
self.chat_widget.open_full_access_confirmation(preset);
}
AppEvent::OpenWorldWritableWarningConfirmation {
preset,

View File

@@ -16,11 +16,11 @@ use codex_core::protocol::RateLimitSnapshot;
use codex_file_search::FileMatch;
use codex_protocol::ThreadId;
use codex_protocol::openai_models::ModelPreset;
use codex_utils_approval_presets::ApprovalPreset;
use crate::bottom_pane::ApprovalRequest;
use crate::bottom_pane::StatusLineItem;
use crate::history_cell::HistoryCell;
use crate::permissions::PermissionsPreset;
use codex_core::features::Feature;
use codex_core::protocol::AskForApproval;
@@ -171,8 +171,7 @@ pub(crate) enum AppEvent {
/// Open the confirmation prompt before enabling full access mode.
OpenFullAccessConfirmation {
preset: ApprovalPreset,
return_to_permissions: bool,
preset: PermissionsPreset,
},
/// Open the Windows world-writable directories warning.
@@ -181,7 +180,7 @@ pub(crate) enum AppEvent {
/// policy change and only acknowledges/dismisses the warning.
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
OpenWorldWritableWarningConfirmation {
preset: Option<ApprovalPreset>,
preset: Option<PermissionsPreset>,
/// Up to 3 sample world-writable directories to display in the warning.
sample_paths: Vec<String>,
/// If there are more than `sample_paths`, this carries the remaining count.
@@ -193,25 +192,25 @@ pub(crate) enum AppEvent {
/// Prompt to enable the Windows sandbox feature before using Agent mode.
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
OpenWindowsSandboxEnablePrompt {
preset: ApprovalPreset,
preset: PermissionsPreset,
},
/// Open the Windows sandbox fallback prompt after declining or failing elevation.
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
OpenWindowsSandboxFallbackPrompt {
preset: ApprovalPreset,
preset: PermissionsPreset,
},
/// Begin the elevated Windows sandbox setup flow.
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
BeginWindowsSandboxElevatedSetup {
preset: ApprovalPreset,
preset: PermissionsPreset,
},
/// Begin the non-elevated Windows sandbox setup flow.
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
BeginWindowsSandboxLegacySetup {
preset: ApprovalPreset,
preset: PermissionsPreset,
},
/// Begin a non-elevated grant of read access for an additional directory.
@@ -230,7 +229,7 @@ pub(crate) enum AppEvent {
/// Enable the Windows sandbox feature and switch to Agent mode.
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
EnableWindowsSandboxForAgentMode {
preset: ApprovalPreset,
preset: PermissionsPreset,
mode: WindowsSandboxEnableMode,
},

View File

@@ -39,6 +39,11 @@ use std::time::Instant;
use crate::bottom_pane::StatusLineItem;
use crate::bottom_pane::StatusLineSetupView;
use crate::permissions::PermissionsPreset;
#[cfg(target_os = "windows")]
use crate::permissions::builtin_permissions_presets;
use crate::permissions::visible_permissions_options;
use crate::permissions::windows_degraded_sandbox_enabled;
use crate::status::RateLimitWindowDisplay;
use crate::status::format_directory_display;
use crate::status::format_tokens_compact;
@@ -242,8 +247,6 @@ use codex_protocol::openai_models::InputModality;
use codex_protocol::openai_models::ModelPreset;
use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
use codex_protocol::plan_tool::UpdatePlanArgs;
use codex_utils_approval_presets::ApprovalPreset;
use codex_utils_approval_presets::builtin_approval_presets;
use strum::IntoEnumIterator;
const USER_SHELL_COMMAND_HELP_TITLE: &str = "Prefix a command with ! to run it locally";
@@ -3338,7 +3341,7 @@ impl ChatWidget {
return;
}
let Some(preset) = builtin_approval_presets()
let Some(preset) = builtin_permissions_presets()
.into_iter()
.find(|preset| preset.id == "auto")
else {
@@ -5335,125 +5338,14 @@ impl ChatWidget {
/// Open a popup to choose the permissions mode (approval policy + sandbox policy).
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 mut items: Vec<SelectionItem> = Vec::new();
let presets: Vec<ApprovalPreset> = builtin_approval_presets();
#[cfg(target_os = "windows")]
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;
let items: Vec<SelectionItem> = visible_permissions_options(&self.config);
let windows_degraded_sandbox_enabled = windows_degraded_sandbox_enabled(&self.config);
let show_elevate_sandbox_hint = codex_core::windows_sandbox::ELEVATED_SANDBOX_NUX_ENABLED
&& windows_degraded_sandbox_enabled
&& presets.iter().any(|preset| preset.id == "auto");
for preset in presets.into_iter() {
if !include_read_only && preset.id == "read-only" {
continue;
}
let is_current =
Self::preset_matches_current(current_approval, current_sandbox, &preset);
let name = if preset.id == "auto" && windows_degraded_sandbox_enabled {
"Default (non-admin sandbox)".to_string()
} else {
preset.label.to_string()
};
let description = Some(preset.description.replace(" (Identical to Agent mode)", ""));
let disabled_reason = match self
.config
.permissions
.approval_policy
.can_set(&preset.approval)
{
Ok(()) => None,
Err(err) => Some(err.to_string()),
};
let requires_confirmation = preset.id == "full-access"
&& !self
.config
.notices
.hide_full_access_warning
.unwrap_or(false);
let actions: Vec<SelectionAction> = if requires_confirmation {
let preset_clone = preset.clone();
vec![Box::new(move |tx| {
tx.send(AppEvent::OpenFullAccessConfirmation {
preset: preset_clone.clone(),
return_to_permissions: !include_read_only,
});
})]
} else if preset.id == "auto" {
#[cfg(target_os = "windows")]
{
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(
self.config.codex_home.as_path(),
)
{
vec![Box::new(move |tx| {
tx.send(AppEvent::EnableWindowsSandboxForAgentMode {
preset: preset_clone.clone(),
mode: WindowsSandboxEnableMode::Elevated,
});
})]
} else {
vec![Box::new(move |tx| {
tx.send(AppEvent::OpenWindowsSandboxEnablePrompt {
preset: preset_clone.clone(),
});
})]
}
} else if let Some((sample_paths, extra_count, failed_scan)) =
self.world_writable_warning_details()
{
let preset_clone = preset.clone();
vec![Box::new(move |tx| {
tx.send(AppEvent::OpenWorldWritableWarningConfirmation {
preset: Some(preset_clone.clone()),
sample_paths: sample_paths.clone(),
extra_count,
failed_scan,
});
})]
} else {
Self::approval_preset_actions(
preset.approval,
preset.sandbox.clone(),
name.clone(),
)
}
}
#[cfg(not(target_os = "windows"))]
{
Self::approval_preset_actions(
preset.approval,
preset.sandbox.clone(),
name.clone(),
)
}
} else {
Self::approval_preset_actions(preset.approval, preset.sandbox.clone(), name.clone())
};
items.push(SelectionItem {
name,
description,
is_current,
actions,
dismiss_on_select: true,
disabled_reason,
..Default::default()
});
}
&& items
.iter()
.any(|item| item.name == "Default (non-admin sandbox)");
let footer_note = show_elevate_sandbox_hint.then(|| {
vec![
@@ -5519,14 +5411,6 @@ impl ChatWidget {
})]
}
fn preset_matches_current(
current_approval: AskForApproval,
current_sandbox: &SandboxPolicy,
preset: &ApprovalPreset,
) -> bool {
current_approval == preset.approval && *current_sandbox == preset.sandbox
}
#[cfg(target_os = "windows")]
pub(crate) fn world_writable_warning_details(&self) -> Option<(Vec<String>, usize, bool)> {
if self
@@ -5557,14 +5441,7 @@ impl ChatWidget {
None
}
pub(crate) fn open_full_access_confirmation(
&mut self,
preset: ApprovalPreset,
return_to_permissions: bool,
) {
let selected_name = preset.label.to_string();
let approval = preset.approval;
let sandbox = preset.sandbox;
pub(crate) fn open_full_access_confirmation(&mut self, preset: PermissionsPreset) {
let mut header_children: Vec<Box<dyn Renderable>> = Vec::new();
let title_line = Line::from("Enable full access?").bold();
let info_line = Line::from(vec![
@@ -5579,25 +5456,27 @@ impl ChatWidget {
));
let header = ColumnRenderable::with(header_children);
let mut accept_actions =
Self::approval_preset_actions(approval, sandbox.clone(), selected_name.clone());
let mut accept_actions = Self::approval_preset_actions(
preset.approval,
preset.sandbox.clone(),
preset.label.to_string(),
);
accept_actions.push(Box::new(|tx| {
tx.send(AppEvent::UpdateFullAccessWarningAcknowledged(true));
}));
let mut accept_and_remember_actions =
Self::approval_preset_actions(approval, sandbox, selected_name);
let mut accept_and_remember_actions = Self::approval_preset_actions(
preset.approval,
preset.sandbox.clone(),
preset.label.to_string(),
);
accept_and_remember_actions.push(Box::new(|tx| {
tx.send(AppEvent::UpdateFullAccessWarningAcknowledged(true));
tx.send(AppEvent::PersistFullAccessWarningAcknowledged);
}));
let deny_actions: Vec<SelectionAction> = vec![Box::new(move |tx| {
if return_to_permissions {
tx.send(AppEvent::OpenPermissionsPopup);
} else {
tx.send(AppEvent::OpenApprovalsPopup);
}
tx.send(AppEvent::OpenPermissionsPopup);
})];
let items = vec![
@@ -5635,7 +5514,7 @@ impl ChatWidget {
#[cfg(target_os = "windows")]
pub(crate) fn open_world_writable_warning_confirmation(
&mut self,
preset: Option<ApprovalPreset>,
preset: Option<PermissionsPreset>,
sample_paths: Vec<String>,
extra_count: usize,
failed_scan: bool,
@@ -5744,7 +5623,7 @@ impl ChatWidget {
#[cfg(not(target_os = "windows"))]
pub(crate) fn open_world_writable_warning_confirmation(
&mut self,
_preset: Option<ApprovalPreset>,
_preset: Option<PermissionsPreset>,
_sample_paths: Vec<String>,
_extra_count: usize,
_failed_scan: bool,
@@ -5752,7 +5631,7 @@ impl ChatWidget {
}
#[cfg(target_os = "windows")]
pub(crate) fn open_windows_sandbox_enable_prompt(&mut self, preset: ApprovalPreset) {
pub(crate) fn open_windows_sandbox_enable_prompt(&mut self, preset: PermissionsPreset) {
use ratatui_macros::line;
if !codex_core::windows_sandbox::ELEVATED_SANDBOX_NUX_ENABLED {
@@ -5864,10 +5743,10 @@ impl ChatWidget {
}
#[cfg(not(target_os = "windows"))]
pub(crate) fn open_windows_sandbox_enable_prompt(&mut self, _preset: ApprovalPreset) {}
pub(crate) fn open_windows_sandbox_enable_prompt(&mut self, _preset: PermissionsPreset) {}
#[cfg(target_os = "windows")]
pub(crate) fn open_windows_sandbox_fallback_prompt(&mut self, preset: ApprovalPreset) {
pub(crate) fn open_windows_sandbox_fallback_prompt(&mut self, preset: PermissionsPreset) {
use ratatui_macros::line;
let mut lines = Vec::new();
@@ -5943,13 +5822,13 @@ impl ChatWidget {
}
#[cfg(not(target_os = "windows"))]
pub(crate) fn open_windows_sandbox_fallback_prompt(&mut self, _preset: ApprovalPreset) {}
pub(crate) fn open_windows_sandbox_fallback_prompt(&mut self, _preset: PermissionsPreset) {}
#[cfg(target_os = "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()
&& let Some(preset) = builtin_permissions_presets()
.into_iter()
.find(|preset| preset.id == "auto")
{

View File

@@ -1,13 +1,12 @@
---
source: tui/src/chatwidget/tests.rs
assertion_line: 3092
expression: popup
---
Update Model Permissions
1. Default Codex can read and edit files in the current workspace, and
run commands. Approval is required to access the internet or
edit other files.
edit other files. (Identical to Agent mode)
2. Full Access Codex can edit files outside this workspace and access the
internet without asking for approval. Exercise caution when
using.

View File

@@ -12,6 +12,7 @@ use crate::bottom_pane::FeedbackAudience;
use crate::bottom_pane::LocalImageAttachment;
use crate::bottom_pane::MentionBinding;
use crate::history_cell::UserHistoryCell;
use crate::permissions::builtin_permissions_presets;
use crate::test_backend::VT100Backend;
use crate::tui::FrameRequester;
use assert_matches::assert_matches;
@@ -91,7 +92,6 @@ use codex_protocol::protocol::SkillScope;
use codex_protocol::user_input::TextElement;
use codex_protocol::user_input::UserInput;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_approval_presets::builtin_approval_presets;
use crossterm::event::KeyCode;
use crossterm::event::KeyEvent;
use crossterm::event::KeyModifiers;
@@ -5064,10 +5064,11 @@ async fn approvals_selection_popup_snapshot_windows_degraded_sandbox() {
#[tokio::test]
async fn preset_matching_requires_exact_workspace_write_settings() {
let preset = builtin_approval_presets()
let preset = builtin_permissions_presets()
.into_iter()
.find(|p| p.id == "auto")
.expect("auto preset exists");
let mut config = test_config().await;
let current_sandbox = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![AbsolutePathBuf::try_from("C:\\extra").unwrap()],
read_only_access: Default::default(),
@@ -5075,13 +5076,28 @@ async fn preset_matching_requires_exact_workspace_write_settings() {
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
};
config
.permissions
.approval_policy
.set(AskForApproval::OnRequest)
.unwrap();
config
.permissions
.sandbox_policy
.set(current_sandbox)
.unwrap();
assert!(
!ChatWidget::preset_matches_current(AskForApproval::OnRequest, &current_sandbox, &preset),
!preset.to_selection_item(&config).is_current,
"WorkspaceWrite with extra roots should not match the Default preset"
);
config
.permissions
.approval_policy
.set(AskForApproval::Never)
.unwrap();
assert!(
!ChatWidget::preset_matches_current(AskForApproval::Never, &current_sandbox, &preset),
!preset.to_selection_item(&config).is_current,
"approval mismatch should prevent matching the preset"
);
}
@@ -5090,11 +5106,11 @@ async fn preset_matching_requires_exact_workspace_write_settings() {
async fn full_access_confirmation_popup_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await;
let preset = builtin_approval_presets()
let preset = builtin_permissions_presets()
.into_iter()
.find(|preset| preset.id == "full-access")
.find(|selection_item| selection_item.id == "full-access")
.expect("full access preset");
chat.open_full_access_confirmation(preset, false);
chat.open_full_access_confirmation(preset);
let popup = render_bottom_popup(&chat, 80);
assert_snapshot!("full_access_confirmation_popup", popup);
@@ -5105,7 +5121,7 @@ async fn full_access_confirmation_popup_snapshot() {
async fn windows_auto_mode_prompt_requests_enabling_sandbox_feature() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await;
let preset = builtin_approval_presets()
let preset = builtin_permissions_presets()
.into_iter()
.find(|preset| preset.id == "auto")
.expect("auto preset");
@@ -5624,11 +5640,8 @@ async fn permissions_full_access_history_cell_emitted_only_after_confirmation()
AppEvent::InsertHistoryCell(cell) => {
cells_before_confirmation.push(cell.display_lines(80));
}
AppEvent::OpenFullAccessConfirmation {
preset,
return_to_permissions,
} => {
open_confirmation_event = Some((preset, return_to_permissions));
AppEvent::OpenFullAccessConfirmation { preset } => {
open_confirmation_event = Some(preset);
}
_ => {}
}
@@ -5639,9 +5652,8 @@ async fn permissions_full_access_history_cell_emitted_only_after_confirmation()
"did not expect history cell before confirming full access"
);
}
let (preset, return_to_permissions) =
open_confirmation_event.expect("expected full access confirmation event");
chat.open_full_access_confirmation(preset, return_to_permissions);
let preset = open_confirmation_event.expect("expected full access confirmation event");
chat.open_full_access_confirmation(preset.clone());
let popup = render_bottom_popup(&chat, 80);
assert!(

View File

@@ -91,6 +91,7 @@ mod notifications;
pub mod onboarding;
mod oss_selection;
mod pager_overlay;
mod permissions;
pub mod public_widgets;
mod render;
mod resume_picker;

View File

@@ -0,0 +1,243 @@
use crate::app_event::AppEvent;
#[cfg(target_os = "windows")]
use crate::app_event::WindowsSandboxEnableMode;
use crate::app_event_sender::AppEventSender;
use crate::bottom_pane::SelectionAction;
use crate::bottom_pane::SelectionItem;
use crate::history_cell;
use codex_core::config::Config;
use codex_core::protocol::AskForApproval;
use codex_core::protocol::Op;
use codex_core::protocol::SandboxPolicy;
use codex_core::protocol_config_types::WindowsSandboxLevel;
/// A simple preset pairing an approval policy with a sandbox policy.
#[derive(Debug, Clone)]
pub struct PermissionsPreset {
/// Stable identifier for the preset.
pub id: &'static str,
/// Display label shown in UIs.
pub label: &'static str,
/// Short human description shown next to the label in UIs.
pub description: &'static str,
/// Approval policy to apply.
pub approval: AskForApproval,
/// Sandbox policy to apply.
pub sandbox: SandboxPolicy,
}
/// Built-in list of approval presets that pair approval and sandbox policy.
pub fn builtin_permissions_presets() -> Vec<PermissionsPreset> {
vec![
PermissionsPreset {
id: "read-only",
label: "Read Only",
description: "Codex can read files in the current workspace. Approval is required to edit files or access the internet.",
approval: AskForApproval::OnRequest,
sandbox: SandboxPolicy::new_read_only_policy(),
},
PermissionsPreset {
id: "auto",
label: "Default",
description: "Codex can read and edit files in the current workspace, and run commands. Approval is required to access the internet or edit other files. (Identical to Agent mode)",
approval: AskForApproval::OnRequest,
sandbox: SandboxPolicy::new_workspace_write_policy(),
},
PermissionsPreset {
id: "full-access",
label: "Full Access",
description: "Codex can edit files outside this workspace and access the internet without asking for approval. Exercise caution when using.",
approval: AskForApproval::Never,
sandbox: SandboxPolicy::DangerFullAccess,
},
]
}
pub(crate) fn visible_permissions_options(config: &Config) -> Vec<SelectionItem> {
builtin_permissions_presets()
.into_iter()
.filter(|preset| preset.is_visible(config))
.map(|preset| preset.to_selection_item(config))
.collect()
}
impl PermissionsPreset {
pub(crate) fn is_visible(&self, config: &Config) -> bool {
match self.id {
"read-only" => cfg!(target_os = "windows"),
"auto" => {
!cfg!(target_os = "windows")
|| codex_core::windows_sandbox::windows_sandbox_level_from_config(config)
== WindowsSandboxLevel::Disabled
}
"full-access" => true,
_ => false,
}
}
pub(crate) fn to_selection_item(&self, config: &Config) -> SelectionItem {
let name = if self.id == "auto" && windows_degraded_sandbox_enabled(config) {
"Default (non-admin sandbox)".to_string()
} else {
self.label.to_string()
};
SelectionItem {
name,
description: Some(self.description.to_string()),
is_current: self.is_current(config),
actions: self.actions(config),
dismiss_on_select: true,
disabled_reason: self.disabled_reason(config),
..Default::default()
}
}
fn is_current(&self, config: &Config) -> bool {
self.approval == config.permissions.approval_policy.value()
&& self.sandbox == *config.permissions.sandbox_policy.get()
}
fn disabled_reason(&self, config: &Config) -> Option<String> {
let disabled_sandbox_reason = match config.permissions.sandbox_policy.can_set(&self.sandbox)
{
Ok(()) => None,
Err(err) => Some(err.to_string()),
};
if disabled_sandbox_reason.is_some() {
return disabled_sandbox_reason;
}
let disabled_approval_reason =
match config.permissions.approval_policy.can_set(&self.approval) {
Ok(()) => None,
Err(err) => Some(err.to_string()),
};
if disabled_approval_reason.is_some() {
return disabled_approval_reason;
}
None
}
pub(crate) fn actions(&self, config: &Config) -> Vec<SelectionAction> {
let requires_full_access_confirmation =
self.id == "full-access" && !config.notices.hide_full_access_warning.unwrap_or(false);
if requires_full_access_confirmation {
let preset = self.clone();
return vec![Box::new(move |tx: &AppEventSender| {
tx.send(AppEvent::OpenFullAccessConfirmation {
preset: preset.clone(),
});
})];
}
#[cfg(target_os = "windows")]
{
if let Some(actions) = windows_permissions_actions(self, config) {
return actions;
}
}
let approval = self.approval;
let sandbox = self.sandbox.clone();
let label = self.label.to_string();
vec![Box::new(move |tx: &AppEventSender| {
let sandbox_clone = sandbox.clone();
tx.send(AppEvent::CodexOp(Op::OverrideTurnContext {
cwd: None,
approval_policy: Some(approval),
sandbox_policy: Some(sandbox_clone.clone()),
windows_sandbox_level: None,
model: None,
effort: None,
summary: None,
collaboration_mode: None,
personality: None,
}));
tx.send(AppEvent::UpdateAskForApprovalPolicy(approval));
tx.send(AppEvent::UpdateSandboxPolicy(sandbox_clone));
tx.send(AppEvent::InsertHistoryCell(Box::new(
history_cell::new_info_event(format!("Permissions updated to {label}"), None),
)));
})]
}
}
/// Handle windows-specific actions for auto preset. Returns Some when it should take precedence over the approval preset actions.
#[cfg(target_os = "windows")]
fn windows_permissions_actions(
preset: &PermissionsPreset,
config: &Config,
) -> Option<Vec<SelectionAction>> {
if preset.id != "auto" {
return None;
}
if codex_core::windows_sandbox::windows_sandbox_level_from_config(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(config.codex_home.as_path())
{
Some(vec![Box::new(move |tx| {
tx.send(AppEvent::EnableWindowsSandboxForAgentMode {
preset: preset_clone.clone(),
mode: WindowsSandboxEnableMode::Elevated,
});
})])
} else {
Some(vec![Box::new(move |tx| {
tx.send(AppEvent::OpenWindowsSandboxEnablePrompt {
preset: preset_clone.clone(),
});
})])
}
} else if let Some((sample_paths, extra_count, failed_scan)) =
world_writable_warning_details(config)
{
let preset_clone = preset.clone();
Some(vec![Box::new(move |tx| {
tx.send(AppEvent::OpenWorldWritableWarningConfirmation {
preset: Some(preset_clone.clone()),
sample_paths: sample_paths.clone(),
extra_count,
failed_scan,
});
})])
} else {
None
}
}
#[cfg(target_os = "windows")]
pub(crate) fn windows_degraded_sandbox_enabled(config: &Config) -> bool {
let windows_sandbox_level =
codex_core::windows_sandbox::windows_sandbox_level_from_config(config);
matches!(windows_sandbox_level, WindowsSandboxLevel::RestrictedToken);
}
#[cfg(target_os = "windows")]
fn world_writable_warning_details(config: &Config) -> Option<(Vec<String>, usize, bool)> {
if config.notices.hide_world_writable_warning.unwrap_or(false) {
return None;
}
let cwd = config.cwd.clone();
let env_map: std::collections::HashMap<String, String> = std::env::vars().collect();
match codex_windows_sandbox::apply_world_writable_scan_and_denies(
config.codex_home.as_path(),
cwd.as_path(),
&env_map,
config.permissions.sandbox_policy.get(),
Some(config.codex_home.as_path()),
) {
Ok(_) => None,
Err(_) => Some((Vec::new(), 0, true)),
}
}
#[cfg(not(target_os = "windows"))]
pub(crate) fn windows_degraded_sandbox_enabled(_config: &Config) -> bool {
false
}