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:
iceweasel-oai
2026-02-11 11:48:33 -08:00
committed by GitHub
parent 24e6adbda5
commit 87279de434
21 changed files with 727 additions and 395 deletions

View File

@@ -1,6 +1,9 @@
use codex_core::AuthManager;
use codex_core::config::Config;
use codex_core::git_info::get_git_repo_root;
#[cfg(target_os = "windows")]
use codex_core::windows_sandbox::WindowsSandboxLevelExt;
#[cfg(target_os = "windows")]
use codex_protocol::config_types::WindowsSandboxLevel;
use crossterm::event::KeyCode;
use crossterm::event::KeyEvent;
use crossterm::event::KeyEventKind;
@@ -82,7 +85,7 @@ impl OnboardingScreen {
let cwd = config.cwd.clone();
let forced_chatgpt_workspace_id = config.forced_chatgpt_workspace_id.clone();
let forced_login_method = config.forced_login_method;
let codex_home = config.codex_home;
let codex_home = config.codex_home.clone();
let cli_auth_credentials_store_mode = config.cli_auth_credentials_store_mode;
let mut steps: Vec<Step> = Vec::new();
steps.push(Step::Welcome(WelcomeWidget::new(
@@ -109,18 +112,18 @@ impl OnboardingScreen {
animations_enabled: config.animations,
}))
}
let is_git_repo = get_git_repo_root(&cwd).is_some();
let highlighted = if is_git_repo {
TrustDirectorySelection::Trust
} else {
// Default to not trusting the directory if it's not a git repo.
TrustDirectorySelection::DontTrust
};
#[cfg(target_os = "windows")]
let show_windows_create_sandbox_hint =
WindowsSandboxLevel::from_config(&config) == WindowsSandboxLevel::Disabled;
#[cfg(not(target_os = "windows"))]
let show_windows_create_sandbox_hint = false;
let highlighted = TrustDirectorySelection::Trust;
if show_trust_screen {
steps.push(Step::TrustDirectory(TrustDirectoryWidget {
cwd,
codex_home,
is_git_repo,
show_windows_create_sandbox_hint,
should_quit: false,
selection: None,
highlighted,
error: None,
@@ -253,6 +256,16 @@ impl KeyboardHandler for OnboardingScreen {
if let Some(active_step) = self.current_steps_mut().into_iter().last() {
active_step.handle_key_event(key_event);
}
if self.steps.iter().any(|step| {
if let Step::TrustDirectory(widget) = step {
widget.should_quit()
} else {
false
}
}) {
self.should_exit = true;
self.is_done = true;
}
}
self.request_frame.schedule_frame();
}

View File

@@ -1,14 +1,14 @@
---
source: tui/src/onboarding/trust_directory.rs
assertion_line: 218
expression: terminal.backend()
---
> You are running Codex in /workspace/project
> You are in /workspace/project
Since this folder is version controlled, you may wish to allow Codex
to work in this folder without asking for approval.
Do you trust the contents of this directory? Working with untrusted
contents comes with higher risk of prompt injection.
1. Yes, allow Codex to work in this folder without asking for
approval
2. No, ask me to approve edits and commands
1. Yes, continue
2. No, quit
Press enter to continue

View File

@@ -27,7 +27,8 @@ use super::onboarding_screen::StepState;
pub(crate) struct TrustDirectoryWidget {
pub codex_home: PathBuf,
pub cwd: PathBuf,
pub is_git_repo: bool,
pub show_windows_create_sandbox_hint: bool,
pub should_quit: bool,
pub selection: Option<TrustDirectorySelection>,
pub highlighted: TrustDirectorySelection,
pub error: Option<String>,
@@ -36,7 +37,7 @@ pub(crate) struct TrustDirectoryWidget {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TrustDirectorySelection {
Trust,
DontTrust,
Quit,
}
impl WidgetRef for &TrustDirectoryWidget {
@@ -45,44 +46,24 @@ impl WidgetRef for &TrustDirectoryWidget {
column.push(Line::from(vec![
"> ".into(),
"You are running Codex in ".bold(),
"You are in ".bold(),
self.cwd.to_string_lossy().to_string().into(),
]));
column.push("");
let guidance = if self.is_git_repo {
"Since this folder is version controlled, you may wish to allow Codex to work in this folder without asking for approval."
} else {
"Since this folder is not version controlled, we recommend requiring approval of all edits and commands."
};
column.push(
Paragraph::new(guidance.to_string())
Paragraph::new(
"Do you trust the contents of this directory? Working with untrusted contents comes with higher risk of prompt injection.".to_string(),
)
.wrap(Wrap { trim: true })
.inset(Insets::tlbr(0, 2, 0, 0)),
);
column.push("");
let mut options: Vec<(&str, TrustDirectorySelection)> = Vec::new();
if self.is_git_repo {
options.push((
"Yes, allow Codex to work in this folder without asking for approval",
TrustDirectorySelection::Trust,
));
options.push((
"No, ask me to approve edits and commands",
TrustDirectorySelection::DontTrust,
));
} else {
options.push((
"Allow Codex to work in this folder without asking for approval",
TrustDirectorySelection::Trust,
));
options.push((
"Require approval of edits and commands",
TrustDirectorySelection::DontTrust,
));
}
let options: Vec<(&str, TrustDirectorySelection)> = vec![
("Yes, continue", TrustDirectorySelection::Trust),
("No, quit", TrustDirectorySelection::Quit),
];
for (idx, (text, selection)) in options.iter().enumerate() {
column.push(selection_option_row(
@@ -108,7 +89,11 @@ impl WidgetRef for &TrustDirectoryWidget {
Line::from(vec![
"Press ".dim(),
key_hint::plain(KeyCode::Enter).into(),
" to continue".dim(),
if self.show_windows_create_sandbox_hint {
" to continue and create a sandbox...".dim()
} else {
" to continue".dim()
},
])
.inset(Insets::tlbr(0, 2, 0, 0)),
);
@@ -128,13 +113,13 @@ impl KeyboardHandler for TrustDirectoryWidget {
self.highlighted = TrustDirectorySelection::Trust;
}
KeyCode::Down | KeyCode::Char('j') => {
self.highlighted = TrustDirectorySelection::DontTrust;
self.highlighted = TrustDirectorySelection::Quit;
}
KeyCode::Char('1') | KeyCode::Char('y') => self.handle_trust(),
KeyCode::Char('2') | KeyCode::Char('n') => self.handle_dont_trust(),
KeyCode::Char('2') | KeyCode::Char('n') => self.handle_quit(),
KeyCode::Enter => match self.highlighted {
TrustDirectorySelection::Trust => self.handle_trust(),
TrustDirectorySelection::DontTrust => self.handle_dont_trust(),
TrustDirectorySelection::Quit => self.handle_quit(),
},
_ => {}
}
@@ -143,9 +128,10 @@ impl KeyboardHandler for TrustDirectoryWidget {
impl StepStateProvider for TrustDirectoryWidget {
fn get_step_state(&self) -> StepState {
match self.selection {
Some(_) => StepState::Complete,
None => StepState::InProgress,
if self.selection.is_some() || self.should_quit {
StepState::Complete
} else {
StepState::InProgress
}
}
}
@@ -162,19 +148,13 @@ impl TrustDirectoryWidget {
self.selection = Some(TrustDirectorySelection::Trust);
}
fn handle_dont_trust(&mut self) {
self.highlighted = TrustDirectorySelection::DontTrust;
let target =
resolve_root_git_project_for_trust(&self.cwd).unwrap_or_else(|| self.cwd.clone());
if let Err(e) = set_project_trust_level(&self.codex_home, &target, TrustLevel::Untrusted) {
tracing::error!("Failed to set project untrusted: {e:?}");
self.error = Some(format!(
"Failed to set untrusted for {}: {e}",
target.display()
));
}
fn handle_quit(&mut self) {
self.highlighted = TrustDirectorySelection::Quit;
self.should_quit = true;
}
self.selection = Some(TrustDirectorySelection::DontTrust);
pub fn should_quit(&self) -> bool {
self.should_quit
}
}
@@ -198,9 +178,10 @@ mod tests {
let mut widget = TrustDirectoryWidget {
codex_home: codex_home.path().to_path_buf(),
cwd: PathBuf::from("."),
is_git_repo: false,
show_windows_create_sandbox_hint: false,
should_quit: false,
selection: None,
highlighted: TrustDirectorySelection::DontTrust,
highlighted: TrustDirectorySelection::Quit,
error: None,
};
@@ -213,7 +194,7 @@ mod tests {
let press = KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE);
widget.handle_key_event(press);
assert_eq!(widget.selection, Some(TrustDirectorySelection::DontTrust));
assert!(widget.should_quit);
}
#[test]
@@ -222,7 +203,8 @@ mod tests {
let widget = TrustDirectoryWidget {
codex_home: codex_home.path().to_path_buf(),
cwd: PathBuf::from("/workspace/project"),
is_git_repo: true,
show_windows_create_sandbox_hint: false,
should_quit: false,
selection: None,
highlighted: TrustDirectorySelection::Trust,
error: None,