feat: support allowed_sandbox_modes in requirements.toml (#8298)

This adds support for `allowed_sandbox_modes` in `requirements.toml` and
provides legacy support for constraining sandbox modes in
`managed_config.toml`. This is converted to `Constrained<SandboxPolicy>`
in `ConfigRequirements` and applied to `Config` such that constraints
are enforced throughout the harness.

Note that, because `managed_config.toml` is deprecated, we do not add
support for the new `external-sandbox` variant recently introduced in
https://github.com/openai/codex/pull/8290. As noted, that variant is not
supported in `config.toml` today, but can be configured programmatically
via app server.
This commit is contained in:
Michael Bolin
2025-12-19 13:09:20 -08:00
committed by GitHub
parent ec3738b47e
commit dc61fc5f50
25 changed files with 345 additions and 96 deletions

View File

@@ -8,6 +8,7 @@ use std::time::Duration;
use codex_app_server_protocol::AuthMode;
use codex_backend_client::Client as BackendClient;
use codex_core::config::Config;
use codex_core::config::ConstraintResult;
use codex_core::config::types::Notifications;
use codex_core::features::FEATURES;
use codex_core::features::Feature;
@@ -2725,12 +2726,12 @@ impl ChatWidget {
/// Open a popup to choose the approvals mode (ask for approval policy + sandbox policy).
pub(crate) fn open_approvals_popup(&mut self) {
let current_approval = self.config.approval_policy.value();
let current_sandbox = self.config.sandbox_policy.clone();
let current_sandbox = self.config.sandbox_policy.get();
let mut items: Vec<SelectionItem> = Vec::new();
let presets: Vec<ApprovalPreset> = builtin_approval_presets();
for preset in presets.into_iter() {
let is_current =
Self::preset_matches_current(current_approval, &current_sandbox, &preset);
Self::preset_matches_current(current_approval, current_sandbox, &preset);
let name = preset.label.to_string();
let description = Some(preset.description.to_string());
let disabled_reason = match self.config.approval_policy.can_set(&preset.approval) {
@@ -2879,7 +2880,7 @@ impl ChatWidget {
self.config.codex_home.as_path(),
cwd.as_path(),
&env_map,
&self.config.sandbox_policy,
self.config.sandbox_policy.get(),
Some(self.config.codex_home.as_path()),
) {
Ok(_) => None,
@@ -2978,7 +2979,7 @@ impl ChatWidget {
let mode_label = preset
.as_ref()
.map(|p| describe_policy(&p.sandbox))
.unwrap_or_else(|| describe_policy(&self.config.sandbox_policy));
.unwrap_or_else(|| describe_policy(self.config.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. "
@@ -3151,17 +3152,19 @@ impl ChatWidget {
}
/// Set the sandbox policy in the widget's config copy.
pub(crate) fn set_sandbox_policy(&mut self, policy: SandboxPolicy) {
pub(crate) fn set_sandbox_policy(&mut self, policy: SandboxPolicy) -> ConstraintResult<()> {
#[cfg(target_os = "windows")]
let should_clear_downgrade = !matches!(policy, SandboxPolicy::ReadOnly)
let should_clear_downgrade = !matches!(&policy, SandboxPolicy::ReadOnly)
|| codex_core::get_platform_sandbox().is_some();
self.config.sandbox_policy = policy;
self.config.sandbox_policy.set(policy)?;
#[cfg(target_os = "windows")]
if should_clear_downgrade {
self.config.forced_auto_mode_downgraded_on_windows = false;
}
Ok(())
}
pub(crate) fn set_feature_enabled(&mut self, feature: Feature, enabled: bool) {