From 5b60e97fdc0ddfd8f740ec080e350eaa467ec57b Mon Sep 17 00:00:00 2001 From: Eva Wong Date: Wed, 18 Mar 2026 11:13:51 -0700 Subject: [PATCH] Restrict fail-closed MDM requirements handling --- codex-rs/core/src/config_loader/macos.rs | 64 +++++++++++++++++++++--- codex-rs/core/src/config_loader/tests.rs | 44 ++++++++++++++++ 2 files changed, 101 insertions(+), 7 deletions(-) diff --git a/codex-rs/core/src/config_loader/macos.rs b/codex-rs/core/src/config_loader/macos.rs index 85773201c2..56fb6251b8 100644 --- a/codex-rs/core/src/config_loader/macos.rs +++ b/codex-rs/core/src/config_loader/macos.rs @@ -193,12 +193,10 @@ fn parse_managed_config_base64(encoded: &str) -> io::Result bool { - let path = dropped_entry - .split_once(':') - .map_or(dropped_entry, |(path, _)| path) - .trim(); - let top_level = path.split(['.', '[']).next().unwrap_or(path); - matches!(top_level, "approval_policy" | "sandbox_mode") + matches!( + managed_entry_top_level_key(dropped_entry), + "approval_policy" | "sandbox_mode" + ) } fn parse_managed_requirements_base64(encoded: &str) -> io::Result { @@ -210,7 +208,44 @@ fn parse_managed_requirements_base64(encoded: &str) -> io::Result(&raw_toml).map_err(|err| { + let parsed = toml::from_str::(&raw_toml).map_err(|err| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("Error parsing managed requirements from {source}: {err}"), + ) + })?; + let TomlValue::Table(parsed) = parsed else { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("Error parsing managed requirements from {source}: root must be a table"), + )); + }; + + let sanitized = sanitize_toml_value::(TomlValue::Table(parsed)) + .map_err(|err| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("Error parsing managed requirements from {source}: {err}"), + ) + })?; + if let Some(dropped_entry) = sanitized + .dropped_entries + .iter() + .find(|entry| is_invalid_security_managed_requirements_entry(entry)) + { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("Error parsing managed requirements from {source}: {dropped_entry}"), + )); + } + for dropped_entry in &sanitized.dropped_entries { + tracing::warn!( + dropped_entry = %dropped_entry, + "Ignoring invalid MDM managed requirements entry", + ); + } + + sanitized.value.try_into().map_err(|err: toml::de::Error| { io::Error::new( io::ErrorKind::InvalidData, format!("Error parsing managed requirements from {source}: {err}"), @@ -218,6 +253,21 @@ fn parse_managed_requirements_base64(encoded: &str) -> io::Result bool { + matches!( + managed_entry_top_level_key(dropped_entry), + "allowed_approval_policies" | "allowed_sandbox_modes" | "allowed_web_search_modes" + ) +} + +fn managed_entry_top_level_key(dropped_entry: &str) -> &str { + let path = dropped_entry + .split_once(':') + .map_or(dropped_entry, |(path, _)| path) + .trim(); + path.split(['.', '[']).next().unwrap_or(path) +} + fn decode_managed_preferences_base64(encoded: &str) -> io::Result { String::from_utf8(BASE64_STANDARD.decode(encoded.as_bytes()).map_err(|err| { tracing::error!("Failed to decode managed value as base64: {err}",); diff --git a/codex-rs/core/src/config_loader/tests.rs b/codex-rs/core/src/config_loader/tests.rs index 6dc15ac02b..a8a6222761 100644 --- a/codex-rs/core/src/config_loader/tests.rs +++ b/codex-rs/core/src/config_loader/tests.rs @@ -578,6 +578,50 @@ async fn managed_preferences_invalid_requirements_fail_closed() -> anyhow::Resul Ok(()) } +#[cfg(target_os = "macos")] +#[tokio::test] +async fn managed_preferences_ignore_invalid_non_security_requirements_entry() -> anyhow::Result<()> +{ + use base64::Engine; + + let tmp = tempdir()?; + + let state = load_config_layers_state( + tmp.path(), + Some(AbsolutePathBuf::try_from(tmp.path())?), + &[] as &[(String, TomlValue)], + LoaderOverrides { + managed_config_path: Some(tmp.path().join("managed_config.toml")), + managed_preferences_base64: Some(String::new()), + macos_managed_config_requirements_base64: Some( + base64::prelude::BASE64_STANDARD.encode( + r#" +allowed_sandbox_modes = ["read-only"] + +[features] +personality = "bogus" +"# + .as_bytes(), + ), + ), + }, + CloudRequirementsLoader::default(), + ) + .await?; + + assert_eq!( + state.requirements_toml().allowed_sandbox_modes, + Some(vec![crate::config_loader::SandboxModeRequirement::ReadOnly]) + ); + assert_eq!(state.requirements_toml().feature_requirements, None); + assert_eq!( + *state.requirements().sandbox_policy.get(), + SandboxPolicy::new_read_only_policy() + ); + + Ok(()) +} + #[cfg(target_os = "macos")] #[tokio::test] async fn managed_preferences_requirements_are_applied() -> anyhow::Result<()> {