use crate::history_cell::PlainHistoryCell; use crate::legacy_core::config::Config; use crate::legacy_core::config::Permissions; use crate::session_state::SessionNetworkProxyRuntime; use codex_config::CONFIG_TOML_FILE; use codex_config::ConfigLayerEntry; use codex_config::ConfigLayerSource; use codex_config::ConfigLayerStack; use codex_config::ConfigLayerStackOrdering; use codex_config::ManagedHooksRequirementsToml; use codex_config::NetworkConstraints; use codex_config::NetworkDomainPermissionToml; use codex_config::NetworkUnixSocketPermissionToml; use codex_config::RequirementSource; use codex_config::ResidencyRequirement; use codex_config::SandboxModeRequirement; use codex_config::WebSearchModeRequirement; use codex_config::format_config_layer_source; use codex_protocol::models::PermissionProfile; use codex_protocol::permissions::NetworkSandboxPolicy; use ratatui::style::Stylize; use ratatui::text::Line; use toml::Value as TomlValue; pub(crate) fn new_debug_config_output( config: &Config, session_network_proxy: Option<&SessionNetworkProxyRuntime>, ) -> PlainHistoryCell { let mut lines = render_debug_config_lines(&config.config_layer_stack, |mode| { sandbox_mode_is_allowed_by_permissions(&config.permissions, mode) }); if let Some(proxy) = session_network_proxy { lines.push("".into()); lines.push("Session runtime:".bold().into()); lines.push(" - network_proxy".into()); let SessionNetworkProxyRuntime { http_addr, socks_addr, } = proxy; let all_proxy = session_all_proxy_url( http_addr, socks_addr, config .permissions .network .as_ref() .is_some_and(crate::legacy_core::config::NetworkProxySpec::socks_enabled), ); lines.push(format!(" - HTTP_PROXY = http://{http_addr}").into()); lines.push(format!(" - ALL_PROXY = {all_proxy}").into()); } PlainHistoryCell::new(lines) } fn sandbox_mode_is_allowed_by_permissions( permissions: &Permissions, mode: SandboxModeRequirement, ) -> bool { let permission_profile = match mode { SandboxModeRequirement::ReadOnly => PermissionProfile::read_only(), SandboxModeRequirement::WorkspaceWrite => PermissionProfile::workspace_write(), SandboxModeRequirement::DangerFullAccess => PermissionProfile::Disabled, SandboxModeRequirement::ExternalSandbox => PermissionProfile::External { network: NetworkSandboxPolicy::Restricted, }, }; permissions .can_set_permission_profile(&permission_profile) .is_ok() } fn session_all_proxy_url(http_addr: &str, socks_addr: &str, socks_enabled: bool) -> String { if socks_enabled { format!("socks5h://{socks_addr}") } else { format!("http://{http_addr}") } } fn render_debug_config_lines( stack: &ConfigLayerStack, sandbox_mode_is_effectively_allowed: impl Fn(SandboxModeRequirement) -> bool, ) -> Vec> { let mut lines = vec!["/debug-config".magenta().into(), "".into()]; lines.push( "Config layer stack (lowest precedence first):" .bold() .into(), ); let layers = stack.get_layers( ConfigLayerStackOrdering::LowestPrecedenceFirst, /*include_disabled*/ true, ); if layers.is_empty() { lines.push(" ".dim().into()); } else { for (index, layer) in layers.iter().enumerate() { let source = format_config_layer_source(&layer.name, CONFIG_TOML_FILE); let status = if layer.is_disabled() { "disabled" } else { "enabled" }; lines.push(format!(" {}. {source} ({status})", index + 1).into()); lines.extend(render_non_file_layer_details(layer)); if let Some(reason) = &layer.disabled_reason { lines.push(format!(" reason: {reason}").dim().into()); } } } let requirements = stack.requirements(); let requirements_toml = stack.requirements_toml(); lines.push("".into()); lines.push("Requirements:".bold().into()); let mut requirement_lines = Vec::new(); if let Some(policies) = requirements_toml.allowed_approval_policies.as_ref() { let value = join_or_empty(policies.iter().map(ToString::to_string).collect::>()); requirement_lines.push(requirement_line( "allowed_approval_policies", value, requirements.approval_policy.source.as_ref(), )); } if let Some(reviewers) = requirements_toml.allowed_approvals_reviewers.as_ref() { let value = join_or_empty( reviewers .iter() .map(ToString::to_string) .collect::>(), ); requirement_lines.push(requirement_line( "allowed_approvals_reviewers", value, requirements.approvals_reviewer.source.as_ref(), )); } if let Some(modes) = requirements_toml.allowed_sandbox_modes.as_ref() { let value = join_or_empty( modes .iter() .copied() .filter(|mode| sandbox_mode_is_effectively_allowed(*mode)) .map(format_sandbox_mode_requirement) .collect::>(), ); requirement_lines.push(requirement_line( "allowed_sandbox_modes", value, requirements.permission_profile.source.as_ref(), )); } if let Some(modes) = requirements_toml.allowed_web_search_modes.as_ref() { let normalized = normalize_allowed_web_search_modes(modes); let value = join_or_empty( normalized .iter() .map(ToString::to_string) .collect::>(), ); requirement_lines.push(requirement_line( "allowed_web_search_modes", value, requirements.web_search_mode.source.as_ref(), )); } if let Some(allow_managed_hooks_only) = requirements_toml.allow_managed_hooks_only { requirement_lines.push(requirement_line( "allow_managed_hooks_only", allow_managed_hooks_only.to_string(), requirements .allow_managed_hooks_only .as_ref() .map(|sourced| &sourced.source), )); } if let Some(allow_appshots) = requirements_toml.allow_appshots { requirement_lines.push(requirement_line( "allow_appshots", allow_appshots.to_string(), requirements .allow_appshots .as_ref() .map(|sourced| &sourced.source), )); } if let Some(allow_remote_control) = requirements_toml.allow_remote_control { requirement_lines.push(requirement_line( "allow_remote_control", allow_remote_control.to_string(), requirements .allow_remote_control .as_ref() .map(|sourced| &sourced.source), )); } if requirements_toml.guardian_policy_config.is_some() { requirement_lines.push(requirement_line( "guardian_policy_config", "configured".to_string(), requirements.guardian_policy_config_source.as_ref(), )); } if let Some(feature_requirements) = requirements.feature_requirements.as_ref() { let value = join_or_empty( feature_requirements .value .entries .iter() .map(|(feature, enabled)| format!("{feature}={enabled}")) .collect::>(), ); requirement_lines.push(requirement_line( "features", value, Some(&feature_requirements.source), )); } if let Some(hooks) = requirements_toml.hooks.as_ref() { requirement_lines.push(requirement_line( "hooks", format_managed_hooks_requirements(hooks), requirements .managed_hooks .as_ref() .and_then(|managed_hooks| managed_hooks.source.as_ref()), )); } if let Some(servers) = requirements_toml.mcp_servers.as_ref() { let value = join_or_empty(servers.keys().cloned().collect::>()); requirement_lines.push(requirement_line( "mcp_servers", value, requirements .mcp_servers .as_ref() .map(|sourced| &sourced.source), )); } // TODO(gt): Expand this debug output with detailed skills and rules display. if requirements_toml.rules.is_some() { requirement_lines.push(requirement_line( "rules", "configured".to_string(), requirements.exec_policy_source(), )); } if let Some(residency) = requirements_toml.enforce_residency { requirement_lines.push(requirement_line( "enforce_residency", format_residency_requirement(residency), requirements.enforce_residency.source.as_ref(), )); } if let Some(network) = requirements.network.as_ref() { requirement_lines.push(requirement_line( "experimental_network", format_network_constraints(&network.value), Some(&network.source), )); } if let Some(filesystem) = requirements.filesystem.as_ref() { let deny_read = join_or_empty( filesystem .value .deny_read .iter() .map(|pattern| pattern.as_str().to_string()) .collect::>(), ); requirement_lines.push(requirement_line( "permissions.filesystem.deny_read", deny_read, Some(&filesystem.source), )); } if requirement_lines.is_empty() { lines.push(" ".dim().into()); } else { lines.extend(requirement_lines); } lines } fn render_non_file_layer_details(layer: &ConfigLayerEntry) -> Vec> { match &layer.name { ConfigLayerSource::SessionFlags => render_session_flag_details(&layer.config), ConfigLayerSource::Mdm { .. } | ConfigLayerSource::EnterpriseManaged { .. } | ConfigLayerSource::LegacyManagedConfigTomlFromMdm => render_non_file_layer_value(layer), ConfigLayerSource::System { .. } | ConfigLayerSource::User { .. } | ConfigLayerSource::Project { .. } | ConfigLayerSource::LegacyManagedConfigTomlFromFile { .. } => Vec::new(), } } fn render_session_flag_details(config: &TomlValue) -> Vec> { let mut pairs = Vec::new(); flatten_toml_key_values(config, /*prefix*/ None, &mut pairs); if pairs.is_empty() { return vec![" - ".dim().into()]; } pairs .into_iter() .map(|(key, value)| format!(" - {key} = {value}").into()) .collect() } fn format_managed_hooks_requirements(hooks: &ManagedHooksRequirementsToml) -> String { let mut parts = Vec::new(); if let Some(managed_dir) = hooks.managed_dir.as_ref() { parts.push(format!("managed_dir={}", managed_dir.display())); } if let Some(windows_managed_dir) = hooks.windows_managed_dir.as_ref() { parts.push(format!( "windows_managed_dir={}", windows_managed_dir.display() )); } parts.push(format!("handlers={}", hooks.handler_count())); join_or_empty(parts) } fn render_non_file_layer_value(layer: &ConfigLayerEntry) -> Vec> { let label = non_file_layer_value_label(&layer.name); let value = layer .raw_toml() .map(ToString::to_string) .unwrap_or_else(|| format_toml_value(&layer.config)); if value.is_empty() { return vec![format!(" {label}: ").dim().into()]; } if value.contains('\n') { let mut lines = vec![format!(" {label}:").into()]; lines.extend(value.lines().map(|line| format!(" {line}").into())); lines } else { vec![format!(" {label}: {value}").into()] } } fn non_file_layer_value_label(source: &ConfigLayerSource) -> &'static str { match source { ConfigLayerSource::Mdm { .. } | ConfigLayerSource::LegacyManagedConfigTomlFromMdm => { "MDM value" } ConfigLayerSource::EnterpriseManaged { .. } => "Enterprise-managed config value", ConfigLayerSource::SessionFlags | ConfigLayerSource::System { .. } | ConfigLayerSource::User { .. } | ConfigLayerSource::Project { .. } | ConfigLayerSource::LegacyManagedConfigTomlFromFile { .. } => "Layer value", } } fn flatten_toml_key_values( value: &TomlValue, prefix: Option<&str>, out: &mut Vec<(String, String)>, ) { match value { TomlValue::Table(table) => { let mut entries = table.iter().collect::>(); entries.sort_by_key(|(key, _)| key.as_str()); for (key, child) in entries { let next_prefix = if let Some(prefix) = prefix { format!("{prefix}.{key}") } else { key.to_string() }; flatten_toml_key_values(child, Some(&next_prefix), out); } } _ => { let key = prefix.unwrap_or("").to_string(); out.push((key, format_toml_value(value))); } } } fn format_toml_value(value: &TomlValue) -> String { value.to_string() } fn requirement_line( name: &str, value: String, source: Option<&RequirementSource>, ) -> Line<'static> { let source = source .map(ToString::to_string) .unwrap_or_else(|| "".to_string()); format!(" - {name}: {value} (source: {source})").into() } fn join_or_empty(values: Vec) -> String { if values.is_empty() { "".to_string() } else { values.join(", ") } } fn normalize_allowed_web_search_modes( modes: &[WebSearchModeRequirement], ) -> Vec { if modes.is_empty() { return vec![WebSearchModeRequirement::Disabled]; } let mut normalized = modes.to_vec(); if !normalized.contains(&WebSearchModeRequirement::Disabled) { normalized.push(WebSearchModeRequirement::Disabled); } normalized } fn format_sandbox_mode_requirement(mode: SandboxModeRequirement) -> String { match mode { SandboxModeRequirement::ReadOnly => "read-only".to_string(), SandboxModeRequirement::WorkspaceWrite => "workspace-write".to_string(), SandboxModeRequirement::DangerFullAccess => "danger-full-access".to_string(), SandboxModeRequirement::ExternalSandbox => "external-sandbox".to_string(), } } fn format_residency_requirement(requirement: ResidencyRequirement) -> String { match requirement { ResidencyRequirement::Us => "us".to_string(), } } fn format_network_constraints(network: &NetworkConstraints) -> String { let mut parts = Vec::new(); let NetworkConstraints { enabled, http_port, socks_port, allow_upstream_proxy, dangerously_allow_non_loopback_proxy, dangerously_allow_all_unix_sockets, domains, managed_allowed_domains_only, unix_sockets, allow_local_binding, } = network; if let Some(enabled) = enabled { parts.push(format!("enabled={enabled}")); } if let Some(http_port) = http_port { parts.push(format!("http_port={http_port}")); } if let Some(socks_port) = socks_port { parts.push(format!("socks_port={socks_port}")); } if let Some(allow_upstream_proxy) = allow_upstream_proxy { parts.push(format!("allow_upstream_proxy={allow_upstream_proxy}")); } if let Some(dangerously_allow_non_loopback_proxy) = dangerously_allow_non_loopback_proxy { parts.push(format!( "dangerously_allow_non_loopback_proxy={dangerously_allow_non_loopback_proxy}" )); } if let Some(dangerously_allow_all_unix_sockets) = dangerously_allow_all_unix_sockets { parts.push(format!( "dangerously_allow_all_unix_sockets={dangerously_allow_all_unix_sockets}" )); } if let Some(domains) = domains { parts.push(format!( "domains={}", format_network_permission_entries(&domains.entries, format_network_domain_permission) )); } if let Some(managed_allowed_domains_only) = managed_allowed_domains_only { parts.push(format!( "managed_allowed_domains_only={managed_allowed_domains_only}" )); } if let Some(unix_sockets) = unix_sockets { parts.push(format!( "unix_sockets={}", format_network_permission_entries( &unix_sockets.entries, format_network_unix_socket_permission, ) )); } if let Some(allow_local_binding) = allow_local_binding { parts.push(format!("allow_local_binding={allow_local_binding}")); } join_or_empty(parts) } fn format_network_permission_entries( entries: &std::collections::BTreeMap, format_value: impl Fn(T) -> &'static str, ) -> String { let parts = entries .iter() .map(|(key, value)| format!("{key}={}", format_value(*value))) .collect::>(); format!("{{{}}}", parts.join(", ")) } fn format_network_domain_permission(permission: NetworkDomainPermissionToml) -> &'static str { match permission { NetworkDomainPermissionToml::Allow => "allow", NetworkDomainPermissionToml::Deny => "deny", } } fn format_network_unix_socket_permission( permission: NetworkUnixSocketPermissionToml, ) -> &'static str { match permission { NetworkUnixSocketPermissionToml::Allow => "allow", NetworkUnixSocketPermissionToml::Deny => "deny", } } #[cfg(test)] mod tests { use super::render_debug_config_lines; use super::sandbox_mode_is_allowed_by_permissions; use super::session_all_proxy_url; use crate::legacy_core::config::Permissions; use codex_app_server_protocol::AskForApproval; use codex_config::ConfigLayerEntry; use codex_config::ConfigLayerSource; use codex_config::ConfigLayerStack; use codex_config::ConfigRequirements; use codex_config::ConfigRequirementsToml; use codex_config::Constrained; use codex_config::ConstrainedWithSource; use codex_config::ConstraintError; use codex_config::FeatureRequirementsToml; use codex_config::FilesystemConstraints; use codex_config::HookEventsToml; use codex_config::HookHandlerConfig; use codex_config::ManagedHooksRequirementsToml; use codex_config::MatcherGroup; use codex_config::McpServerIdentity; use codex_config::McpServerRequirement; use codex_config::NetworkConstraints; use codex_config::NetworkDomainPermissionToml; use codex_config::NetworkDomainPermissionsToml; use codex_config::NetworkUnixSocketPermissionToml; use codex_config::NetworkUnixSocketPermissionsToml; use codex_config::RequirementSource; use codex_config::ResidencyRequirement; use codex_config::SandboxModeRequirement; use codex_config::Sourced; use codex_config::WebSearchModeRequirement; use codex_config::sandbox_mode_requirement_for_permission_profile; use codex_protocol::config_types::ApprovalsReviewer; use codex_protocol::config_types::WebSearchMode; use codex_protocol::models::PermissionProfile; use codex_utils_absolute_path::AbsolutePathBuf; use ratatui::text::Line; use std::collections::BTreeMap; use toml::Value as TomlValue; fn empty_toml_table() -> TomlValue { TomlValue::Table(toml::map::Map::new()) } fn absolute_path(path: &str) -> AbsolutePathBuf { AbsolutePathBuf::from_absolute_path(path).expect("absolute path") } fn render_to_text(lines: &[Line<'static>]) -> String { lines .iter() .map(|line| { line.spans .iter() .map(|span| span.content.as_ref()) .collect::() }) .collect::>() .join("\n") } fn render_stack_to_text(stack: &ConfigLayerStack) -> String { render_stack_to_text_with_sandbox_mode_filter(stack, |_| true) } fn render_stack_to_text_with_sandbox_mode_filter( stack: &ConfigLayerStack, sandbox_mode_is_effectively_allowed: impl Fn(SandboxModeRequirement) -> bool, ) -> String { render_to_text(&render_debug_config_lines( stack, sandbox_mode_is_effectively_allowed, )) } #[test] fn debug_config_output_lists_all_layers_including_disabled() { let system_file = if cfg!(windows) { absolute_path("C:\\etc\\codex\\config.toml") } else { absolute_path("/etc/codex/config.toml") }; let project_folder = if cfg!(windows) { absolute_path("C:\\repo\\.codex") } else { absolute_path("/repo/.codex") }; let layers = vec![ ConfigLayerEntry::new( ConfigLayerSource::System { file: system_file }, empty_toml_table(), ), ConfigLayerEntry::new_disabled( ConfigLayerSource::Project { dot_codex_folder: project_folder, }, empty_toml_table(), "project is untrusted", ), ]; let stack = ConfigLayerStack::new( layers, ConfigRequirements::default(), ConfigRequirementsToml::default(), ) .expect("config layer stack"); let rendered = render_stack_to_text(&stack); assert!(rendered.contains("(enabled)")); assert!(rendered.contains("(disabled)")); assert!(rendered.contains("reason: project is untrusted")); assert!(rendered.contains("Requirements:")); assert!(rendered.contains(" ")); } #[test] fn debug_config_output_lists_requirement_sources() { let requirements_file = if cfg!(windows) { absolute_path("C:\\ProgramData\\OpenAI\\Codex\\requirements.toml") } else { absolute_path("/etc/codex/requirements.toml") }; let denied_path = if cfg!(windows) { absolute_path("C:\\Users\\alice\\.gitconfig") } else { absolute_path("/home/alice/.gitconfig") }; let requirements = ConfigRequirements { approval_policy: ConstrainedWithSource::new( Constrained::allow_any(AskForApproval::OnRequest.to_core()), Some(RequirementSource::LegacyManagedConfigTomlFromMdm), ), approvals_reviewer: ConstrainedWithSource::new( Constrained::allow_any(ApprovalsReviewer::AutoReview), Some(RequirementSource::LegacyManagedConfigTomlFromMdm), ), permission_profile: ConstrainedWithSource::new( Constrained::allow_any(PermissionProfile::read_only()), Some(RequirementSource::SystemRequirementsToml { file: requirements_file.clone(), }), ), mcp_servers: Some(Sourced::new( BTreeMap::from([( "docs".to_string(), McpServerRequirement::Identity { identity: McpServerIdentity::Command { command: "codex-mcp".to_string(), }, }, )]), RequirementSource::LegacyManagedConfigTomlFromMdm, )), enforce_residency: ConstrainedWithSource::new( Constrained::allow_any(Some(ResidencyRequirement::Us)), Some(RequirementSource::LegacyManagedConfigTomlFromMdm), ), web_search_mode: ConstrainedWithSource::new( Constrained::allow_any(WebSearchMode::Cached), Some(RequirementSource::LegacyManagedConfigTomlFromMdm), ), allow_managed_hooks_only: Some(Sourced::new( /*value*/ true, RequirementSource::LegacyManagedConfigTomlFromMdm, )), allow_appshots: Some(Sourced::new( /*value*/ false, RequirementSource::LegacyManagedConfigTomlFromMdm, )), allow_remote_control: Some(Sourced::new( /*value*/ false, RequirementSource::LegacyManagedConfigTomlFromMdm, )), feature_requirements: Some(Sourced::new( FeatureRequirementsToml { entries: BTreeMap::from([("guardian_approval".to_string(), true)]), }, RequirementSource::LegacyManagedConfigTomlFromMdm, )), network: Some(Sourced::new( NetworkConstraints { enabled: Some(true), domains: Some(NetworkDomainPermissionsToml { entries: BTreeMap::from([( "example.com".to_string(), NetworkDomainPermissionToml::Allow, )]), }), ..Default::default() }, RequirementSource::LegacyManagedConfigTomlFromMdm, )), filesystem: Some(Sourced::new( FilesystemConstraints { deny_read: vec![denied_path.clone().into()], }, RequirementSource::SystemRequirementsToml { file: requirements_file.clone(), }, )), guardian_policy_config_source: Some(RequirementSource::LegacyManagedConfigTomlFromMdm), ..ConfigRequirements::default() }; let requirements_toml = ConfigRequirementsToml { allowed_approval_policies: Some(vec![AskForApproval::OnRequest.to_core()]), allowed_approvals_reviewers: Some(vec![ApprovalsReviewer::AutoReview]), allowed_sandbox_modes: Some(vec![SandboxModeRequirement::ReadOnly]), allowed_permission_profiles: None, default_permissions: None, remote_sandbox_config: None, allowed_web_search_modes: Some(vec![WebSearchModeRequirement::Cached]), allow_managed_hooks_only: Some(true), allow_appshots: Some(false), allow_remote_control: Some(false), computer_use: None, windows: None, guardian_policy_config: Some("Use the managed guardian policy.".to_string()), feature_requirements: Some(FeatureRequirementsToml { entries: BTreeMap::from([("guardian_approval".to_string(), true)]), }), hooks: None, mcp_servers: Some(BTreeMap::from([( "docs".to_string(), McpServerRequirement::Identity { identity: McpServerIdentity::Command { command: "codex-mcp".to_string(), }, }, )])), plugins: None, marketplaces: None, apps: None, rules: None, enforce_residency: Some(ResidencyRequirement::Us), network: None, permissions: None, models: None, }; let user_file = if cfg!(windows) { absolute_path("C:\\users\\alice\\.codex\\config.toml") } else { absolute_path("/home/alice/.codex/config.toml") }; let stack = ConfigLayerStack::new( vec![ConfigLayerEntry::new( ConfigLayerSource::User { file: user_file, profile: None, }, empty_toml_table(), )], requirements, requirements_toml, ) .expect("config layer stack"); let rendered = render_stack_to_text(&stack); #[cfg(not(windows))] insta::assert_snapshot!("debug_config_requirement_sources", rendered.as_str()); let requirements_source = (RequirementSource::LegacyManagedConfigTomlFromMdm).to_string(); assert!(rendered.contains(&format!( "allowed_approval_policies: on-request (source: {requirements_source})" ))); assert!(rendered.contains( "allowed_approvals_reviewers: auto_review (source: MDM managed_config.toml (legacy))" )); assert!( rendered.contains( format!( "allowed_sandbox_modes: read-only (source: {})", requirements_file.as_path().display() ) .as_str(), ) ); assert!(rendered.contains(&format!( "allowed_web_search_modes: cached, disabled (source: {requirements_source})" ))); assert!(rendered.contains(&format!( "allow_managed_hooks_only: true (source: {requirements_source})" ))); assert!(rendered.contains(&format!( "allow_appshots: false (source: {requirements_source})" ))); assert!(rendered.contains(&format!( "allow_remote_control: false (source: {requirements_source})" ))); assert!(rendered.contains(&format!( "guardian_policy_config: configured (source: {requirements_source})" ))); assert!(rendered.contains(&format!( "features: guardian_approval=true (source: {requirements_source})" ))); assert!(rendered.contains("mcp_servers: docs (source: MDM managed_config.toml (legacy))")); assert!(rendered.contains(&format!( "enforce_residency: us (source: {requirements_source})" ))); assert!(rendered.contains(&format!( "experimental_network: enabled=true, domains={{example.com=allow}} (source: {requirements_source})" ))); assert!( rendered.contains( format!( "permissions.filesystem.deny_read: {}", denied_path.as_path().display() ) .as_str() ) ); assert!(!rendered.contains(" - rules:")); } #[test] fn debug_config_output_filters_sandbox_modes_blocked_by_deny_read_requirements() { let requirements_file = if cfg!(windows) { absolute_path("C:\\ProgramData\\OpenAI\\Codex\\requirements.toml") } else { absolute_path("/etc/codex/requirements.toml") }; let denied_path = if cfg!(windows) { absolute_path("C:\\Users\\alice\\.gitconfig") } else { absolute_path("/home/alice/.gitconfig") }; let requirements = ConfigRequirements { permission_profile: ConstrainedWithSource::new( Constrained::allow_any(PermissionProfile::read_only()), Some(RequirementSource::SystemRequirementsToml { file: requirements_file.clone(), }), ), filesystem: Some(Sourced::new( FilesystemConstraints { deny_read: vec![denied_path.into()], }, RequirementSource::SystemRequirementsToml { file: requirements_file.clone(), }, )), ..ConfigRequirements::default() }; let requirements_toml = ConfigRequirementsToml { allowed_sandbox_modes: Some(vec![ SandboxModeRequirement::ReadOnly, SandboxModeRequirement::WorkspaceWrite, SandboxModeRequirement::DangerFullAccess, SandboxModeRequirement::ExternalSandbox, ]), ..ConfigRequirementsToml::default() }; let stack = ConfigLayerStack::new(Vec::new(), requirements, requirements_toml) .expect("config layer stack"); let constrained_permission_profile = Constrained::new(PermissionProfile::read_only(), |candidate| { let mode = sandbox_mode_requirement_for_permission_profile(candidate); match mode { SandboxModeRequirement::ReadOnly | SandboxModeRequirement::WorkspaceWrite => { Ok(()) } SandboxModeRequirement::DangerFullAccess | SandboxModeRequirement::ExternalSandbox => { Err(ConstraintError::InvalidValue { field_name: "sandbox_mode", candidate: format!("{mode:?}"), allowed: "[read-only, workspace-write]".to_string(), requirement_source: RequirementSource::Unknown, }) } } }) .expect("constrained permission profile"); let permissions = Permissions::from_approval_and_profile( Constrained::allow_any(AskForApproval::OnRequest.to_core()), constrained_permission_profile, ) .expect("permissions"); let rendered = render_stack_to_text_with_sandbox_mode_filter(&stack, |mode| { sandbox_mode_is_allowed_by_permissions(&permissions, mode) }); #[cfg(not(windows))] insta::assert_snapshot!( "debug_config_effective_sandbox_modes_with_deny_read", rendered.as_str() ); assert!( rendered.contains( format!( "allowed_sandbox_modes: read-only, workspace-write (source: {})", requirements_file.as_path().display() ) .as_str() ) ); assert!(!rendered.contains("danger-full-access")); assert!(!rendered.contains("external-sandbox")); } #[test] fn debug_config_output_lists_approvals_reviewer_as_requirement() { let requirements = ConfigRequirements { approvals_reviewer: ConstrainedWithSource::new( Constrained::allow_any(ApprovalsReviewer::AutoReview), Some(RequirementSource::LegacyManagedConfigTomlFromMdm), ), ..ConfigRequirements::default() }; let requirements_toml = ConfigRequirementsToml { allowed_approvals_reviewers: Some(vec![ApprovalsReviewer::AutoReview]), ..ConfigRequirementsToml::default() }; let stack = ConfigLayerStack::new(Vec::new(), requirements, requirements_toml) .expect("config layer stack"); let rendered = render_stack_to_text(&stack); assert!(rendered.contains( "allowed_approvals_reviewers: auto_review (source: MDM managed_config.toml (legacy))" )); assert!(!rendered.contains("Requirements:\n ")); } #[test] fn debug_config_output_formats_unix_socket_permissions() { let requirements = ConfigRequirements { network: Some(Sourced::new( NetworkConstraints { unix_sockets: Some(NetworkUnixSocketPermissionsToml { entries: BTreeMap::from([ ( "/tmp/codex.sock".to_string(), NetworkUnixSocketPermissionToml::Allow, ), ( "/tmp/blocked.sock".to_string(), NetworkUnixSocketPermissionToml::Deny, ), ]), }), ..Default::default() }, RequirementSource::LegacyManagedConfigTomlFromMdm, )), ..ConfigRequirements::default() }; let stack = ConfigLayerStack::new(Vec::new(), requirements, ConfigRequirementsToml::default()) .expect("config layer stack"); let rendered = render_stack_to_text(&stack); let requirements_source = (RequirementSource::LegacyManagedConfigTomlFromMdm).to_string(); assert!(rendered.contains(&format!( "experimental_network: unix_sockets={{/tmp/blocked.sock=deny, /tmp/codex.sock=allow}} (source: {requirements_source})" ))); } #[test] fn debug_config_output_lists_session_flag_key_value_pairs() { let session_flags = toml::from_str::( r#" model = "gpt-5" [sandbox_workspace_write] network_access = true writable_roots = ["/tmp"] "#, ) .expect("session flags"); let stack = ConfigLayerStack::new( vec![ConfigLayerEntry::new( ConfigLayerSource::SessionFlags, session_flags, )], ConfigRequirements::default(), ConfigRequirementsToml::default(), ) .expect("config layer stack"); let rendered = render_stack_to_text(&stack); assert!(rendered.contains("session-flags (enabled)")); assert!(rendered.contains(" - model = \"gpt-5\"")); assert!(rendered.contains(" - sandbox_workspace_write.network_access = true")); assert!(rendered.contains("sandbox_workspace_write.writable_roots")); assert!(rendered.contains("/tmp")); } #[test] fn debug_config_output_shows_legacy_mdm_layer_value() { let raw_mdm_toml = r#" # managed by MDM model = "managed_model" approval_policy = "never" "#; let mdm_value = toml::from_str::(raw_mdm_toml).expect("MDM value"); let mdm_base_dir = if cfg!(windows) { absolute_path("C:\\codex") } else { absolute_path("/var/lib/codex") }; let stack = ConfigLayerStack::new( vec![ConfigLayerEntry::new_with_raw_toml( ConfigLayerSource::LegacyManagedConfigTomlFromMdm, mdm_value, raw_mdm_toml.to_string(), mdm_base_dir, )], ConfigRequirements::default(), ConfigRequirementsToml::default(), ) .expect("config layer stack"); let rendered = render_stack_to_text(&stack); assert!(rendered.contains("legacy managed_config.toml (MDM) (enabled)")); assert!(rendered.contains("MDM value:")); assert!(rendered.contains("# managed by MDM")); assert!(rendered.contains("model = \"managed_model\"")); assert!(rendered.contains("approval_policy = \"never\"")); } #[test] fn debug_config_output_shows_enterprise_managed_layer_value() { let raw_cloud_toml = r#" # managed by cloud model = "enterprise_model" approval_policy = "never" "#; let cloud_value = toml::from_str::(raw_cloud_toml).expect("cloud value"); let cloud_base_dir = if cfg!(windows) { absolute_path("C:\\codex") } else { absolute_path("/var/lib/codex") }; let stack = ConfigLayerStack::new( vec![ConfigLayerEntry::new_with_raw_toml( ConfigLayerSource::EnterpriseManaged { id: "cfg_123".to_string(), name: "Base policy".to_string(), }, cloud_value, raw_cloud_toml.to_string(), cloud_base_dir, )], ConfigRequirements::default(), ConfigRequirementsToml::default(), ) .expect("config layer stack"); let rendered = render_stack_to_text(&stack); assert!(rendered.contains("enterprise-managed (Base policy, cfg_123) (enabled)")); assert!(rendered.contains("Enterprise-managed config value:")); assert!(!rendered.contains("MDM value:")); assert!(rendered.contains("# managed by cloud")); assert!(rendered.contains("model = \"enterprise_model\"")); assert!(rendered.contains("approval_policy = \"never\"")); } #[test] fn debug_config_output_normalizes_empty_web_search_mode_list() { let requirements = ConfigRequirements { web_search_mode: ConstrainedWithSource::new( Constrained::allow_any(WebSearchMode::Disabled), Some(RequirementSource::LegacyManagedConfigTomlFromMdm), ), ..ConfigRequirements::default() }; let requirements_toml = ConfigRequirementsToml { allowed_approval_policies: None, allowed_approvals_reviewers: None, allowed_sandbox_modes: None, allowed_permission_profiles: None, default_permissions: None, remote_sandbox_config: None, allowed_web_search_modes: Some(Vec::new()), allow_managed_hooks_only: None, allow_appshots: None, allow_remote_control: None, computer_use: None, windows: None, guardian_policy_config: None, feature_requirements: None, hooks: None, mcp_servers: None, plugins: None, marketplaces: None, apps: None, rules: None, enforce_residency: None, network: None, permissions: None, models: None, }; let stack = ConfigLayerStack::new(Vec::new(), requirements, requirements_toml) .expect("config layer stack"); let rendered = render_stack_to_text(&stack); let requirements_source = (RequirementSource::LegacyManagedConfigTomlFromMdm).to_string(); assert!(rendered.contains(&format!( "allowed_web_search_modes: disabled (source: {requirements_source})" ))); } #[test] fn debug_config_output_lists_managed_hooks_requirement() { let requirements = ConfigRequirements { managed_hooks: Some(ConstrainedWithSource::new( Constrained::allow_any(ManagedHooksRequirementsToml { managed_dir: Some(if cfg!(windows) { std::path::PathBuf::from(r"C:\enterprise\hooks") } else { std::path::PathBuf::from("/enterprise/hooks") }), windows_managed_dir: Some(std::path::PathBuf::from(r"C:\enterprise\hooks")), hooks: HookEventsToml { pre_tool_use: vec![MatcherGroup { matcher: Some("^Bash$".to_string()), hooks: vec![HookHandlerConfig::Command { command: "python3 /enterprise/hooks/pre.py".to_string(), command_windows: None, timeout_sec: Some(10), r#async: false, status_message: Some("checking".to_string()), }], }], ..Default::default() }, }), Some(RequirementSource::LegacyManagedConfigTomlFromMdm), )), ..ConfigRequirements::default() }; let requirements_toml = ConfigRequirementsToml { hooks: requirements .managed_hooks .as_ref() .map(|hooks| hooks.get().clone()), ..ConfigRequirementsToml::default() }; let stack = ConfigLayerStack::new(Vec::new(), requirements, requirements_toml) .expect("config layer stack"); let rendered = render_stack_to_text(&stack); let requirements_source = (RequirementSource::LegacyManagedConfigTomlFromMdm).to_string(); assert!(rendered.contains("hooks:")); assert!(rendered.contains("handlers=1")); assert!(rendered.contains(&format!("(source: {requirements_source})"))); } #[test] fn session_all_proxy_url_uses_socks_when_enabled() { assert_eq!( session_all_proxy_url( "127.0.0.1:3128", "127.0.0.1:8081", /*socks_enabled*/ true ), "socks5h://127.0.0.1:8081".to_string() ); } #[test] fn session_all_proxy_url_uses_http_when_socks_disabled() { assert_eq!( session_all_proxy_url( "127.0.0.1:3128", "127.0.0.1:8081", /*socks_enabled*/ false ), "http://127.0.0.1:3128".to_string() ); } }