Files
codex/codex-rs/config/src/loader/macos.rs
T
acrognale-oai 22a3f6d5d8 Enforce application network policy across app-server requests (#47407)
## What changed

- Load `application.network` requirements at startup and explicit config/account reloads, applying local policy before authentication and cloud configuration bootstrap.
- Block traffic when policy loading fails, cancel requests that a new policy no longer permits, and reject configuration loads based on superseded policy snapshots. Local requirements edits take effect on the next explicit reload or restart.
- Bind authenticated clients to the current account and revoke them when account ownership changes. Carry policy enforcement through backend requests, realtime connections, workload identity exchanges, and code-mode gRPC streams while preserving gRPC trailers.
- Treat denied credential refreshes as policy errors without retrying them or invalidating stored credentials.

## Testing

Add regression coverage for startup restrictions, reload ordering, cancellation after restrictive or malformed requirements edits, account revocation, gRPC destination denial, workload identity cancellation, and credential preservation after denied OAuth refresh.

GitOrigin-RevId: 0cb4c720a906397e9df06675f01ecf60d741101f
2026-09-23 00:47:07 +00:00

308 lines
10 KiB
Rust

//! Loads administrator configuration only from forced macOS preferences.
//!
//! Ordinary user defaults must never become trusted managed configuration layers.
//! Forced values must be strings containing base64-encoded TOML.
use crate::RequirementsLayerEntry;
use crate::config_requirements::RequirementSource;
use crate::config_toml::ConfigToml;
use crate::diagnostics::ConfigDiagnosticSource;
use crate::diagnostics::config_error_from_toml_for_source;
use crate::diagnostics::io_error_from_config_error;
use crate::strict_config::config_error_from_ignored_toml_value_fields_for_source_name;
use base64::Engine;
use base64::prelude::BASE64_STANDARD;
use codex_utils_absolute_path::AbsolutePathBufGuard;
use core_foundation::base::Boolean;
use core_foundation::base::CFType;
use core_foundation::base::TCFType;
use core_foundation::string::CFString;
use core_foundation::string::CFStringRef;
use std::ffi::c_void;
use std::io;
use std::path::Path;
use tokio::task;
use toml::Value as TomlValue;
const MANAGED_PREFERENCES_APPLICATION_ID: &str = "com.openai.codex";
const MANAGED_PREFERENCES_CONFIG_KEY: &str = "config_toml_base64";
const MANAGED_PREFERENCES_REQUIREMENTS_KEY: &str = "requirements_toml_base64";
#[link(name = "CoreFoundation", kind = "framework")]
unsafe extern "C" {
fn CFPreferencesCopyAppValue(key: CFStringRef, application_id: CFStringRef) -> *mut c_void;
fn CFPreferencesAppValueIsForced(key: CFStringRef, application_id: CFStringRef) -> Boolean;
}
#[derive(Debug, Clone)]
pub(super) struct ManagedAdminConfigLayer {
pub config: TomlValue,
pub raw_toml: String,
}
pub(super) fn managed_preferences_requirements_source() -> RequirementSource {
RequirementSource::MdmManagedPreferences {
domain: MANAGED_PREFERENCES_APPLICATION_ID.to_string(),
key: MANAGED_PREFERENCES_REQUIREMENTS_KEY.to_string(),
}
}
pub(super) fn has_managed_preferences() -> io::Result<bool> {
Ok(
load_managed_preference(MANAGED_PREFERENCES_CONFIG_KEY)?.is_some()
|| load_managed_preference(MANAGED_PREFERENCES_REQUIREMENTS_KEY)?.is_some(),
)
}
pub(crate) async fn load_managed_admin_config_layer(
override_base64: Option<&str>,
strict_config: bool,
base_dir: &Path,
) -> io::Result<Option<ManagedAdminConfigLayer>> {
if let Some(encoded) = override_base64 {
let trimmed = encoded.trim();
return if trimmed.is_empty() {
Ok(None)
} else {
parse_managed_config_base64(trimmed, strict_config, base_dir).map(Some)
};
}
let base_dir = base_dir.to_path_buf();
match task::spawn_blocking(move || load_managed_admin_config(strict_config, &base_dir)).await {
Ok(result) => result,
Err(join_err) => {
if join_err.is_cancelled() {
tracing::error!("Managed config load task was cancelled");
} else {
tracing::error!("Managed config load task failed: {join_err}");
}
Err(io::Error::other("Failed to load managed config"))
}
}
}
fn load_managed_admin_config(
strict_config: bool,
base_dir: &Path,
) -> io::Result<Option<ManagedAdminConfigLayer>> {
load_managed_preference(MANAGED_PREFERENCES_CONFIG_KEY)?
.as_deref()
.map(str::trim)
.map(|encoded| parse_managed_config_base64(encoded, strict_config, base_dir))
.transpose()
}
pub(crate) async fn load_managed_admin_requirements_layer(
override_base64: Option<&str>,
) -> io::Result<Option<RequirementsLayerEntry>> {
if let Some(encoded) = override_base64 {
let trimmed = encoded.trim();
if trimmed.is_empty() {
return Ok(None);
}
return parse_managed_requirements_base64(trimmed).map(|contents| {
Some(RequirementsLayerEntry::from_toml(
managed_preferences_requirements_source(),
contents,
))
});
}
match task::spawn_blocking(load_managed_admin_requirements).await {
Ok(result) => Ok(result?.map(|contents| {
RequirementsLayerEntry::from_toml(managed_preferences_requirements_source(), contents)
})),
Err(join_err) => {
if join_err.is_cancelled() {
tracing::error!("Managed requirements load task was cancelled");
} else {
tracing::error!("Managed requirements load task failed: {join_err}");
}
Err(io::Error::other("Failed to load managed requirements"))
}
}
}
fn load_managed_admin_requirements() -> io::Result<Option<String>> {
load_managed_preference(MANAGED_PREFERENCES_REQUIREMENTS_KEY)?
.as_deref()
.map(str::trim)
.map(parse_managed_requirements_base64)
.transpose()
}
pub(super) fn synchronize_managed_preferences() -> io::Result<()> {
#[link(name = "CoreFoundation", kind = "framework")]
unsafe extern "C" {
fn CFPreferencesAppSynchronize(application_id: CFStringRef) -> u8;
}
let application_id = CFString::new(MANAGED_PREFERENCES_APPLICATION_ID);
if unsafe { CFPreferencesAppSynchronize(application_id.as_concrete_TypeRef()) } == 0 {
return Err(io::Error::other(
"Failed to synchronize managed preferences",
));
}
Ok(())
}
fn load_managed_preference(key_name: &str) -> io::Result<Option<String>> {
let key = CFString::new(key_name);
let application = CFString::new(MANAGED_PREFERENCES_APPLICATION_ID);
load_managed_preference_with(
key_name,
|| preference_is_forced(&key, &application),
|| copy_preference_value(&key, &application),
)
}
fn preference_is_forced(key: &CFString, application: &CFString) -> bool {
unsafe {
CFPreferencesAppValueIsForced(key.as_concrete_TypeRef(), application.as_concrete_TypeRef())
!= 0
}
}
fn copy_preference_value(key: &CFString, application: &CFString) -> Option<CFType> {
let value_ref = unsafe {
CFPreferencesCopyAppValue(key.as_concrete_TypeRef(), application.as_concrete_TypeRef())
};
if value_ref.is_null() {
return None;
}
// CopyAppValue returns an owned property-list value, not necessarily a string.
Some(unsafe { CFType::wrap_under_create_rule(value_ref) })
}
fn load_managed_preference_with(
key_name: &str,
mut is_forced: impl FnMut() -> bool,
copy_value: impl FnOnce() -> Option<CFType>,
) -> io::Result<Option<String>> {
// CopyAppValue also searches user-writable domains. Only forced values may
// supply administrator configuration or override lower requirements layers.
if !is_forced() {
tracing::debug!(
"No forced managed preference for {MANAGED_PREFERENCES_APPLICATION_ID} key {key_name}"
);
return Ok(None);
}
let Some(value) = copy_value() else {
tracing::debug!(
"Managed preferences for {MANAGED_PREFERENCES_APPLICATION_ID} key {key_name} not found"
);
return Ok(None);
};
// Reject a user-default fallback if the preference stopped being forced
// during the read. These separate calls do not form an atomic snapshot.
if !is_forced() {
tracing::debug!(
"Managed preference {MANAGED_PREFERENCES_APPLICATION_ID}:{key_name} is no longer forced after reading"
);
return Ok(None);
}
let value = value.downcast::<CFString>().ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Managed preference {MANAGED_PREFERENCES_APPLICATION_ID}:{key_name} must be a string"
),
)
})?;
Ok(Some(value.to_string()))
}
fn parse_managed_config_base64(
encoded: &str,
strict_config: bool,
base_dir: &Path,
) -> io::Result<ManagedAdminConfigLayer> {
let raw_toml = decode_managed_preferences_base64(encoded)?;
let source_name =
format!("{MANAGED_PREFERENCES_APPLICATION_ID}:{MANAGED_PREFERENCES_CONFIG_KEY}");
let parsed = toml::from_str::<TomlValue>(&raw_toml).map_err(|err| {
tracing::error!("Failed to parse managed config TOML: {err}");
if strict_config {
let config_error = config_error_from_toml_for_source(
ConfigDiagnosticSource::DisplayName(&source_name),
&raw_toml,
err.clone(),
);
io_error_from_config_error(io::ErrorKind::InvalidData, config_error, Some(err))
} else {
io::Error::new(io::ErrorKind::InvalidData, err)
}
})?;
validate_managed_config_toml_strictly_if_requested(
strict_config,
&source_name,
&raw_toml,
&parsed,
base_dir,
)?;
match parsed {
TomlValue::Table(parsed) => Ok(ManagedAdminConfigLayer {
config: TomlValue::Table(parsed),
raw_toml,
}),
other => {
tracing::error!("Managed config TOML must have a table at the root, found {other:?}",);
Err(io::Error::new(
io::ErrorKind::InvalidData,
"managed config root must be a table",
))
}
}
}
fn validate_managed_config_toml_strictly_if_requested(
strict_config: bool,
source_name: &str,
raw_toml: &str,
parsed: &TomlValue,
base_dir: &Path,
) -> io::Result<()> {
if !strict_config {
return Ok(());
}
let _guard = AbsolutePathBufGuard::new(base_dir);
if let Some(config_error) = config_error_from_ignored_toml_value_fields_for_source_name::<
ConfigToml,
>(source_name, raw_toml, parsed.clone())
{
Err(io_error_from_config_error(
io::ErrorKind::InvalidData,
config_error,
/*source*/ None,
))
} else {
Ok(())
}
}
fn parse_managed_requirements_base64(encoded: &str) -> io::Result<String> {
decode_managed_preferences_base64(encoded)
}
fn decode_managed_preferences_base64(encoded: &str) -> io::Result<String> {
String::from_utf8(BASE64_STANDARD.decode(encoded.as_bytes()).map_err(|err| {
tracing::error!("Failed to decode managed value as base64: {err}",);
io::Error::new(io::ErrorKind::InvalidData, err)
})?)
.map_err(|err| {
tracing::error!("Managed value base64 contents were not valid UTF-8: {err}",);
io::Error::new(io::ErrorKind::InvalidData, err)
})
}
#[cfg(test)]
#[path = "macos_tests.rs"]
mod tests;