mirror of
https://github.com/openai/codex.git
synced 2026-04-27 16:15:09 +00:00
feat: add support for allowed_web_search_modes in requirements.toml (#10964)
This PR makes it possible to disable live web search via an enterprise config even if the user is running in `--yolo` mode (though cached web search will still be available). To do this, create `/etc/codex/requirements.toml` as follows: ```toml # "live" is not allowed; "disabled" is allowed even though not listed explicitly. allowed_web_search_modes = ["cached"] ``` Or set `requirements_toml_base64` MDM as explained on https://developers.openai.com/codex/security/#locations. ### Why - Enforce admin/MDM/`requirements.toml` constraints on web-search behavior, independent of user config and per-turn sandbox defaults. - Ensure per-turn config resolution and review-mode overrides never crash when constraints are present. ### What - Add `allowed_web_search_modes` to requirements parsing and surface it in app-server v2 `ConfigRequirements` (`allowedWebSearchModes`), with fixtures updated. - Define a requirements allowlist type (`WebSearchModeRequirement`) and normalize semantics: - `disabled` is always implicitly allowed (even if not listed). - An empty list is treated as `["disabled"]`. - Make `Config.web_search_mode` a `Constrained<WebSearchMode>` and apply requirements via `ConstrainedWithSource<WebSearchMode>`. - Update per-turn resolution (`resolve_web_search_mode_for_turn`) to: - Prefer `Live → Cached → Disabled` when `SandboxPolicy::DangerFullAccess` is active (subject to requirements), unless the user preference is explicitly `Disabled`. - Otherwise, honor the user’s preferred mode, falling back to an allowed mode when necessary. - Update TUI `/debug-config` and app-server mapping to display normalized `allowed_web_search_modes` (including implicit `disabled`). - Fix web-search integration tests to assert cached behavior under `SandboxPolicy::ReadOnly` (since `DangerFullAccess` legitimately prefers `live` when allowed).
This commit is contained in:
@@ -6,6 +6,7 @@ use codex_core::config_loader::ConfigLayerStackOrdering;
|
||||
use codex_core::config_loader::RequirementSource;
|
||||
use codex_core::config_loader::ResidencyRequirement;
|
||||
use codex_core::config_loader::SandboxModeRequirement;
|
||||
use codex_core::config_loader::WebSearchModeRequirement;
|
||||
use ratatui::style::Stylize;
|
||||
use ratatui::text::Line;
|
||||
|
||||
@@ -70,6 +71,21 @@ fn render_debug_config_lines(stack: &ConfigLayerStack) -> Vec<Line<'static>> {
|
||||
));
|
||||
}
|
||||
|
||||
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::<Vec<_>>(),
|
||||
);
|
||||
requirement_lines.push(requirement_line(
|
||||
"allowed_web_search_modes",
|
||||
value,
|
||||
requirements.web_search_mode.source.as_ref(),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(servers) = requirements_toml.mcp_servers.as_ref() {
|
||||
let value = join_or_empty(servers.keys().cloned().collect::<Vec<_>>());
|
||||
requirement_lines.push(requirement_line(
|
||||
@@ -127,6 +143,20 @@ fn join_or_empty(values: Vec<String>) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_allowed_web_search_modes(
|
||||
modes: &[WebSearchModeRequirement],
|
||||
) -> Vec<WebSearchModeRequirement> {
|
||||
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_config_layer_source(source: &ConfigLayerSource) -> String {
|
||||
match source {
|
||||
ConfigLayerSource::Mdm { domain, key } => {
|
||||
@@ -185,8 +215,10 @@ mod tests {
|
||||
use codex_core::config_loader::ResidencyRequirement;
|
||||
use codex_core::config_loader::SandboxModeRequirement;
|
||||
use codex_core::config_loader::Sourced;
|
||||
use codex_core::config_loader::WebSearchModeRequirement;
|
||||
use codex_core::protocol::AskForApproval;
|
||||
use codex_core::protocol::SandboxPolicy;
|
||||
use codex_protocol::config_types::WebSearchMode;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use ratatui::text::Line;
|
||||
use std::collections::BTreeMap;
|
||||
@@ -287,10 +319,15 @@ mod tests {
|
||||
Constrained::allow_any(Some(ResidencyRequirement::Us)),
|
||||
Some(RequirementSource::CloudRequirements),
|
||||
);
|
||||
requirements.web_search_mode = ConstrainedWithSource::new(
|
||||
Constrained::allow_any(WebSearchMode::Cached),
|
||||
Some(RequirementSource::CloudRequirements),
|
||||
);
|
||||
|
||||
let requirements_toml = ConfigRequirementsToml {
|
||||
allowed_approval_policies: Some(vec![AskForApproval::OnRequest]),
|
||||
allowed_sandbox_modes: Some(vec![SandboxModeRequirement::ReadOnly]),
|
||||
allowed_web_search_modes: Some(vec![WebSearchModeRequirement::Cached]),
|
||||
mcp_servers: Some(BTreeMap::from([(
|
||||
"docs".to_string(),
|
||||
McpServerRequirement {
|
||||
@@ -331,8 +368,39 @@ mod tests {
|
||||
.as_str(),
|
||||
)
|
||||
);
|
||||
assert!(
|
||||
rendered.contains(
|
||||
"allowed_web_search_modes: cached, disabled (source: cloud requirements)"
|
||||
)
|
||||
);
|
||||
assert!(rendered.contains("mcp_servers: docs (source: MDM managed_config.toml (legacy))"));
|
||||
assert!(rendered.contains("enforce_residency: us (source: cloud requirements)"));
|
||||
assert!(!rendered.contains(" - rules:"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug_config_output_normalizes_empty_web_search_mode_list() {
|
||||
let mut requirements = ConfigRequirements::default();
|
||||
requirements.web_search_mode = ConstrainedWithSource::new(
|
||||
Constrained::allow_any(WebSearchMode::Disabled),
|
||||
Some(RequirementSource::CloudRequirements),
|
||||
);
|
||||
|
||||
let requirements_toml = ConfigRequirementsToml {
|
||||
allowed_approval_policies: None,
|
||||
allowed_sandbox_modes: None,
|
||||
allowed_web_search_modes: Some(Vec::new()),
|
||||
mcp_servers: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
};
|
||||
|
||||
let stack = ConfigLayerStack::new(Vec::new(), requirements, requirements_toml)
|
||||
.expect("config layer stack");
|
||||
|
||||
let rendered = render_to_text(&render_debug_config_lines(&stack));
|
||||
assert!(
|
||||
rendered.contains("allowed_web_search_modes: disabled (source: cloud requirements)")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user