Always confirm before enabling full access (#32989)

## Why

Full access significantly increases the risk of data loss, leaks, or unexpected behavior, so users should explicitly confirm each time they select it.

## What changed

- Always open the full access confirmation when user-reviewed approvals are active, regardless of `notices.hide_full_access_warning`.
- Remove the persistent "don't ask again" option and its acknowledgement events.

## Testing

- Update the TUI permission tests and confirmation popup snapshot to cover repeated confirmation.

GitOrigin-RevId: 0ee827332441e13d9445349ed6a5a92dc8fef4b2
This commit is contained in:
Shijie Rao
2026-07-14 06:55:29 +00:00
committed by copyberry
parent 1c9f890c05
commit d7ba5ff955
6 changed files with 23 additions and 84 deletions
-18
View File
@@ -1832,9 +1832,6 @@ impl App {
AppEvent::SkipNextWorldWritableScan => {
self.windows_sandbox.skip_world_writable_scan_once = true;
}
AppEvent::UpdateFullAccessWarningAcknowledged(ack) => {
self.chat_widget.set_full_access_warning_acknowledged(ack);
}
AppEvent::UpdateWorldWritableWarningAcknowledged(ack) => {
self.chat_widget
.set_world_writable_warning_acknowledged(ack);
@@ -1847,21 +1844,6 @@ impl App {
self.sync_active_thread_plan_mode_reasoning_setting(app_server)
.await;
}
AppEvent::PersistFullAccessWarningAcknowledged => {
if let Err(err) = ConfigEditsBuilder::for_config(&self.config)
.set_hide_full_access_warning(/*acknowledged*/ true)
.apply()
.await
{
tracing::error!(
error = %err,
"failed to persist full access warning acknowledgement"
);
self.chat_widget.add_error_message(format!(
"Failed to save full access confirmation preference: {err}"
));
}
}
AppEvent::PersistWorldWritableWarningAcknowledged => {
if let Err(err) = ConfigEditsBuilder::for_config(&self.config)
.set_hide_world_writable_warning(/*acknowledged*/ true)
-6
View File
@@ -864,9 +864,6 @@ pub(crate) enum AppEvent {
/// Clear all persisted local memory artifacts via the app-server.
ResetMemories,
/// Update whether the full access warning prompt has been acknowledged.
UpdateFullAccessWarningAcknowledged(bool),
/// Update whether the world-writable directories warning has been acknowledged.
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
UpdateWorldWritableWarningAcknowledged(bool),
@@ -877,9 +874,6 @@ pub(crate) enum AppEvent {
/// Update the Plan-mode-specific reasoning effort in memory.
UpdatePlanModeReasoningEffort(Option<ReasoningEffort>),
/// Persist the acknowledgement flag for the full access warning prompt.
PersistFullAccessWarningAcknowledged,
/// Persist the acknowledgement flag for the world-writable directories warning.
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
PersistWorldWritableWarningAcknowledged,
@@ -304,13 +304,8 @@ impl ChatWidget {
Self::permission_profile_selection_actions,
)
};
let requires_confirmation = approvals_reviewer == ApprovalsReviewer::User
&& preset.id == "full-access"
&& !self
.config
.notices
.hide_full_access_warning
.unwrap_or(false);
let requires_confirmation =
approvals_reviewer == ApprovalsReviewer::User && preset.id == "full-access";
if requires_confirmation {
let preset = preset.clone();
return vec![Box::new(move |tx| {
@@ -426,23 +421,7 @@ impl ChatWidget {
));
let header = ColumnRenderable::with(header_children);
let mut accept_actions = profile_selection.clone().map_or_else(
|| {
Self::approval_preset_actions(
approval,
preset.permission_profile.clone(),
preset.active_permission_profile.clone(),
selected_name.clone(),
ApprovalsReviewer::User,
)
},
Self::permission_profile_selection_actions,
);
accept_actions.push(Box::new(|tx| {
tx.send(AppEvent::UpdateFullAccessWarningAcknowledged(true));
}));
let mut accept_and_remember_actions = profile_selection.map_or_else(
let accept_actions = profile_selection.map_or_else(
|| {
Self::approval_preset_actions(
approval,
@@ -454,10 +433,6 @@ impl ChatWidget {
},
Self::permission_profile_selection_actions,
);
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 {
@@ -475,13 +450,6 @@ impl ChatWidget {
dismiss_on_select: true,
..Default::default()
},
SelectionItem {
name: "Yes, and don't ask again".to_string(),
description: Some("Enable full access and remember this choice".to_string()),
actions: accept_and_remember_actions,
dismiss_on_select: true,
..Default::default()
},
SelectionItem {
name: "Cancel".to_string(),
description: Some("Go back without enabling full access".to_string()),
-4
View File
@@ -122,10 +122,6 @@ impl ChatWidget {
self.refresh_status_surfaces();
}
pub(crate) fn set_full_access_warning_acknowledged(&mut self, acknowledged: bool) {
self.config.notices.hide_full_access_warning = Some(acknowledged);
}
pub(crate) fn set_world_writable_warning_acknowledged(&mut self, acknowledged: bool) {
self.config.notices.hide_world_writable_warning = Some(acknowledged);
}
@@ -8,8 +8,7 @@ expression: popup
enabling full access. This significantly increases the risk of data loss,
leaks, or unexpected behavior.
› 1. Yes, continue anyway Apply full access for this session
2. Yes, and don't ask again Enable full access and remember this choice
3. Cancel Go back without enabling full access
› 1. Yes, continue anyway Apply full access for this session
2. Cancel Go back without enabling full access
Press enter to confirm or esc to go back
@@ -77,7 +77,6 @@ async fn approvals_selection_popup_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
chat.set_feature_enabled(Feature::GuardianApproval, /*enabled*/ false);
chat.config.notices.hide_full_access_warning = None;
chat.open_approvals_popup();
let popup = render_bottom_popup(&chat, /*width*/ 80);
@@ -260,11 +259,11 @@ async fn profile_permissions_selection_emits_auto_review_mode_event() {
}
#[tokio::test]
async fn profile_permissions_full_access_opens_confirmation() {
async fn profile_permissions_full_access_always_opens_confirmation() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
chat.config.explicit_permission_profile_mode = true;
chat.set_feature_enabled(Feature::GuardianApproval, /*enabled*/ false);
chat.config.notices.hide_full_access_warning = None;
chat.config.notices.hide_full_access_warning = Some(true);
chat.open_permissions_popup();
chat.handle_key_event(KeyEvent::from(KeyCode::Up));
@@ -295,7 +294,6 @@ async fn profile_permissions_full_access_opens_confirmation() {
async fn approvals_selection_popup_snapshot_windows_degraded_sandbox() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
chat.config.notices.hide_full_access_warning = None;
chat.set_feature_enabled(Feature::WindowsSandbox, /*enabled*/ true);
chat.set_feature_enabled(Feature::WindowsSandboxElevated, /*enabled*/ false);
@@ -752,8 +750,7 @@ async fn permissions_selection_emits_history_cell_when_selection_changes() {
chat.config.notices.hide_world_writable_warning = Some(true);
chat.set_windows_sandbox_mode(Some(WindowsSandboxModeToml::Unelevated));
}
chat.config.notices.hide_full_access_warning = Some(true);
chat.set_feature_enabled(Feature::GuardianApproval, /*enabled*/ true);
chat.open_permissions_popup();
chat.handle_key_event(KeyEvent::from(KeyCode::Down));
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
@@ -780,14 +777,26 @@ async fn permissions_selection_history_snapshot_after_mode_switch() {
chat.set_windows_sandbox_mode(Some(WindowsSandboxModeToml::Unelevated));
}
chat.set_feature_enabled(Feature::GuardianApproval, /*enabled*/ false);
chat.config.notices.hide_full_access_warning = Some(true);
chat.open_permissions_popup();
chat.handle_key_event(KeyEvent::from(KeyCode::Down));
#[cfg(target_os = "windows")]
chat.handle_key_event(KeyEvent::from(KeyCode::Down));
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
let (preset, return_to_permissions, profile_selection) =
std::iter::from_fn(|| rx.try_recv().ok())
.find_map(|event| match event {
AppEvent::OpenFullAccessConfirmation {
preset,
return_to_permissions,
profile_selection,
} => Some((preset, return_to_permissions, profile_selection)),
_ => None,
})
.expect("expected full access confirmation event");
chat.open_full_access_confirmation(preset, return_to_permissions, profile_selection);
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
let cells = drain_insert_history(&mut rx);
assert_eq!(cells.len(), 1, "expected one mode-switch history cell");
assert_chatwidget_snapshot!(
@@ -804,7 +813,6 @@ async fn permissions_selection_history_snapshot_full_access_to_default() {
chat.config.notices.hide_world_writable_warning = Some(true);
chat.set_windows_sandbox_mode(Some(WindowsSandboxModeToml::Unelevated));
}
chat.config.notices.hide_full_access_warning = Some(true);
chat.config
.permissions
.approval_policy
@@ -882,8 +890,6 @@ async fn permissions_selection_hides_auto_review_when_feature_disabled() {
chat.set_windows_sandbox_mode(Some(WindowsSandboxModeToml::Unelevated));
}
chat.set_feature_enabled(Feature::GuardianApproval, /*enabled*/ false);
chat.config.notices.hide_full_access_warning = Some(true);
chat.open_permissions_popup();
let popup = render_bottom_popup(&chat, /*width*/ 120);
@@ -903,7 +909,6 @@ async fn permissions_selection_hides_auto_review_when_feature_disabled_even_if_a
chat.set_windows_sandbox_mode(Some(WindowsSandboxModeToml::Unelevated));
}
chat.set_feature_enabled(Feature::GuardianApproval, /*enabled*/ false);
chat.config.notices.hide_full_access_warning = Some(true);
chat.config.approvals_reviewer = ApprovalsReviewer::AutoReview;
chat.config
.permissions
@@ -932,7 +937,6 @@ async fn permissions_selection_marks_auto_review_current_after_session_configure
chat.config.notices.hide_world_writable_warning = Some(true);
chat.set_windows_sandbox_mode(Some(WindowsSandboxModeToml::Unelevated));
}
chat.config.notices.hide_full_access_warning = Some(true);
let _ = chat
.config
.features
@@ -978,7 +982,6 @@ async fn permissions_selection_marks_auto_review_current_with_custom_workspace_w
chat.config.notices.hide_world_writable_warning = Some(true);
chat.set_windows_sandbox_mode(Some(WindowsSandboxModeToml::Unelevated));
}
chat.config.notices.hide_full_access_warning = Some(true);
let _ = chat
.config
.features
@@ -1028,7 +1031,6 @@ async fn permissions_selection_can_disable_auto_review() {
chat.config.notices.hide_world_writable_warning = Some(true);
chat.set_windows_sandbox_mode(Some(WindowsSandboxModeToml::Unelevated));
}
chat.config.notices.hide_full_access_warning = Some(true);
chat.set_feature_enabled(Feature::GuardianApproval, /*enabled*/ true);
chat.config
.permissions
@@ -1039,6 +1041,7 @@ async fn permissions_selection_can_disable_auto_review() {
.permissions
.set_permission_profile(PermissionProfile::workspace_write())
.expect("set permission profile");
chat.set_approvals_reviewer(ApprovalsReviewer::AutoReview);
chat.open_permissions_popup();
chat.handle_key_event(KeyEvent::from(KeyCode::Up));
@@ -1068,7 +1071,6 @@ async fn permissions_selection_sends_approvals_reviewer_in_override_turn_context
chat.config.notices.hide_world_writable_warning = Some(true);
chat.set_windows_sandbox_mode(Some(WindowsSandboxModeToml::Unelevated));
}
chat.config.notices.hide_full_access_warning = Some(true);
chat.set_feature_enabled(Feature::GuardianApproval, /*enabled*/ true);
chat.config
.permissions
@@ -1151,8 +1153,6 @@ async fn permissions_full_access_history_cell_emitted_only_after_confirmation()
chat.set_windows_sandbox_mode(Some(WindowsSandboxModeToml::Unelevated));
}
chat.set_feature_enabled(Feature::GuardianApproval, /*enabled*/ false);
chat.config.notices.hide_full_access_warning = None;
chat.open_permissions_popup();
chat.handle_key_event(KeyEvent::from(KeyCode::Down));
#[cfg(target_os = "windows")]