mirror of
https://github.com/openai/codex.git
synced 2026-10-17 17:08:55 +00:00
Allow managed requirements to disable the Windows MXC sandbox (#49642)
## Why `windows.allowed_sandbox_implementations` permits MXC regardless of the listed implementations. Managed requirements need a separate way to block both explicit MXC configuration and automatic selection through `prefer_mxc`. ## What changed Add the optional managed requirement `windows.allow_mxc`. Setting it to `false` disables automatic MXC selection and rejects explicit `windows.sandbox = "mxc"` configuration with an error identifying the requirement. Leaving it unset or setting it to `true` preserves existing behavior. ## Testing Add coverage for the requirement alongside allowed sandbox implementations, preference resolution, and cloud-managed opt-out behavior for implicit, elevated, and explicit MXC configurations. GitOrigin-RevId: a458a7b4c92cf17e687dbbad8208884d1c097da5
This commit is contained in:
@@ -1218,6 +1218,7 @@ client_id = "mcp-client"
|
||||
codex_config::WindowsSandboxImplementationToml::Elevated,
|
||||
codex_config::WindowsSandboxImplementationToml::Unelevated,
|
||||
]),
|
||||
allow_mxc: None,
|
||||
}),
|
||||
..ConfigRequirementsToml::default()
|
||||
});
|
||||
|
||||
@@ -870,6 +870,8 @@ impl fmt::Display for WebSearchModeRequirement {
|
||||
#[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct WindowsRequirementsToml {
|
||||
pub allowed_sandbox_implementations: Option<Vec<WindowsSandboxImplementationToml>>,
|
||||
/// False blocks both explicit MXC configuration and automatic selection.
|
||||
pub allow_mxc: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -881,7 +883,7 @@ pub enum WindowsSandboxImplementationToml {
|
||||
|
||||
impl WindowsRequirementsToml {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.allowed_sandbox_implementations.is_none()
|
||||
self.allowed_sandbox_implementations.is_none() && self.allow_mxc.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1915,11 +1917,16 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
|
||||
/*source*/ None,
|
||||
),
|
||||
};
|
||||
let windows_sandbox_mode = match windows {
|
||||
let mxc_requirement_source = windows
|
||||
.as_ref()
|
||||
.filter(|windows| windows.value.allow_mxc == Some(false))
|
||||
.map(|windows| windows.source.clone());
|
||||
let mut windows_sandbox_mode = match windows {
|
||||
Some(Sourced {
|
||||
value:
|
||||
WindowsRequirementsToml {
|
||||
allowed_sandbox_implementations: Some(implementations),
|
||||
allow_mxc: _,
|
||||
},
|
||||
source: requirement_source,
|
||||
}) => {
|
||||
@@ -1965,6 +1972,7 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
|
||||
value:
|
||||
WindowsRequirementsToml {
|
||||
allowed_sandbox_implementations: None,
|
||||
allow_mxc: _,
|
||||
},
|
||||
..
|
||||
})
|
||||
@@ -1973,6 +1981,18 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
|
||||
/*source*/ None,
|
||||
),
|
||||
};
|
||||
if let Some(requirement_source) = mxc_requirement_source {
|
||||
windows_sandbox_mode.add_validator(move |candidate| match candidate {
|
||||
Some(WindowsSandboxModeToml::Mxc) => Err(ConstraintError::InvalidValue {
|
||||
field_name: "windows.sandbox",
|
||||
candidate: format!("{candidate:?}"),
|
||||
allowed: "windows.allow_mxc = false".to_string(),
|
||||
requirement_source: requirement_source.clone(),
|
||||
}),
|
||||
Some(WindowsSandboxModeToml::Elevated | WindowsSandboxModeToml::Unelevated)
|
||||
| None => Ok(()),
|
||||
})?;
|
||||
}
|
||||
let exec_policy = match rules {
|
||||
Some(Sourced { value, source }) => {
|
||||
let policy = value.to_requirements_policy().map_err(|err| {
|
||||
@@ -2827,6 +2847,7 @@ mod tests {
|
||||
};
|
||||
let windows = WindowsRequirementsToml {
|
||||
allowed_sandbox_implementations: Some(vec![WindowsSandboxImplementationToml::Elevated]),
|
||||
allow_mxc: None,
|
||||
};
|
||||
let enforce_residency = ResidencyRequirement::Us;
|
||||
let enforce_source = source.clone();
|
||||
@@ -3812,6 +3833,22 @@ allowed_approvals_reviewers = ["user"]
|
||||
);
|
||||
assert!(requirements.windows_sandbox_mode.can_set(&None).is_err());
|
||||
|
||||
for allow_mxc in [true, false] {
|
||||
let config = from_str(&format!("{toml_str}\nallow_mxc = {allow_mxc}"))?;
|
||||
let requirements: ConfigRequirements = with_unknown_source(config).try_into()?;
|
||||
let constraint = &requirements.windows_sandbox_mode;
|
||||
assert_eq!(
|
||||
[
|
||||
Some(WindowsSandboxModeToml::Elevated),
|
||||
Some(WindowsSandboxModeToml::Unelevated),
|
||||
Some(WindowsSandboxModeToml::Mxc),
|
||||
None,
|
||||
]
|
||||
.map(|mode| constraint.can_set(&mode).is_ok()),
|
||||
[true, false, allow_mxc, false]
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -11102,22 +11102,41 @@ async fn explicit_sandbox_mode_falls_back_when_disallowed_by_requirements() -> s
|
||||
#[tokio::test]
|
||||
async fn local_mxc_preference_preserves_configured_backend() -> anyhow::Result<()> {
|
||||
use codex_sandboxing::SandboxType::WindowsMxc;
|
||||
use codex_sandboxing::SandboxType::WindowsRestrictedToken;
|
||||
use codex_sandboxing::SandboxType::WindowsRestrictedToken as RestrictedToken;
|
||||
|
||||
let codex_home = TempDir::new()?;
|
||||
for (prefer, resolved_preference, binding, mode, expected) in [
|
||||
(true, true, true, "unelevated", WindowsMxc),
|
||||
(true, false, true, "unelevated", WindowsRestrictedToken),
|
||||
(true, false, false, "unelevated", WindowsRestrictedToken),
|
||||
(false, false, true, "unelevated", WindowsRestrictedToken),
|
||||
(false, false, false, "mxc", WindowsMxc),
|
||||
for (prefer, resolved_preference, binding, allow_mxc, mode, expected) in [
|
||||
(true, true, true, true, "unelevated", WindowsMxc),
|
||||
(true, false, true, true, "unelevated", RestrictedToken),
|
||||
(true, false, true, false, "unelevated", RestrictedToken),
|
||||
(true, false, false, true, "unelevated", RestrictedToken),
|
||||
(false, false, true, true, "unelevated", RestrictedToken),
|
||||
(false, false, false, true, "mxc", WindowsMxc),
|
||||
] {
|
||||
let cfg: ConfigToml = toml::from_str(&format!(
|
||||
"[windows]\nsandbox = {mode:?}\n[features]\nprefer_mxc = {prefer}\n\
|
||||
[features.network_proxy]\nenabled = true\nallow_local_binding = {binding}\n"
|
||||
))?;
|
||||
std::fs::write(
|
||||
codex_home.path().join(CONFIG_TOML_FILE),
|
||||
toml::to_string(&cfg)?,
|
||||
)?;
|
||||
let mut config = ConfigBuilder::without_managed_config_for_tests()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.fallback_cwd(Some(codex_home.path().to_path_buf()))
|
||||
.cloud_config_bundle(
|
||||
CloudConfigBundleFixture::loader_with_enterprise_requirement(format!(
|
||||
"[windows]\nallow_mxc = {allow_mxc}\n"
|
||||
)),
|
||||
)
|
||||
.build()
|
||||
.await?;
|
||||
assert_eq!(
|
||||
network_config_allows_mxc(
|
||||
config_allows_mxc(
|
||||
&config
|
||||
.config_layer_stack
|
||||
.requirements()
|
||||
.windows_sandbox_mode,
|
||||
&EffectivePermissionSelection {
|
||||
profiles: None,
|
||||
selected_profile_id: None,
|
||||
@@ -11130,20 +11149,11 @@ async fn local_mxc_preference_preserves_configured_backend() -> anyhow::Result<(
|
||||
cfg.features.as_ref(),
|
||||
/*enable_network_proxy*/ true,
|
||||
)?,
|
||||
binding,
|
||||
binding && allow_mxc,
|
||||
);
|
||||
let mut config = Config::load_from_base_config_with_overrides(
|
||||
cfg,
|
||||
ConfigOverrides {
|
||||
cwd: Some(codex_home.path().to_path_buf()),
|
||||
..Default::default()
|
||||
},
|
||||
codex_home.abs(),
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(
|
||||
config.prefer_mxc,
|
||||
prefer && binding && codex_sandboxing::windows_mxc_available(),
|
||||
prefer && binding && allow_mxc && codex_sandboxing::windows_mxc_available(),
|
||||
);
|
||||
// Exercise both resolved decisions independently of the host's native support.
|
||||
config.prefer_mxc = resolved_preference;
|
||||
@@ -11156,7 +11166,7 @@ async fn local_mxc_preference_preserves_configured_backend() -> anyhow::Result<(
|
||||
if mode == "mxc" {
|
||||
WindowsMxc
|
||||
} else {
|
||||
WindowsRestrictedToken
|
||||
RestrictedToken
|
||||
},
|
||||
expected
|
||||
),
|
||||
|
||||
@@ -218,7 +218,7 @@ pub use permissions::resolve_permission_profile;
|
||||
pub(crate) use resolved_permission_profile::PermissionProfileState;
|
||||
pub use token_budget_startup::TokenBudgetStartupConfig;
|
||||
pub use windows_sandbox_config::PreparedWindowsSandboxConfig;
|
||||
use windows_sandbox_config::network_config_allows_mxc;
|
||||
use windows_sandbox_config::config_allows_mxc;
|
||||
pub use windows_sandbox_config::prepare_windows_sandbox_config;
|
||||
use windows_sandbox_config::resolve_windows_sandbox_type;
|
||||
|
||||
@@ -3518,7 +3518,8 @@ impl Config {
|
||||
permission_config_syntax,
|
||||
);
|
||||
let prefer_mxc = features.enabled(Feature::PreferMxc)
|
||||
&& network_config_allows_mxc(
|
||||
&& config_allows_mxc(
|
||||
&constrained_windows_sandbox_mode,
|
||||
&effective_permission_selection,
|
||||
profiles_are_active,
|
||||
permission_profile.as_ref(),
|
||||
|
||||
@@ -58,6 +58,9 @@ pub fn prepare_windows_sandbox_config(
|
||||
constraint: &mut ConstrainedWithSource<Option<WindowsSandboxModeToml>>,
|
||||
warnings: &mut Vec<String>,
|
||||
) -> std::io::Result<PreparedWindowsSandboxConfig> {
|
||||
if configured_mode == Some(WindowsSandboxModeToml::Mxc) {
|
||||
constraint.can_set(&configured_mode)?;
|
||||
}
|
||||
let selected_mode = configured_mode.or(match feature_level {
|
||||
WindowsSandboxLevel::Elevated => Some(WindowsSandboxModeToml::Elevated),
|
||||
WindowsSandboxLevel::RestrictedToken => Some(WindowsSandboxModeToml::Unelevated),
|
||||
@@ -93,7 +96,8 @@ pub fn prepare_windows_sandbox_config(
|
||||
|
||||
/// Managed requirements take precedence; otherwise preserve explicit
|
||||
/// feature-level or active-profile binding denials during automatic selection.
|
||||
pub(super) fn network_config_allows_mxc(
|
||||
pub(super) fn config_allows_mxc(
|
||||
windows_sandbox_mode: &ConstrainedWithSource<Option<WindowsSandboxModeToml>>,
|
||||
permission_selection: &EffectivePermissionSelection<'_>,
|
||||
profiles_are_active: bool,
|
||||
permission_profile: Option<&PermissionProfile>,
|
||||
@@ -101,6 +105,12 @@ pub(super) fn network_config_allows_mxc(
|
||||
features: Option<&FeaturesToml>,
|
||||
enable_network_proxy: bool,
|
||||
) -> std::io::Result<bool> {
|
||||
if windows_sandbox_mode
|
||||
.can_set(&Some(WindowsSandboxModeToml::Mxc))
|
||||
.is_err()
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
let profile_local_binding = if profiles_are_active
|
||||
&& permission_profile.is_none_or(profile_allows_configured_network_proxy)
|
||||
&& let Some(profile) = permission_selection.selected_profile_id
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use codex_config::CloudConfigBundleLoader;
|
||||
use codex_config::LoaderOverrides;
|
||||
use codex_config::test_support::CloudConfigBundleFixture;
|
||||
use codex_config::types::WindowsSandboxModeToml;
|
||||
use codex_core::CodexThreadSettingsOverrides;
|
||||
use codex_core::config::ConfigBuilder;
|
||||
use codex_features::Feature;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||
use codex_protocol::sandbox::SandboxType;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use core_test_support::responses::start_mock_server;
|
||||
use core_test_support::skip_if_target_windows;
|
||||
@@ -18,6 +22,52 @@ use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[tokio::test]
|
||||
async fn managed_mxc_opt_out_blocks_preferred_and_explicit_selection() -> Result<()> {
|
||||
for mode in [None, Some("elevated"), Some("mxc")] {
|
||||
let home = TempDir::new()?;
|
||||
let sandbox = mode
|
||||
.map(|mode| format!("sandbox = {mode:?}\n"))
|
||||
.unwrap_or_default();
|
||||
std::fs::write(
|
||||
home.path().join("config.toml"),
|
||||
format!("[windows]\n{sandbox}[features]\nprefer_mxc = true\n"),
|
||||
)?;
|
||||
let result = ConfigBuilder::default()
|
||||
.loader_overrides(LoaderOverrides::without_managed_config_for_tests())
|
||||
.codex_home(home.path().to_path_buf())
|
||||
.fallback_cwd(Some(home.path().to_path_buf()))
|
||||
.cloud_config_bundle(
|
||||
CloudConfigBundleFixture::loader_with_enterprise_requirement(
|
||||
"[windows]\nallow_mxc = false\n[features]\nelevated_windows_sandbox = true\n",
|
||||
),
|
||||
)
|
||||
.build()
|
||||
.await;
|
||||
if mode == Some("mxc") {
|
||||
let error =
|
||||
result.expect_err("explicit MXC should be rejected by managed requirements");
|
||||
assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput);
|
||||
assert!(error.to_string().contains("windows.allow_mxc = false"));
|
||||
} else {
|
||||
let config = result?;
|
||||
assert_eq!(
|
||||
(
|
||||
config.permissions.windows_sandbox_mode,
|
||||
config.prefer_mxc,
|
||||
config.effective_local_windows_sandbox_type()
|
||||
),
|
||||
(
|
||||
mode.map(|_| WindowsSandboxModeToml::Elevated),
|
||||
false,
|
||||
SandboxType::WindowsRestrictedToken,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn refreshed_cloud_bundle_updates_later_sessions() -> Result<()> {
|
||||
let server = start_mock_server().await;
|
||||
|
||||
@@ -974,6 +974,7 @@ interrupt_message = false
|
||||
in_app_browser: None,
|
||||
windows: Some(WindowsRequirementsToml {
|
||||
allowed_sandbox_implementations: None,
|
||||
allow_mxc: None,
|
||||
}),
|
||||
additional_developer_instructions: None,
|
||||
guardian_policy_config: Some("Use the managed guardian policy.".to_string()),
|
||||
|
||||
Reference in New Issue
Block a user