feat: introduce Permissions (#11633)
## Why We currently carry multiple permission-related concepts directly on `Config` for shell/unified-exec behavior (`approval_policy`, `sandbox_policy`, `network`, `shell_environment_policy`, `windows_sandbox_mode`). Consolidating these into one in-memory struct makes permission handling easier to reason about and sets up the next step: supporting named permission profiles (`[permissions.PROFILE_NAME]`) without changing behavior now. This change is mostly mechanical: it updates existing callsites to go through `config.permissions`, but it does not yet refactor those callsites to take a single `Permissions` value in places where multiple permission fields are still threaded separately. This PR intentionally **does not** change the on-disk `config.toml` format yet and keeps compatibility with legacy config keys. ## What Changed - Introduced `Permissions` in `core/src/config/mod.rs`. - Added `Config::permissions` and moved effective runtime permission fields under it: - `approval_policy` - `sandbox_policy` - `network` - `shell_environment_policy` - `windows_sandbox_mode` - Updated config loading/building so these effective values are still derived from the same existing config inputs and constraints. - Updated Windows sandbox helpers/resolution to read/write via `permissions`. - Threaded the new field through all permission consumers across core runtime, app-server, CLI/exec, TUI, and sandbox summary code. - Updated affected tests to reference `config.permissions.*`. - Renamed the struct/field from `EffectivePermissions`/`effective_permissions` to `Permissions`/`permissions` and aligned variable naming accordingly. ## Verification - `just fix -p codex-core -p codex-tui -p codex-cli -p codex-app-server -p codex-exec -p codex-utils-sandbox-summary` - `cargo build -p codex-core -p codex-tui -p codex-cli -p codex-app-server -p codex-exec -p codex-utils-sandbox-summary`
This commit is contained in:
parent
d7cb70ed26
commit
a4cc1a4a85
30 changed files with 280 additions and 193 deletions
|
|
@ -1677,11 +1677,11 @@ impl CodexMessageProcessor {
|
|||
}
|
||||
|
||||
let cwd = params.cwd.unwrap_or_else(|| self.config.cwd.clone());
|
||||
let env = create_env(&self.config.shell_environment_policy, None);
|
||||
let env = create_env(&self.config.permissions.shell_environment_policy, None);
|
||||
let timeout_ms = params
|
||||
.timeout_ms
|
||||
.and_then(|timeout_ms| u64::try_from(timeout_ms).ok());
|
||||
let started_network_proxy = match self.config.network.as_ref() {
|
||||
let started_network_proxy = match self.config.permissions.network.as_ref() {
|
||||
Some(spec) => match spec.start_proxy().await {
|
||||
Ok(started) => Some(started),
|
||||
Err(err) => {
|
||||
|
|
@ -1713,7 +1713,7 @@ impl CodexMessageProcessor {
|
|||
|
||||
let requested_policy = params.sandbox_policy.map(|policy| policy.to_core());
|
||||
let effective_policy = match requested_policy {
|
||||
Some(policy) => match self.config.sandbox_policy.can_set(&policy) {
|
||||
Some(policy) => match self.config.permissions.sandbox_policy.can_set(&policy) {
|
||||
Ok(()) => policy,
|
||||
Err(err) => {
|
||||
let error = JSONRPCErrorError {
|
||||
|
|
@ -1725,7 +1725,7 @@ impl CodexMessageProcessor {
|
|||
return;
|
||||
}
|
||||
},
|
||||
None => self.config.sandbox_policy.get().clone(),
|
||||
None => self.config.permissions.sandbox_policy.get().clone(),
|
||||
};
|
||||
|
||||
let codex_linux_sandbox_exe = self.config.codex_linux_sandbox_exe.clone();
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ async fn run_command_under_sandbox(
|
|||
let sandbox_policy_cwd = cwd.clone();
|
||||
|
||||
let stdio_policy = StdioPolicy::Inherit;
|
||||
let env = create_env(&config.shell_environment_policy, None);
|
||||
let env = create_env(&config.permissions.shell_environment_policy, None);
|
||||
|
||||
// Special-case Windows sandbox: execute and exit the process to emulate inherited stdio.
|
||||
if let SandboxType::Windows = sandbox_type {
|
||||
|
|
@ -141,7 +141,7 @@ async fn run_command_under_sandbox(
|
|||
use codex_windows_sandbox::run_windows_sandbox_capture;
|
||||
use codex_windows_sandbox::run_windows_sandbox_capture_elevated;
|
||||
|
||||
let policy_str = serde_json::to_string(config.sandbox_policy.get())?;
|
||||
let policy_str = serde_json::to_string(config.permissions.sandbox_policy.get())?;
|
||||
|
||||
let sandbox_cwd = sandbox_policy_cwd.clone();
|
||||
let cwd_clone = cwd.clone();
|
||||
|
|
@ -214,7 +214,7 @@ async fn run_command_under_sandbox(
|
|||
let _ = log_denials;
|
||||
|
||||
// This proxy should only live for the lifetime of the child process.
|
||||
let network_proxy = match config.network.as_ref() {
|
||||
let network_proxy = match config.permissions.network.as_ref() {
|
||||
Some(spec) => Some(
|
||||
spec.start_proxy()
|
||||
.await
|
||||
|
|
@ -232,7 +232,7 @@ async fn run_command_under_sandbox(
|
|||
spawn_command_under_seatbelt(
|
||||
command,
|
||||
cwd,
|
||||
config.sandbox_policy.get(),
|
||||
config.permissions.sandbox_policy.get(),
|
||||
sandbox_policy_cwd.as_path(),
|
||||
stdio_policy,
|
||||
network.as_ref(),
|
||||
|
|
@ -251,7 +251,7 @@ async fn run_command_under_sandbox(
|
|||
codex_linux_sandbox_exe,
|
||||
command,
|
||||
cwd,
|
||||
config.sandbox_policy.get(),
|
||||
config.permissions.sandbox_policy.get(),
|
||||
sandbox_policy_cwd.as_path(),
|
||||
use_bwrap_sandbox,
|
||||
stdio_policy,
|
||||
|
|
|
|||
|
|
@ -122,6 +122,7 @@ Rules:
|
|||
}
|
||||
if profile.read_only {
|
||||
config
|
||||
.permissions
|
||||
.sandbox_policy
|
||||
.set(SandboxPolicy::new_read_only_policy())
|
||||
.map_err(|err| format!("sandbox_policy is invalid: {err}"))?;
|
||||
|
|
|
|||
|
|
@ -389,8 +389,8 @@ impl Codex {
|
|||
personality: config.personality,
|
||||
base_instructions,
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.approval_policy.clone(),
|
||||
sandbox_policy: config.sandbox_policy.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
|
|
@ -932,7 +932,7 @@ impl Session {
|
|||
sandbox_policy: session_configuration.sandbox_policy.get().clone(),
|
||||
network,
|
||||
windows_sandbox_level: session_configuration.windows_sandbox_level,
|
||||
shell_environment_policy: per_turn_config.shell_environment_policy.clone(),
|
||||
shell_environment_policy: per_turn_config.permissions.shell_environment_policy.clone(),
|
||||
tools_config,
|
||||
features: per_turn_config.features.clone(),
|
||||
ghost_snapshot: per_turn_config.ghost_snapshot.clone(),
|
||||
|
|
@ -1099,7 +1099,7 @@ impl Session {
|
|||
});
|
||||
}
|
||||
maybe_push_unstable_features_warning(&config, &mut post_session_configured_events);
|
||||
if config.approval_policy.value() == AskForApproval::OnFailure {
|
||||
if config.permissions.approval_policy.value() == AskForApproval::OnFailure {
|
||||
post_session_configured_events.push(Event {
|
||||
id: "".to_owned(),
|
||||
msg: EventMsg::Warning(WarningEvent {
|
||||
|
|
@ -1142,8 +1142,8 @@ impl Session {
|
|||
config.model_reasoning_summary,
|
||||
config.model_context_window,
|
||||
config.model_auto_compact_token_limit,
|
||||
config.approval_policy.value(),
|
||||
config.sandbox_policy.get().clone(),
|
||||
config.permissions.approval_policy.value(),
|
||||
config.permissions.sandbox_policy.get().clone(),
|
||||
mcp_servers.keys().map(String::as_str).collect(),
|
||||
config.active_profile.clone(),
|
||||
);
|
||||
|
|
@ -1175,7 +1175,7 @@ impl Session {
|
|||
session_configuration.thread_name = thread_name.clone();
|
||||
let mut state = SessionState::new(session_configuration.clone());
|
||||
let network_proxy =
|
||||
match config.network.as_ref() {
|
||||
match config.permissions.network.as_ref() {
|
||||
Some(spec) => Some(spec.start_proxy().await.map_err(|err| {
|
||||
anyhow::anyhow!("failed to start managed network proxy: {err}")
|
||||
})?),
|
||||
|
|
@ -1697,7 +1697,7 @@ impl Session {
|
|||
|
||||
if sandbox_policy_changed {
|
||||
let sandbox_state = SandboxState {
|
||||
sandbox_policy: per_turn_config.sandbox_policy.get().clone(),
|
||||
sandbox_policy: per_turn_config.permissions.sandbox_policy.get().clone(),
|
||||
codex_linux_sandbox_exe: per_turn_config.codex_linux_sandbox_exe.clone(),
|
||||
sandbox_cwd: per_turn_config.cwd.clone(),
|
||||
use_linux_sandbox_bwrap: per_turn_config
|
||||
|
|
@ -6312,8 +6312,8 @@ mod tests {
|
|||
.clone()
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.personality)),
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.approval_policy.clone(),
|
||||
sandbox_policy: config.sandbox_policy.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
|
|
@ -6403,8 +6403,8 @@ mod tests {
|
|||
.clone()
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.personality)),
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.approval_policy.clone(),
|
||||
sandbox_policy: config.sandbox_policy.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
|
|
@ -6713,8 +6713,8 @@ mod tests {
|
|||
.clone()
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.personality)),
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.approval_policy.clone(),
|
||||
sandbox_policy: config.sandbox_policy.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
|
|
@ -6766,8 +6766,8 @@ mod tests {
|
|||
.clone()
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.personality)),
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.approval_policy.clone(),
|
||||
sandbox_policy: config.sandbox_policy.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
|
|
@ -6912,8 +6912,8 @@ mod tests {
|
|||
.clone()
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.personality)),
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.approval_policy.clone(),
|
||||
sandbox_policy: config.sandbox_policy.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
sandbox_policy: config.permissions.sandbox_policy.clone(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
|
|
|
|||
|
|
@ -117,6 +117,22 @@ pub(crate) fn test_config() -> Config {
|
|||
.expect("load default test config")
|
||||
}
|
||||
|
||||
/// Application configuration loaded from disk and merged with overrides.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Permissions {
|
||||
/// Approval policy for executing commands.
|
||||
pub approval_policy: Constrained<AskForApproval>,
|
||||
/// Effective sandbox policy used for shell/unified exec.
|
||||
pub sandbox_policy: Constrained<SandboxPolicy>,
|
||||
/// Effective network configuration applied to all spawned processes.
|
||||
pub network: Option<NetworkProxySpec>,
|
||||
/// Policy used to build process environments for shell/unified exec.
|
||||
pub shell_environment_policy: ShellEnvironmentPolicy,
|
||||
/// Effective Windows sandbox mode derived from `[windows].sandbox` or
|
||||
/// legacy feature keys.
|
||||
pub windows_sandbox_mode: Option<WindowsSandboxModeToml>,
|
||||
}
|
||||
|
||||
/// Application configuration loaded from disk and merged with overrides.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Config {
|
||||
|
|
@ -148,25 +164,18 @@ pub struct Config {
|
|||
/// Optionally specify the personality of the model
|
||||
pub personality: Option<Personality>,
|
||||
|
||||
/// Approval policy for executing commands.
|
||||
pub approval_policy: Constrained<AskForApproval>,
|
||||
|
||||
pub sandbox_policy: Constrained<SandboxPolicy>,
|
||||
/// Effective permission configuration for shell tool execution.
|
||||
pub permissions: Permissions,
|
||||
|
||||
/// enforce_residency means web traffic cannot be routed outside of a
|
||||
/// particular geography. HTTP clients should direct their requests
|
||||
/// using backend-specific headers or URLs to enforce this.
|
||||
pub enforce_residency: Constrained<Option<ResidencyRequirement>>,
|
||||
|
||||
/// Effective network configuration applied to all spawned processes.
|
||||
pub network: Option<NetworkProxySpec>,
|
||||
|
||||
/// True if the user passed in an override or set a value in config.toml
|
||||
/// for either of approval_policy or sandbox_mode.
|
||||
pub did_user_set_custom_approval_policy_or_sandbox_mode: bool,
|
||||
|
||||
pub shell_environment_policy: ShellEnvironmentPolicy,
|
||||
|
||||
/// When `true`, `AgentReasoning` events emitted by the backend will be
|
||||
/// suppressed from the frontend output. This can reduce visual noise when
|
||||
/// users are only interested in the final agent responses.
|
||||
|
|
@ -345,10 +354,6 @@ pub struct Config {
|
|||
/// Settings for ghost snapshots (used for undo).
|
||||
pub ghost_snapshot: GhostSnapshotConfig,
|
||||
|
||||
/// Effective Windows sandbox mode derived from `[windows].sandbox` or
|
||||
/// legacy feature keys.
|
||||
pub windows_sandbox_mode: Option<WindowsSandboxModeToml>,
|
||||
|
||||
/// Centralized feature flags; source of truth for feature gating.
|
||||
pub features: Features,
|
||||
|
||||
|
|
@ -1726,12 +1731,15 @@ impl Config {
|
|||
model_provider,
|
||||
cwd: resolved_cwd,
|
||||
startup_warnings,
|
||||
approval_policy: constrained_approval_policy.value,
|
||||
sandbox_policy: constrained_sandbox_policy.value,
|
||||
permissions: Permissions {
|
||||
approval_policy: constrained_approval_policy.value,
|
||||
sandbox_policy: constrained_sandbox_policy.value,
|
||||
network,
|
||||
shell_environment_policy,
|
||||
windows_sandbox_mode,
|
||||
},
|
||||
enforce_residency: enforce_residency.value,
|
||||
network,
|
||||
did_user_set_custom_approval_policy_or_sandbox_mode,
|
||||
shell_environment_policy,
|
||||
notify: cfg.notify,
|
||||
user_instructions,
|
||||
base_instructions,
|
||||
|
|
@ -1796,7 +1804,6 @@ impl Config {
|
|||
web_search_mode: constrained_web_search_mode.value,
|
||||
use_experimental_unified_exec_tool,
|
||||
ghost_snapshot,
|
||||
windows_sandbox_mode,
|
||||
features,
|
||||
suppress_unstable_features_warning: cfg
|
||||
.suppress_unstable_features_warning
|
||||
|
|
@ -1902,28 +1909,28 @@ impl Config {
|
|||
}
|
||||
|
||||
pub fn set_windows_sandbox_enabled(&mut self, value: bool) {
|
||||
self.windows_sandbox_mode = if value {
|
||||
self.permissions.windows_sandbox_mode = if value {
|
||||
Some(WindowsSandboxModeToml::Unelevated)
|
||||
} else if matches!(
|
||||
self.windows_sandbox_mode,
|
||||
self.permissions.windows_sandbox_mode,
|
||||
Some(WindowsSandboxModeToml::Unelevated)
|
||||
) {
|
||||
None
|
||||
} else {
|
||||
self.windows_sandbox_mode
|
||||
self.permissions.windows_sandbox_mode
|
||||
};
|
||||
}
|
||||
|
||||
pub fn set_windows_elevated_sandbox_enabled(&mut self, value: bool) {
|
||||
self.windows_sandbox_mode = if value {
|
||||
self.permissions.windows_sandbox_mode = if value {
|
||||
Some(WindowsSandboxModeToml::Elevated)
|
||||
} else if matches!(
|
||||
self.windows_sandbox_mode,
|
||||
self.permissions.windows_sandbox_mode,
|
||||
Some(WindowsSandboxModeToml::Elevated)
|
||||
) {
|
||||
None
|
||||
} else {
|
||||
self.windows_sandbox_mode
|
||||
self.permissions.windows_sandbox_mode
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -2366,12 +2373,12 @@ trust_level = "trusted"
|
|||
|
||||
let expected_backend = AbsolutePathBuf::try_from(backend).unwrap();
|
||||
if cfg!(target_os = "windows") {
|
||||
match config.sandbox_policy.get() {
|
||||
match config.permissions.sandbox_policy.get() {
|
||||
SandboxPolicy::ReadOnly { .. } => {}
|
||||
other => panic!("expected read-only policy on Windows, got {other:?}"),
|
||||
}
|
||||
} else {
|
||||
match config.sandbox_policy.get() {
|
||||
match config.permissions.sandbox_policy.get() {
|
||||
SandboxPolicy::WorkspaceWrite { writable_roots, .. } => {
|
||||
assert_eq!(
|
||||
writable_roots
|
||||
|
|
@ -2660,7 +2667,7 @@ profile = "project"
|
|||
)?;
|
||||
|
||||
assert!(matches!(
|
||||
config.sandbox_policy.get(),
|
||||
config.permissions.sandbox_policy.get(),
|
||||
&SandboxPolicy::DangerFullAccess
|
||||
));
|
||||
assert!(config.did_user_set_custom_approval_policy_or_sandbox_mode);
|
||||
|
|
@ -2698,12 +2705,12 @@ profile = "project"
|
|||
|
||||
if cfg!(target_os = "windows") {
|
||||
assert!(matches!(
|
||||
config.sandbox_policy.get(),
|
||||
config.permissions.sandbox_policy.get(),
|
||||
SandboxPolicy::ReadOnly { .. }
|
||||
));
|
||||
} else {
|
||||
assert!(matches!(
|
||||
config.sandbox_policy.get(),
|
||||
config.permissions.sandbox_policy.get(),
|
||||
SandboxPolicy::WorkspaceWrite { .. }
|
||||
));
|
||||
}
|
||||
|
|
@ -4019,12 +4026,15 @@ model_verbosity = "high"
|
|||
model_auto_compact_token_limit: None,
|
||||
model_provider_id: "openai".to_string(),
|
||||
model_provider: fixture.openai_provider.clone(),
|
||||
approval_policy: Constrained::allow_any(AskForApproval::Never),
|
||||
sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
|
||||
permissions: Permissions {
|
||||
approval_policy: Constrained::allow_any(AskForApproval::Never),
|
||||
sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
|
||||
network: None,
|
||||
shell_environment_policy: ShellEnvironmentPolicy::default(),
|
||||
windows_sandbox_mode: None,
|
||||
},
|
||||
enforce_residency: Constrained::allow_any(None),
|
||||
network: None,
|
||||
did_user_set_custom_approval_policy_or_sandbox_mode: true,
|
||||
shell_environment_policy: ShellEnvironmentPolicy::default(),
|
||||
user_instructions: None,
|
||||
notify: None,
|
||||
cwd: fixture.cwd(),
|
||||
|
|
@ -4067,7 +4077,6 @@ model_verbosity = "high"
|
|||
suppress_unstable_features_warning: false,
|
||||
active_profile: Some("o3".to_string()),
|
||||
active_project: ProjectConfig { trust_level: None },
|
||||
windows_sandbox_mode: None,
|
||||
windows_wsl_setup_acknowledged: false,
|
||||
notices: Default::default(),
|
||||
check_for_update_on_startup: true,
|
||||
|
|
@ -4126,12 +4135,15 @@ model_verbosity = "high"
|
|||
model_auto_compact_token_limit: None,
|
||||
model_provider_id: "openai-custom".to_string(),
|
||||
model_provider: fixture.openai_custom_provider.clone(),
|
||||
approval_policy: Constrained::allow_any(AskForApproval::UnlessTrusted),
|
||||
sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
|
||||
permissions: Permissions {
|
||||
approval_policy: Constrained::allow_any(AskForApproval::UnlessTrusted),
|
||||
sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
|
||||
network: None,
|
||||
shell_environment_policy: ShellEnvironmentPolicy::default(),
|
||||
windows_sandbox_mode: None,
|
||||
},
|
||||
enforce_residency: Constrained::allow_any(None),
|
||||
network: None,
|
||||
did_user_set_custom_approval_policy_or_sandbox_mode: true,
|
||||
shell_environment_policy: ShellEnvironmentPolicy::default(),
|
||||
user_instructions: None,
|
||||
notify: None,
|
||||
cwd: fixture.cwd(),
|
||||
|
|
@ -4174,7 +4186,6 @@ model_verbosity = "high"
|
|||
suppress_unstable_features_warning: false,
|
||||
active_profile: Some("gpt3".to_string()),
|
||||
active_project: ProjectConfig { trust_level: None },
|
||||
windows_sandbox_mode: None,
|
||||
windows_wsl_setup_acknowledged: false,
|
||||
notices: Default::default(),
|
||||
check_for_update_on_startup: true,
|
||||
|
|
@ -4231,12 +4242,15 @@ model_verbosity = "high"
|
|||
model_auto_compact_token_limit: None,
|
||||
model_provider_id: "openai".to_string(),
|
||||
model_provider: fixture.openai_provider.clone(),
|
||||
approval_policy: Constrained::allow_any(AskForApproval::OnFailure),
|
||||
sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
|
||||
permissions: Permissions {
|
||||
approval_policy: Constrained::allow_any(AskForApproval::OnFailure),
|
||||
sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
|
||||
network: None,
|
||||
shell_environment_policy: ShellEnvironmentPolicy::default(),
|
||||
windows_sandbox_mode: None,
|
||||
},
|
||||
enforce_residency: Constrained::allow_any(None),
|
||||
network: None,
|
||||
did_user_set_custom_approval_policy_or_sandbox_mode: true,
|
||||
shell_environment_policy: ShellEnvironmentPolicy::default(),
|
||||
user_instructions: None,
|
||||
notify: None,
|
||||
cwd: fixture.cwd(),
|
||||
|
|
@ -4279,7 +4293,6 @@ model_verbosity = "high"
|
|||
suppress_unstable_features_warning: false,
|
||||
active_profile: Some("zdr".to_string()),
|
||||
active_project: ProjectConfig { trust_level: None },
|
||||
windows_sandbox_mode: None,
|
||||
windows_wsl_setup_acknowledged: false,
|
||||
notices: Default::default(),
|
||||
check_for_update_on_startup: true,
|
||||
|
|
@ -4322,12 +4335,15 @@ model_verbosity = "high"
|
|||
model_auto_compact_token_limit: None,
|
||||
model_provider_id: "openai".to_string(),
|
||||
model_provider: fixture.openai_provider.clone(),
|
||||
approval_policy: Constrained::allow_any(AskForApproval::OnFailure),
|
||||
sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
|
||||
permissions: Permissions {
|
||||
approval_policy: Constrained::allow_any(AskForApproval::OnFailure),
|
||||
sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
|
||||
network: None,
|
||||
shell_environment_policy: ShellEnvironmentPolicy::default(),
|
||||
windows_sandbox_mode: None,
|
||||
},
|
||||
enforce_residency: Constrained::allow_any(None),
|
||||
network: None,
|
||||
did_user_set_custom_approval_policy_or_sandbox_mode: true,
|
||||
shell_environment_policy: ShellEnvironmentPolicy::default(),
|
||||
user_instructions: None,
|
||||
notify: None,
|
||||
cwd: fixture.cwd(),
|
||||
|
|
@ -4370,7 +4386,6 @@ model_verbosity = "high"
|
|||
suppress_unstable_features_warning: false,
|
||||
active_profile: Some("gpt5".to_string()),
|
||||
active_project: ProjectConfig { trust_level: None },
|
||||
windows_sandbox_mode: None,
|
||||
windows_wsl_setup_acknowledged: false,
|
||||
notices: Default::default(),
|
||||
check_for_update_on_startup: true,
|
||||
|
|
@ -4903,7 +4918,7 @@ mcp_oauth_callback_port = 5678
|
|||
|
||||
// Verify that untrusted projects get UnlessTrusted approval policy
|
||||
assert_eq!(
|
||||
config.approval_policy.value(),
|
||||
config.permissions.approval_policy.value(),
|
||||
AskForApproval::UnlessTrusted,
|
||||
"Expected UnlessTrusted approval policy for untrusted project"
|
||||
);
|
||||
|
|
@ -4911,13 +4926,16 @@ mcp_oauth_callback_port = 5678
|
|||
// Verify that untrusted projects still get WorkspaceWrite sandbox (or ReadOnly on Windows)
|
||||
if cfg!(target_os = "windows") {
|
||||
assert!(
|
||||
matches!(config.sandbox_policy.get(), SandboxPolicy::ReadOnly { .. }),
|
||||
matches!(
|
||||
config.permissions.sandbox_policy.get(),
|
||||
SandboxPolicy::ReadOnly { .. }
|
||||
),
|
||||
"Expected ReadOnly on Windows"
|
||||
);
|
||||
} else {
|
||||
assert!(
|
||||
matches!(
|
||||
config.sandbox_policy.get(),
|
||||
config.permissions.sandbox_policy.get(),
|
||||
SandboxPolicy::WorkspaceWrite { .. }
|
||||
),
|
||||
"Expected WorkspaceWrite sandbox for untrusted project"
|
||||
|
|
@ -4944,9 +4962,8 @@ mcp_oauth_callback_port = 5678
|
|||
}))
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
*config.sandbox_policy.get(),
|
||||
*config.permissions.sandbox_policy.get(),
|
||||
SandboxPolicy::new_read_only_policy()
|
||||
);
|
||||
Ok(())
|
||||
|
|
@ -4983,7 +5000,7 @@ mcp_oauth_callback_port = 5678
|
|||
.build()
|
||||
.await?;
|
||||
assert_eq!(
|
||||
*config.sandbox_policy.get(),
|
||||
*config.permissions.sandbox_policy.get(),
|
||||
SandboxPolicy::new_read_only_policy()
|
||||
);
|
||||
Ok(())
|
||||
|
|
@ -5015,7 +5032,10 @@ mcp_oauth_callback_port = 5678
|
|||
|
||||
assert_eq!(config.web_search_mode.value(), WebSearchMode::Cached);
|
||||
assert_eq!(
|
||||
resolve_web_search_mode_for_turn(&config.web_search_mode, config.sandbox_policy.get()),
|
||||
resolve_web_search_mode_for_turn(
|
||||
&config.web_search_mode,
|
||||
config.permissions.sandbox_policy.get(),
|
||||
),
|
||||
WebSearchMode::Cached,
|
||||
);
|
||||
Ok(())
|
||||
|
|
@ -5049,7 +5069,10 @@ trust_level = "untrusted"
|
|||
.build()
|
||||
.await?;
|
||||
|
||||
assert_eq!(config.approval_policy.value(), AskForApproval::OnRequest);
|
||||
assert_eq!(
|
||||
config.permissions.approval_policy.value(),
|
||||
AskForApproval::OnRequest
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -5074,7 +5097,10 @@ trust_level = "untrusted"
|
|||
}))
|
||||
.build()
|
||||
.await?;
|
||||
assert_eq!(config.approval_policy.value(), AskForApproval::OnRequest);
|
||||
assert_eq!(
|
||||
config.permissions.approval_policy.value(),
|
||||
AskForApproval::OnRequest
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,7 +95,8 @@ pub(in crate::memories) async fn run_global_memory_consolidation(
|
|||
let consolidation_config = {
|
||||
let mut consolidation_config = config.as_ref().clone();
|
||||
consolidation_config.cwd = root.clone();
|
||||
consolidation_config.approval_policy = Constrained::allow_only(AskForApproval::Never);
|
||||
consolidation_config.permissions.approval_policy =
|
||||
Constrained::allow_only(AskForApproval::Never);
|
||||
let mut writable_roots = Vec::new();
|
||||
match AbsolutePathBuf::from_absolute_path(consolidation_config.codex_home.clone()) {
|
||||
Ok(codex_home) => writable_roots.push(codex_home),
|
||||
|
|
@ -112,6 +113,7 @@ pub(in crate::memories) async fn run_global_memory_consolidation(
|
|||
exclude_slash_tmp: false,
|
||||
};
|
||||
if let Err(err) = consolidation_config
|
||||
.permissions
|
||||
.sandbox_policy
|
||||
.set(consolidation_sandbox_policy)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ async fn start_review_conversation(
|
|||
|
||||
// Set explicit review rubric for the sub-agent
|
||||
sub_agent_config.base_instructions = Some(crate::REVIEW_PROMPT.to_string());
|
||||
sub_agent_config.approval_policy = Constrained::allow_only(AskForApproval::Never);
|
||||
sub_agent_config.permissions.approval_policy = Constrained::allow_only(AskForApproval::Never);
|
||||
|
||||
let model = config
|
||||
.review_model
|
||||
|
|
|
|||
|
|
@ -818,11 +818,12 @@ fn build_agent_shared_config(
|
|||
config.model_reasoning_summary = turn.reasoning_summary;
|
||||
config.developer_instructions = turn.developer_instructions.clone();
|
||||
config.compact_prompt = turn.compact_prompt.clone();
|
||||
config.shell_environment_policy = turn.shell_environment_policy.clone();
|
||||
config.permissions.shell_environment_policy = turn.shell_environment_policy.clone();
|
||||
config.codex_linux_sandbox_exe = turn.codex_linux_sandbox_exe.clone();
|
||||
config.cwd = turn.cwd.clone();
|
||||
config.approval_policy = Constrained::allow_only(AskForApproval::Never);
|
||||
config.permissions.approval_policy = Constrained::allow_only(AskForApproval::Never);
|
||||
config
|
||||
.permissions
|
||||
.sandbox_policy
|
||||
.set(turn.sandbox_policy.clone())
|
||||
.map_err(|err| {
|
||||
|
|
@ -1717,8 +1718,8 @@ mod tests {
|
|||
turn.cwd = temp_dir.path().to_path_buf();
|
||||
turn.codex_linux_sandbox_exe = Some(PathBuf::from("/bin/echo"));
|
||||
turn.sandbox_policy = pick_allowed_sandbox_policy(
|
||||
&turn.config.sandbox_policy,
|
||||
turn.config.sandbox_policy.get().clone(),
|
||||
&turn.config.permissions.sandbox_policy,
|
||||
turn.config.permissions.sandbox_policy.get().clone(),
|
||||
);
|
||||
|
||||
let config = build_agent_spawn_config(&base_instructions, &turn, 0).expect("spawn config");
|
||||
|
|
@ -1730,14 +1731,16 @@ mod tests {
|
|||
expected.model_reasoning_summary = turn.reasoning_summary;
|
||||
expected.developer_instructions = turn.developer_instructions.clone();
|
||||
expected.compact_prompt = turn.compact_prompt.clone();
|
||||
expected.shell_environment_policy = turn.shell_environment_policy.clone();
|
||||
expected.permissions.shell_environment_policy = turn.shell_environment_policy.clone();
|
||||
expected.codex_linux_sandbox_exe = turn.codex_linux_sandbox_exe.clone();
|
||||
expected.cwd = turn.cwd.clone();
|
||||
expected
|
||||
.permissions
|
||||
.approval_policy
|
||||
.set(AskForApproval::Never)
|
||||
.expect("approval policy set");
|
||||
expected
|
||||
.permissions
|
||||
.sandbox_policy
|
||||
.set(turn.sandbox_policy)
|
||||
.expect("sandbox policy set");
|
||||
|
|
@ -1777,14 +1780,16 @@ mod tests {
|
|||
expected.model_reasoning_summary = turn.reasoning_summary;
|
||||
expected.developer_instructions = turn.developer_instructions.clone();
|
||||
expected.compact_prompt = turn.compact_prompt.clone();
|
||||
expected.shell_environment_policy = turn.shell_environment_policy.clone();
|
||||
expected.permissions.shell_environment_policy = turn.shell_environment_policy.clone();
|
||||
expected.codex_linux_sandbox_exe = turn.codex_linux_sandbox_exe.clone();
|
||||
expected.cwd = turn.cwd.clone();
|
||||
expected
|
||||
.permissions
|
||||
.approval_policy
|
||||
.set(AskForApproval::Never)
|
||||
.expect("approval policy set");
|
||||
expected
|
||||
.permissions
|
||||
.sandbox_policy
|
||||
.set(turn.sandbox_policy)
|
||||
.expect("sandbox policy set");
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ pub trait WindowsSandboxLevelExt {
|
|||
|
||||
impl WindowsSandboxLevelExt for WindowsSandboxLevel {
|
||||
fn from_config(config: &Config) -> WindowsSandboxLevel {
|
||||
match config.windows_sandbox_mode {
|
||||
match config.permissions.windows_sandbox_mode {
|
||||
Some(WindowsSandboxModeToml::Elevated) => WindowsSandboxLevel::Elevated,
|
||||
Some(WindowsSandboxModeToml::Unelevated) => WindowsSandboxLevel::RestrictedToken,
|
||||
None => Self::from_features(&config.features),
|
||||
|
|
|
|||
|
|
@ -1467,8 +1467,8 @@ async fn run_scenario(scenario: &ScenarioSpec) -> Result<()> {
|
|||
let model = model_override.unwrap_or("gpt-5.1");
|
||||
|
||||
let mut builder = test_codex().with_model(model).with_config(move |config| {
|
||||
config.approval_policy = Constrained::allow_any(approval_policy);
|
||||
config.sandbox_policy = Constrained::allow_any(sandbox_policy.clone());
|
||||
config.permissions.approval_policy = Constrained::allow_any(approval_policy);
|
||||
config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy.clone());
|
||||
for feature in features {
|
||||
config.features.enable(feature);
|
||||
}
|
||||
|
|
@ -1585,8 +1585,8 @@ async fn approving_apply_patch_for_session_skips_future_prompts_for_same_file()
|
|||
let mut builder = test_codex()
|
||||
.with_model("gpt-5.1-codex")
|
||||
.with_config(move |config| {
|
||||
config.approval_policy = Constrained::allow_any(approval_policy);
|
||||
config.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config);
|
||||
config.permissions.approval_policy = Constrained::allow_any(approval_policy);
|
||||
config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config);
|
||||
});
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
|
|
@ -1692,8 +1692,8 @@ async fn approving_execpolicy_amendment_persists_policy_and_skips_future_prompts
|
|||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
let sandbox_policy_for_config = sandbox_policy.clone();
|
||||
let mut builder = test_codex().with_config(move |config| {
|
||||
config.approval_policy = Constrained::allow_any(approval_policy);
|
||||
config.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config);
|
||||
config.permissions.approval_policy = Constrained::allow_any(approval_policy);
|
||||
config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config);
|
||||
});
|
||||
let test = builder.build(&server).await?;
|
||||
let allow_prefix_path = test.cwd.path().join("allow-prefix.txt");
|
||||
|
|
|
|||
|
|
@ -973,8 +973,8 @@ async fn user_turn_collaboration_mode_overrides_model_and_effort() -> anyhow::Re
|
|||
text_elements: Vec::new(),
|
||||
}],
|
||||
cwd: config.cwd.clone(),
|
||||
approval_policy: config.approval_policy.value(),
|
||||
sandbox_policy: config.sandbox_policy.get().clone(),
|
||||
approval_policy: config.permissions.approval_policy.value(),
|
||||
sandbox_policy: config.permissions.sandbox_policy.get().clone(),
|
||||
model: session_configured.model.clone(),
|
||||
effort: Some(ReasoningEffort::Low),
|
||||
summary: config.model_reasoning_summary,
|
||||
|
|
|
|||
|
|
@ -63,8 +63,9 @@ async fn codex_delegate_forwards_exec_approval_and_proceeds_on_approval() {
|
|||
// Build a conversation configured to require approvals so the delegate
|
||||
// routes ExecApprovalRequest via the parent.
|
||||
let mut builder = test_codex().with_model("gpt-5.1").with_config(|config| {
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
config.sandbox_policy = Constrained::allow_any(SandboxPolicy::new_read_only_policy());
|
||||
config.permissions.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
config.permissions.sandbox_policy =
|
||||
Constrained::allow_any(SandboxPolicy::new_read_only_policy());
|
||||
});
|
||||
let test = builder.build(&server).await.expect("build test codex");
|
||||
|
||||
|
|
@ -144,9 +145,10 @@ async fn codex_delegate_forwards_patch_approval_and_proceeds_on_decision() {
|
|||
mount_sse_sequence(&server, vec![sse1, sse2]).await;
|
||||
|
||||
let mut builder = test_codex().with_model("gpt-5.1").with_config(|config| {
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
config.permissions.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
// Use a restricted sandbox so patch approval is required
|
||||
config.sandbox_policy = Constrained::allow_any(SandboxPolicy::new_read_only_policy());
|
||||
config.permissions.sandbox_policy =
|
||||
Constrained::allow_any(SandboxPolicy::new_read_only_policy());
|
||||
config.include_apply_patch_tool = true;
|
||||
});
|
||||
let test = builder.build(&server).await.expect("build test codex");
|
||||
|
|
|
|||
|
|
@ -165,8 +165,8 @@ async fn collaboration_instructions_added_on_user_turn() -> Result<()> {
|
|||
text_elements: Vec::new(),
|
||||
}],
|
||||
cwd: test.config.cwd.clone(),
|
||||
approval_policy: test.config.approval_policy.value(),
|
||||
sandbox_policy: test.config.sandbox_policy.get().clone(),
|
||||
approval_policy: test.config.permissions.approval_policy.value(),
|
||||
sandbox_policy: test.config.permissions.sandbox_policy.get().clone(),
|
||||
model: test.session_configured.model.clone(),
|
||||
effort: None,
|
||||
summary: test.config.model_reasoning_summary,
|
||||
|
|
@ -271,8 +271,8 @@ async fn user_turn_overrides_collaboration_instructions_after_override() -> Resu
|
|||
text_elements: Vec::new(),
|
||||
}],
|
||||
cwd: test.config.cwd.clone(),
|
||||
approval_policy: test.config.approval_policy.value(),
|
||||
sandbox_policy: test.config.sandbox_policy.get().clone(),
|
||||
approval_policy: test.config.permissions.approval_policy.value(),
|
||||
sandbox_policy: test.config.permissions.sandbox_policy.get().clone(),
|
||||
model: test.session_configured.model.clone(),
|
||||
effort: None,
|
||||
summary: test.config.model_reasoning_summary,
|
||||
|
|
|
|||
|
|
@ -964,8 +964,9 @@ async fn handle_container_exec_autoapprove_from_config_records_tool_decision() {
|
|||
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(|config| {
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
config.sandbox_policy = Constrained::allow_any(SandboxPolicy::DangerFullAccess);
|
||||
config.permissions.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
config.permissions.sandbox_policy =
|
||||
Constrained::allow_any(SandboxPolicy::DangerFullAccess);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
|
|
@ -1015,7 +1016,8 @@ async fn handle_container_exec_user_approved_records_tool_decision() {
|
|||
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(|config| {
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::UnlessTrusted);
|
||||
config.permissions.approval_policy =
|
||||
Constrained::allow_any(AskForApproval::UnlessTrusted);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
|
|
@ -1080,7 +1082,8 @@ async fn handle_container_exec_user_approved_for_session_records_tool_decision()
|
|||
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(|config| {
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::UnlessTrusted);
|
||||
config.permissions.approval_policy =
|
||||
Constrained::allow_any(AskForApproval::UnlessTrusted);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
|
|
@ -1145,7 +1148,8 @@ async fn handle_sandbox_error_user_approves_retry_records_tool_decision() {
|
|||
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(|config| {
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::UnlessTrusted);
|
||||
config.permissions.approval_policy =
|
||||
Constrained::allow_any(AskForApproval::UnlessTrusted);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
|
|
@ -1210,7 +1214,8 @@ async fn handle_container_exec_user_denies_records_tool_decision() {
|
|||
.await;
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(|config| {
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::UnlessTrusted);
|
||||
config.permissions.approval_policy =
|
||||
Constrained::allow_any(AskForApproval::UnlessTrusted);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
|
|
@ -1275,7 +1280,8 @@ async fn handle_sandbox_error_user_approves_for_session_records_tool_decision()
|
|||
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(|config| {
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::UnlessTrusted);
|
||||
config.permissions.approval_policy =
|
||||
Constrained::allow_any(AskForApproval::UnlessTrusted);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
|
|
@ -1341,7 +1347,8 @@ async fn handle_sandbox_error_user_denies_records_tool_decision() {
|
|||
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(|config| {
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::UnlessTrusted);
|
||||
config.permissions.approval_policy =
|
||||
Constrained::allow_any(AskForApproval::UnlessTrusted);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ async fn override_turn_context_without_user_turn_does_not_record_permissions_upd
|
|||
|
||||
let server = start_mock_server().await;
|
||||
let mut builder = test_codex().with_config(|config| {
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
config.permissions.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
});
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ async fn permissions_message_sent_once_on_start() -> Result<()> {
|
|||
.await;
|
||||
|
||||
let mut builder = test_codex().with_config(move |config| {
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
config.permissions.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
});
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
|
|
@ -96,7 +96,7 @@ async fn permissions_message_added_on_override_change() -> Result<()> {
|
|||
.await;
|
||||
|
||||
let mut builder = test_codex().with_config(move |config| {
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
config.permissions.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
});
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
|
|
@ -168,7 +168,7 @@ async fn permissions_message_not_added_when_no_change() -> Result<()> {
|
|||
.await;
|
||||
|
||||
let mut builder = test_codex().with_config(move |config| {
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
config.permissions.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
});
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
|
|
@ -230,7 +230,7 @@ async fn resume_replays_permissions_messages() -> Result<()> {
|
|||
.await;
|
||||
|
||||
let mut builder = test_codex().with_config(|config| {
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
config.permissions.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
});
|
||||
let initial = builder.build(&server).await?;
|
||||
let rollout_path = initial
|
||||
|
|
@ -329,7 +329,7 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> {
|
|||
.await;
|
||||
|
||||
let mut builder = test_codex().with_config(|config| {
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
config.permissions.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
});
|
||||
let initial = builder.build(&server).await?;
|
||||
let rollout_path = initial
|
||||
|
|
@ -384,7 +384,7 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> {
|
|||
assert_eq!(permissions_base.len(), 2);
|
||||
|
||||
builder = builder.with_config(|config| {
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::UnlessTrusted);
|
||||
config.permissions.approval_policy = Constrained::allow_any(AskForApproval::UnlessTrusted);
|
||||
});
|
||||
let resumed = builder.resume(&server, home, rollout_path.clone()).await?;
|
||||
resumed
|
||||
|
|
@ -410,7 +410,7 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> {
|
|||
assert!(!permissions_base.contains(permissions_resume.last().expect("new permissions")));
|
||||
|
||||
let mut fork_config = initial.config.clone();
|
||||
fork_config.approval_policy = Constrained::allow_any(AskForApproval::UnlessTrusted);
|
||||
fork_config.permissions.approval_policy = Constrained::allow_any(AskForApproval::UnlessTrusted);
|
||||
let forked = initial
|
||||
.thread_manager
|
||||
.fork_thread(usize::MAX, fork_config, rollout_path, false)
|
||||
|
|
@ -465,8 +465,8 @@ async fn permissions_message_includes_writable_roots() -> Result<()> {
|
|||
let sandbox_policy_for_config = sandbox_policy.clone();
|
||||
|
||||
let mut builder = test_codex().with_config(move |config| {
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
config.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config);
|
||||
config.permissions.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config);
|
||||
});
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ async fn user_turn_personality_none_does_not_add_update_message() -> anyhow::Res
|
|||
}],
|
||||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: test.config.approval_policy.value(),
|
||||
approval_policy: test.config.permissions.approval_policy.value(),
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: test.session_configured.model.clone(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
|
|
@ -141,7 +141,7 @@ async fn config_personality_some_sets_instructions_template() -> anyhow::Result<
|
|||
}],
|
||||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: test.config.approval_policy.value(),
|
||||
approval_policy: test.config.permissions.approval_policy.value(),
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: test.session_configured.model.clone(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
|
|
@ -195,7 +195,7 @@ async fn config_personality_none_sends_no_personality() -> anyhow::Result<()> {
|
|||
}],
|
||||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: test.config.approval_policy.value(),
|
||||
approval_policy: test.config.permissions.approval_policy.value(),
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: test.session_configured.model.clone(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
|
|
@ -255,7 +255,7 @@ async fn default_personality_is_pragmatic_without_config_toml() -> anyhow::Resul
|
|||
}],
|
||||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: test.config.approval_policy.value(),
|
||||
approval_policy: test.config.permissions.approval_policy.value(),
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: test.session_configured.model.clone(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
|
|
@ -303,7 +303,7 @@ async fn user_turn_personality_some_adds_update_message() -> anyhow::Result<()>
|
|||
}],
|
||||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: test.config.approval_policy.value(),
|
||||
approval_policy: test.config.permissions.approval_policy.value(),
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: test.session_configured.model.clone(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
|
|
@ -337,7 +337,7 @@ async fn user_turn_personality_some_adds_update_message() -> anyhow::Result<()>
|
|||
}],
|
||||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: test.config.approval_policy.value(),
|
||||
approval_policy: test.config.permissions.approval_policy.value(),
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: test.session_configured.model.clone(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
|
|
@ -400,7 +400,7 @@ async fn user_turn_personality_same_value_does_not_add_update_message() -> anyho
|
|||
}],
|
||||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: test.config.approval_policy.value(),
|
||||
approval_policy: test.config.permissions.approval_policy.value(),
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: test.session_configured.model.clone(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
|
|
@ -434,7 +434,7 @@ async fn user_turn_personality_same_value_does_not_add_update_message() -> anyho
|
|||
}],
|
||||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: test.config.approval_policy.value(),
|
||||
approval_policy: test.config.permissions.approval_policy.value(),
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: test.session_configured.model.clone(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
|
|
@ -507,7 +507,7 @@ async fn user_turn_personality_skips_if_feature_disabled() -> anyhow::Result<()>
|
|||
}],
|
||||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: test.config.approval_policy.value(),
|
||||
approval_policy: test.config.permissions.approval_policy.value(),
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: test.session_configured.model.clone(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
|
|
@ -541,7 +541,7 @@ async fn user_turn_personality_skips_if_feature_disabled() -> anyhow::Result<()>
|
|||
}],
|
||||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: test.config.approval_policy.value(),
|
||||
approval_policy: test.config.permissions.approval_policy.value(),
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: test.session_configured.model.clone(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
|
|
|
|||
|
|
@ -733,8 +733,8 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a
|
|||
.await?;
|
||||
|
||||
let default_cwd = config.cwd.clone();
|
||||
let default_approval_policy = config.approval_policy.value();
|
||||
let default_sandbox_policy = config.sandbox_policy.get();
|
||||
let default_approval_policy = config.permissions.approval_policy.value();
|
||||
let default_sandbox_policy = config.permissions.sandbox_policy.get();
|
||||
let default_model = session_configured.model;
|
||||
let default_effort = config.model_reasoning_effort;
|
||||
let default_summary = config.model_reasoning_summary;
|
||||
|
|
@ -841,8 +841,8 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu
|
|||
.await?;
|
||||
|
||||
let default_cwd = config.cwd.clone();
|
||||
let default_approval_policy = config.approval_policy.value();
|
||||
let default_sandbox_policy = config.sandbox_policy.get();
|
||||
let default_approval_policy = config.permissions.approval_policy.value();
|
||||
let default_sandbox_policy = config.permissions.sandbox_policy.get();
|
||||
let default_model = session_configured.model;
|
||||
let default_effort = config.model_reasoning_effort;
|
||||
let default_summary = config.model_reasoning_summary;
|
||||
|
|
|
|||
|
|
@ -177,8 +177,8 @@ async fn remote_models_long_model_slug_is_sent_with_high_reasoning() -> Result<(
|
|||
}],
|
||||
final_output_json_schema: None,
|
||||
cwd: cwd.path().to_path_buf(),
|
||||
approval_policy: config.approval_policy.value(),
|
||||
sandbox_policy: config.sandbox_policy.get().clone(),
|
||||
approval_policy: config.permissions.approval_policy.value(),
|
||||
sandbox_policy: config.permissions.sandbox_policy.get().clone(),
|
||||
model: requested_model.to_string(),
|
||||
effort: None,
|
||||
summary: config.model_reasoning_summary,
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ fn resume_history(
|
|||
let turn_ctx = TurnContextItem {
|
||||
turn_id: None,
|
||||
cwd: config.cwd.clone(),
|
||||
approval_policy: config.approval_policy.value(),
|
||||
sandbox_policy: config.sandbox_policy.get().clone(),
|
||||
approval_policy: config.permissions.approval_policy.value(),
|
||||
sandbox_policy: config.permissions.sandbox_policy.get().clone(),
|
||||
model: previous_model.to_string(),
|
||||
personality: None,
|
||||
collaboration_mode: None,
|
||||
|
|
|
|||
|
|
@ -416,6 +416,7 @@ async fn shell_timeout_handles_background_grandchild_stdout() -> Result<()> {
|
|||
let server = start_mock_server().await;
|
||||
let mut builder = test_codex().with_model("gpt-5.1").with_config(|config| {
|
||||
config
|
||||
.permissions
|
||||
.sandbox_policy
|
||||
.set(SandboxPolicy::DangerFullAccess)
|
||||
.expect("set sandbox policy");
|
||||
|
|
@ -511,7 +512,8 @@ async fn shell_spawn_failure_truncates_exec_error() -> Result<()> {
|
|||
|
||||
let server = start_mock_server().await;
|
||||
let mut builder = test_codex().with_config(|cfg| {
|
||||
cfg.sandbox_policy
|
||||
cfg.permissions
|
||||
.sandbox_policy
|
||||
.set(SandboxPolicy::DangerFullAccess)
|
||||
.expect("set sandbox policy");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -332,8 +332,8 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option<PathBuf>) -> any
|
|||
}
|
||||
|
||||
let default_cwd = config.cwd.to_path_buf();
|
||||
let default_approval_policy = config.approval_policy.value();
|
||||
let default_sandbox_policy = config.sandbox_policy.get();
|
||||
let default_approval_policy = config.permissions.approval_policy.value();
|
||||
let default_sandbox_policy = config.permissions.sandbox_policy.get();
|
||||
let default_effort = config.model_reasoning_effort;
|
||||
let default_summary = config.model_reasoning_summary;
|
||||
|
||||
|
|
|
|||
|
|
@ -628,7 +628,7 @@ impl App {
|
|||
|
||||
fn apply_runtime_policy_overrides(&mut self, config: &mut Config) {
|
||||
if let Some(policy) = self.runtime_approval_policy_override.as_ref()
|
||||
&& let Err(err) = config.approval_policy.set(*policy)
|
||||
&& let Err(err) = config.permissions.approval_policy.set(*policy)
|
||||
{
|
||||
tracing::warn!(%err, "failed to carry forward approval policy override");
|
||||
self.chat_widget.add_error_message(format!(
|
||||
|
|
@ -636,7 +636,7 @@ impl App {
|
|||
));
|
||||
}
|
||||
if let Some(policy) = self.runtime_sandbox_policy_override.as_ref()
|
||||
&& let Err(err) = config.sandbox_policy.set(policy.clone())
|
||||
&& let Err(err) = config.permissions.sandbox_policy.set(policy.clone())
|
||||
{
|
||||
tracing::warn!(%err, "failed to carry forward sandbox policy override");
|
||||
self.chat_widget.add_error_message(format!(
|
||||
|
|
@ -1156,7 +1156,7 @@ impl App {
|
|||
let should_check = WindowsSandboxLevel::from_config(&app.config)
|
||||
!= WindowsSandboxLevel::Disabled
|
||||
&& matches!(
|
||||
app.config.sandbox_policy.get(),
|
||||
app.config.permissions.sandbox_policy.get(),
|
||||
codex_core::protocol::SandboxPolicy::WorkspaceWrite { .. }
|
||||
| codex_core::protocol::SandboxPolicy::ReadOnly { .. }
|
||||
)
|
||||
|
|
@ -1170,7 +1170,7 @@ impl App {
|
|||
let env_map: std::collections::HashMap<String, String> = std::env::vars().collect();
|
||||
let tx = app.app_event_tx.clone();
|
||||
let logs_base_dir = app.config.codex_home.clone();
|
||||
let sandbox_policy = app.config.sandbox_policy.get().clone();
|
||||
let sandbox_policy = app.config.permissions.sandbox_policy.get().clone();
|
||||
Self::spawn_world_writable_scan(cwd, env_map, logs_base_dir, sandbox_policy, tx);
|
||||
}
|
||||
}
|
||||
|
|
@ -1835,7 +1835,7 @@ impl App {
|
|||
None,
|
||||
));
|
||||
|
||||
let policy = self.config.sandbox_policy.get().clone();
|
||||
let policy = self.config.permissions.sandbox_policy.get().clone();
|
||||
let policy_cwd = self.config.cwd.clone();
|
||||
let command_cwd = self.config.cwd.clone();
|
||||
let env_map: std::collections::HashMap<String, String> =
|
||||
|
|
@ -1913,8 +1913,9 @@ impl App {
|
|||
self.config.set_windows_sandbox_enabled(true);
|
||||
self.config.set_windows_elevated_sandbox_enabled(false);
|
||||
}
|
||||
self.chat_widget
|
||||
.set_windows_sandbox_mode(self.config.windows_sandbox_mode);
|
||||
self.chat_widget.set_windows_sandbox_mode(
|
||||
self.config.permissions.windows_sandbox_mode,
|
||||
);
|
||||
let windows_sandbox_level =
|
||||
WindowsSandboxLevel::from_config(&self.config);
|
||||
if let Some((sample_paths, extra_count, failed_scan)) =
|
||||
|
|
@ -2060,7 +2061,7 @@ impl App {
|
|||
}
|
||||
AppEvent::UpdateAskForApprovalPolicy(policy) => {
|
||||
self.runtime_approval_policy_override = Some(policy);
|
||||
if let Err(err) = self.config.approval_policy.set(policy) {
|
||||
if let Err(err) = self.config.permissions.approval_policy.set(policy) {
|
||||
tracing::warn!(%err, "failed to set approval policy on app config");
|
||||
self.chat_widget
|
||||
.add_error_message(format!("Failed to set approval policy: {err}"));
|
||||
|
|
@ -2077,7 +2078,7 @@ impl App {
|
|||
);
|
||||
let policy_for_chat = policy.clone();
|
||||
|
||||
if let Err(err) = self.config.sandbox_policy.set(policy) {
|
||||
if let Err(err) = self.config.permissions.sandbox_policy.set(policy) {
|
||||
tracing::warn!(%err, "failed to set sandbox policy on app config");
|
||||
self.chat_widget
|
||||
.add_error_message(format!("Failed to set sandbox policy: {err}"));
|
||||
|
|
@ -2090,7 +2091,7 @@ impl App {
|
|||
return Ok(AppRunControl::Continue);
|
||||
}
|
||||
self.runtime_sandbox_policy_override =
|
||||
Some(self.config.sandbox_policy.get().clone());
|
||||
Some(self.config.permissions.sandbox_policy.get().clone());
|
||||
|
||||
// If sandbox policy becomes workspace-write or read-only, run the Windows world-writable scan.
|
||||
#[cfg(target_os = "windows")]
|
||||
|
|
@ -2111,7 +2112,7 @@ impl App {
|
|||
std::env::vars().collect();
|
||||
let tx = self.app_event_tx.clone();
|
||||
let logs_base_dir = self.config.codex_home.clone();
|
||||
let sandbox_policy = self.config.sandbox_policy.get().clone();
|
||||
let sandbox_policy = self.config.permissions.sandbox_policy.get().clone();
|
||||
Self::spawn_world_writable_scan(
|
||||
cwd,
|
||||
env_map,
|
||||
|
|
|
|||
|
|
@ -3323,7 +3323,12 @@ impl ChatWidget {
|
|||
return;
|
||||
};
|
||||
|
||||
if let Err(err) = self.config.approval_policy.can_set(&preset.approval) {
|
||||
if let Err(err) = self
|
||||
.config
|
||||
.permissions
|
||||
.approval_policy
|
||||
.can_set(&preset.approval)
|
||||
{
|
||||
self.add_error_message(err.to_string());
|
||||
return;
|
||||
}
|
||||
|
|
@ -3814,8 +3819,8 @@ impl ChatWidget {
|
|||
let op = Op::UserTurn {
|
||||
items,
|
||||
cwd: self.config.cwd.clone(),
|
||||
approval_policy: self.config.approval_policy.value(),
|
||||
sandbox_policy: self.config.sandbox_policy.get().clone(),
|
||||
approval_policy: self.config.permissions.approval_policy.value(),
|
||||
sandbox_policy: self.config.permissions.sandbox_policy.get().clone(),
|
||||
model: effective_mode.model().to_string(),
|
||||
effort: effective_mode.reasoning_effort(),
|
||||
summary: self.config.model_reasoning_summary,
|
||||
|
|
@ -5272,8 +5277,8 @@ 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.approval_policy.value();
|
||||
let current_sandbox = self.config.sandbox_policy.get();
|
||||
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();
|
||||
|
||||
|
|
@ -5301,7 +5306,12 @@ impl ChatWidget {
|
|||
preset.label.to_string()
|
||||
};
|
||||
let description = Some(preset.description.replace(" (Identical to Agent mode)", ""));
|
||||
let disabled_reason = match self.config.approval_policy.can_set(&preset.approval) {
|
||||
let disabled_reason = match self
|
||||
.config
|
||||
.permissions
|
||||
.approval_policy
|
||||
.can_set(&preset.approval)
|
||||
{
|
||||
Ok(()) => None,
|
||||
Err(err) => Some(err.to_string()),
|
||||
};
|
||||
|
|
@ -5462,7 +5472,7 @@ impl ChatWidget {
|
|||
self.config.codex_home.as_path(),
|
||||
cwd.as_path(),
|
||||
&env_map,
|
||||
self.config.sandbox_policy.get(),
|
||||
self.config.permissions.sandbox_policy.get(),
|
||||
Some(self.config.codex_home.as_path()),
|
||||
) {
|
||||
Ok(_) => None,
|
||||
|
|
@ -5569,7 +5579,7 @@ impl ChatWidget {
|
|||
let mode_label = preset
|
||||
.as_ref()
|
||||
.map(|p| describe_policy(&p.sandbox))
|
||||
.unwrap_or_else(|| describe_policy(self.config.sandbox_policy.get()));
|
||||
.unwrap_or_else(|| describe_policy(self.config.permissions.sandbox_policy.get()));
|
||||
let info_line = if failed_scan {
|
||||
Line::from(vec![
|
||||
"We couldn't complete the world-writable scan, so protections cannot be verified. "
|
||||
|
|
@ -5912,7 +5922,7 @@ impl ChatWidget {
|
|||
|
||||
/// 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) {
|
||||
if let Err(err) = self.config.permissions.approval_policy.set(policy) {
|
||||
tracing::warn!(%err, "failed to set approval_policy on chat config");
|
||||
}
|
||||
}
|
||||
|
|
@ -5920,13 +5930,13 @@ 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<()> {
|
||||
self.config.sandbox_policy.set(policy)?;
|
||||
self.config.permissions.sandbox_policy.set(policy)?;
|
||||
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;
|
||||
self.config.permissions.windows_sandbox_mode = mode;
|
||||
#[cfg(target_os = "windows")]
|
||||
self.bottom_pane.set_windows_degraded_sandbox_active(
|
||||
codex_core::windows_sandbox::ELEVATED_SANDBOX_NUX_ENABLED
|
||||
|
|
|
|||
|
|
@ -4510,7 +4510,7 @@ async fn disabled_slash_command_while_task_running_snapshot() {
|
|||
async fn approvals_popup_shows_disabled_presets() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
|
||||
chat.config.approval_policy =
|
||||
chat.config.permissions.approval_policy =
|
||||
Constrained::new(AskForApproval::OnRequest, |candidate| match candidate {
|
||||
AskForApproval::OnRequest => Ok(()),
|
||||
_ => Err(invalid_value(
|
||||
|
|
@ -4546,7 +4546,7 @@ async fn approvals_popup_shows_disabled_presets() {
|
|||
async fn approvals_popup_navigation_skips_disabled() {
|
||||
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None).await;
|
||||
|
||||
chat.config.approval_policy =
|
||||
chat.config.permissions.approval_policy =
|
||||
Constrained::new(AskForApproval::OnRequest, |candidate| match candidate {
|
||||
AskForApproval::OnRequest => Ok(()),
|
||||
_ => Err(invalid_value(candidate.to_string(), "[on-request]")),
|
||||
|
|
@ -4623,7 +4623,10 @@ async fn approval_modal_exec_snapshot() -> anyhow::Result<()> {
|
|||
// Build a chat widget with manual channels to avoid spawning the agent.
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
// Ensure policy allows surfacing approvals explicitly (not strictly required for direct event).
|
||||
chat.config.approval_policy.set(AskForApproval::OnRequest)?;
|
||||
chat.config
|
||||
.permissions
|
||||
.approval_policy
|
||||
.set(AskForApproval::OnRequest)?;
|
||||
// Inject an exec approval request to display the approval modal.
|
||||
let ev = ExecApprovalRequestEvent {
|
||||
call_id: "call-approve-cmd".into(),
|
||||
|
|
@ -4678,7 +4681,10 @@ async fn approval_modal_exec_snapshot() -> anyhow::Result<()> {
|
|||
#[tokio::test]
|
||||
async fn approval_modal_exec_without_reason_snapshot() -> anyhow::Result<()> {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
chat.config.approval_policy.set(AskForApproval::OnRequest)?;
|
||||
chat.config
|
||||
.permissions
|
||||
.approval_policy
|
||||
.set(AskForApproval::OnRequest)?;
|
||||
|
||||
let ev = ExecApprovalRequestEvent {
|
||||
call_id: "call-approve-cmd-noreason".into(),
|
||||
|
|
@ -4720,7 +4726,10 @@ async fn approval_modal_exec_without_reason_snapshot() -> anyhow::Result<()> {
|
|||
async fn approval_modal_exec_multiline_prefix_hides_execpolicy_option_snapshot()
|
||||
-> anyhow::Result<()> {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
chat.config.approval_policy.set(AskForApproval::OnRequest)?;
|
||||
chat.config
|
||||
.permissions
|
||||
.approval_policy
|
||||
.set(AskForApproval::OnRequest)?;
|
||||
|
||||
let script = "python - <<'PY'\nprint('hello')\nPY".to_string();
|
||||
let command = vec!["bash".into(), "-lc".into(), script];
|
||||
|
|
@ -4760,7 +4769,10 @@ async fn approval_modal_exec_multiline_prefix_hides_execpolicy_option_snapshot()
|
|||
#[tokio::test]
|
||||
async fn approval_modal_patch_snapshot() -> anyhow::Result<()> {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
chat.config.approval_policy.set(AskForApproval::OnRequest)?;
|
||||
chat.config
|
||||
.permissions
|
||||
.approval_policy
|
||||
.set(AskForApproval::OnRequest)?;
|
||||
|
||||
// Build a small changeset and a reason/grant_root to exercise the prompt text.
|
||||
let mut changes = HashMap::new();
|
||||
|
|
@ -5529,7 +5541,10 @@ async fn apply_patch_full_flow_integration_like() {
|
|||
async fn apply_patch_untrusted_shows_approval_modal() -> anyhow::Result<()> {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
// Ensure approval policy is untrusted (OnRequest)
|
||||
chat.config.approval_policy.set(AskForApproval::OnRequest)?;
|
||||
chat.config
|
||||
.permissions
|
||||
.approval_policy
|
||||
.set(AskForApproval::OnRequest)?;
|
||||
|
||||
// Simulate a patch approval request from backend
|
||||
let mut changes = HashMap::new();
|
||||
|
|
@ -5577,7 +5592,10 @@ async fn apply_patch_request_shows_diff_summary() -> anyhow::Result<()> {
|
|||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
|
||||
// Ensure we are in OnRequest so an approval is surfaced
|
||||
chat.config.approval_policy.set(AskForApproval::OnRequest)?;
|
||||
chat.config
|
||||
.permissions
|
||||
.approval_policy
|
||||
.set(AskForApproval::OnRequest)?;
|
||||
|
||||
// Simulate backend asking to apply a patch adding two lines to README.md
|
||||
let mut changes = HashMap::new();
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ pub(crate) fn new_debug_config_output(
|
|||
http_addr,
|
||||
socks_addr,
|
||||
config
|
||||
.permissions
|
||||
.network
|
||||
.as_ref()
|
||||
.is_some_and(codex_core::config::NetworkProxySpec::socks_enabled),
|
||||
|
|
|
|||
|
|
@ -290,7 +290,9 @@ pub async fn run_main(
|
|||
.await;
|
||||
set_default_client_residency_requirement(config.enforce_residency.value());
|
||||
|
||||
if let Some(warning) = add_dir_warning_message(&cli.add_dir, config.sandbox_policy.get()) {
|
||||
if let Some(warning) =
|
||||
add_dir_warning_message(&cli.add_dir, config.permissions.sandbox_policy.get())
|
||||
{
|
||||
#[allow(clippy::print_stderr)]
|
||||
{
|
||||
eprintln!("Error adding directories: {warning}");
|
||||
|
|
@ -1004,8 +1006,8 @@ mod tests {
|
|||
TurnContextItem {
|
||||
turn_id: None,
|
||||
cwd,
|
||||
approval_policy: config.approval_policy.value(),
|
||||
sandbox_policy: config.sandbox_policy.get().clone(),
|
||||
approval_policy: config.permissions.approval_policy.value(),
|
||||
sandbox_policy: config.permissions.sandbox_policy.get().clone(),
|
||||
model,
|
||||
personality: None,
|
||||
collaboration_mode: None,
|
||||
|
|
@ -1123,7 +1125,7 @@ trust_level = "untrusted"
|
|||
.build()
|
||||
.await?;
|
||||
assert_eq!(
|
||||
trusted_config.approval_policy.value(),
|
||||
trusted_config.permissions.approval_policy.value(),
|
||||
AskForApproval::OnRequest
|
||||
);
|
||||
|
||||
|
|
@ -1137,7 +1139,7 @@ trust_level = "untrusted"
|
|||
.build()
|
||||
.await?;
|
||||
assert_eq!(
|
||||
untrusted_config.approval_policy.value(),
|
||||
untrusted_config.permissions.approval_policy.value(),
|
||||
AskForApproval::UnlessTrusted
|
||||
);
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -168,10 +168,13 @@ impl StatusHistoryCell {
|
|||
("workdir", config.cwd.display().to_string()),
|
||||
("model", model_name.to_string()),
|
||||
("provider", config.model_provider_id.clone()),
|
||||
("approval", config.approval_policy.value().to_string()),
|
||||
(
|
||||
"approval",
|
||||
config.permissions.approval_policy.value().to_string(),
|
||||
),
|
||||
(
|
||||
"sandbox",
|
||||
summarize_sandbox_policy(config.sandbox_policy.get()),
|
||||
summarize_sandbox_policy(config.permissions.sandbox_policy.get()),
|
||||
),
|
||||
];
|
||||
if config.model_provider.wire_api == WireApi::Responses {
|
||||
|
|
@ -191,7 +194,7 @@ impl StatusHistoryCell {
|
|||
.find(|(k, _)| *k == "approval")
|
||||
.map(|(_, v)| v.clone())
|
||||
.unwrap_or_else(|| "<unknown>".to_string());
|
||||
let sandbox = match config.sandbox_policy.get() {
|
||||
let sandbox = match config.permissions.sandbox_policy.get() {
|
||||
SandboxPolicy::DangerFullAccess => "danger-full-access".to_string(),
|
||||
SandboxPolicy::ReadOnly { .. } => "read-only".to_string(),
|
||||
SandboxPolicy::WorkspaceWrite {
|
||||
|
|
@ -207,12 +210,13 @@ impl StatusHistoryCell {
|
|||
}
|
||||
}
|
||||
};
|
||||
let permissions = if config.approval_policy.value() == AskForApproval::OnRequest
|
||||
&& *config.sandbox_policy.get() == SandboxPolicy::new_workspace_write_policy()
|
||||
let permissions = if config.permissions.approval_policy.value() == AskForApproval::OnRequest
|
||||
&& *config.permissions.sandbox_policy.get()
|
||||
== SandboxPolicy::new_workspace_write_policy()
|
||||
{
|
||||
"Default".to_string()
|
||||
} else if config.approval_policy.value() == AskForApproval::Never
|
||||
&& *config.sandbox_policy.get() == SandboxPolicy::DangerFullAccess
|
||||
} else if config.permissions.approval_policy.value() == AskForApproval::Never
|
||||
&& *config.permissions.sandbox_policy.get() == SandboxPolicy::DangerFullAccess
|
||||
{
|
||||
"Full Access".to_string()
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ async fn status_snapshot_includes_reasoning_details() {
|
|||
config.model_provider_id = "openai".to_string();
|
||||
config.model_reasoning_summary = ReasoningSummary::Detailed;
|
||||
config
|
||||
.permissions
|
||||
.sandbox_policy
|
||||
.set(SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: Vec::new(),
|
||||
|
|
@ -177,10 +178,12 @@ async fn status_permissions_non_default_workspace_write_is_custom() {
|
|||
config.model = Some("gpt-5.1-codex-max".to_string());
|
||||
config.model_provider_id = "openai".to_string();
|
||||
config
|
||||
.permissions
|
||||
.approval_policy
|
||||
.set(AskForApproval::OnRequest)
|
||||
.expect("set approval policy");
|
||||
config
|
||||
.permissions
|
||||
.sandbox_policy
|
||||
.set(SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: Vec::new(),
|
||||
|
|
|
|||
|
|
@ -9,10 +9,13 @@ pub fn create_config_summary_entries(config: &Config, model: &str) -> Vec<(&'sta
|
|||
("workdir", config.cwd.display().to_string()),
|
||||
("model", model.to_string()),
|
||||
("provider", config.model_provider_id.clone()),
|
||||
("approval", config.approval_policy.value().to_string()),
|
||||
(
|
||||
"approval",
|
||||
config.permissions.approval_policy.value().to_string(),
|
||||
),
|
||||
(
|
||||
"sandbox",
|
||||
summarize_sandbox_policy(config.sandbox_policy.get()),
|
||||
summarize_sandbox_policy(config.permissions.sandbox_policy.get()),
|
||||
),
|
||||
];
|
||||
if config.model_provider.wire_api == WireApi::Responses {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue