mirror of
https://github.com/openai/codex.git
synced 2026-11-03 08:22:30 +00:00
Add remote_sandbox_config to our config requirements (#18763)
## Why Customers need finer-grained control over allowed sandbox modes based on the host Codex is running on. For example, they may want stricter sandbox limits on devboxes while keeping a different default elsewhere. Our current cloud requirements can target user/account groups, but they cannot vary sandbox requirements by host. That makes remote development environments awkward because the same top-level `allowed_sandbox_modes` has to apply everywhere. ## What Adds a new `remote_sandbox_config` section to `requirements.toml`: ```toml allowed_sandbox_modes = ["read-only"] [[remote_sandbox_config]] hostname_patterns = ["*.org"] allowed_sandbox_modes = ["read-only", "workspace-write"] [[remote_sandbox_config]] hostname_patterns = ["*.sh", "runner-*.ci"] allowed_sandbox_modes = ["read-only", "danger-full-access"] ``` During requirements resolution, Codex resolves the local host name once, preferring the machine FQDN when available and falling back to the cleaned kernel hostname. This host classification is best effort rather than authenticated device proof. Each requirements source applies its first matching `remote_sandbox_config` entry before it is merged with other sources. The shared merge helper keeps that `apply_remote_sandbox_config` step paired with requirements merging so new requirements sources do not have to remember the extra call. That preserves source precedence: a lower-precedence requirements file with a matching `remote_sandbox_config` cannot override a higher-precedence source that already set `allowed_sandbox_modes`. This also wires the hostname-aware resolution through app-server, CLI/TUI config loading, config API reads, and config layer metadata so they all evaluate remote sandbox requirements consistently. ## Verification - `cargo test -p codex-config remote_sandbox_config` - `cargo test -p codex-config host_name` - `cargo test -p codex-core load_config_layers_applies_matching_remote_sandbox_config` - `cargo test -p codex-core system_remote_sandbox_config_keeps_cloud_sandbox_modes` - `cargo test -p codex-config` - `cargo test -p codex-core` unit tests passed; `tests/all.rs` integration matrix was intentionally stopped after the relevant focused tests passed - `just fix -p codex-config` - `just fix -p codex-core` - `cargo check -p codex-app-server`
This commit is contained in:
1 parent
86535c9901
commit
ab26554a3a
21 files changed
+598
-27
No files matched your search
@@ -2213,6 +2213,7 @@ async fn managed_config_overrides_oauth_store_mode() -> anyhow::Result<()> {
|
||||
overrides,
|
||||
CloudRequirementsLoader::default(),
|
||||
&codex_config::NoopThreadConfigLoader,
|
||||
/*host_name*/ None,
|
||||
)
|
||||
.await?;
|
||||
let cfg =
|
||||
@@ -2348,6 +2349,7 @@ async fn managed_config_wins_over_cli_overrides() -> anyhow::Result<()> {
|
||||
overrides,
|
||||
CloudRequirementsLoader::default(),
|
||||
&codex_config::NoopThreadConfigLoader,
|
||||
/*host_name*/ None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -5484,6 +5486,7 @@ async fn test_requirements_web_search_mode_allowlist_does_not_warn_when_unset()
|
||||
allowed_approval_policies: None,
|
||||
allowed_approvals_reviewers: None,
|
||||
allowed_sandbox_modes: None,
|
||||
remote_sandbox_config: None,
|
||||
allowed_web_search_modes: Some(vec![
|
||||
crate::config_loader::WebSearchModeRequirement::Cached,
|
||||
]),
|
||||
@@ -6160,6 +6163,7 @@ async fn explicit_sandbox_mode_falls_back_when_disallowed_by_requirements() -> s
|
||||
allowed_approval_policies: None,
|
||||
allowed_approvals_reviewers: None,
|
||||
allowed_sandbox_modes: Some(vec![crate::config_loader::SandboxModeRequirement::ReadOnly]),
|
||||
remote_sandbox_config: None,
|
||||
allowed_web_search_modes: None,
|
||||
feature_requirements: None,
|
||||
mcp_servers: None,
|
||||
|
||||
@@ -649,7 +649,7 @@ impl AuthManagerConfig for Config {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
#[derive(Clone)]
|
||||
pub struct ConfigBuilder {
|
||||
codex_home: Option<PathBuf>,
|
||||
cli_overrides: Option<Vec<(String, TomlValue)>>,
|
||||
@@ -658,6 +658,22 @@ pub struct ConfigBuilder {
|
||||
cloud_requirements: CloudRequirementsLoader,
|
||||
thread_config_loader: Option<Arc<dyn ThreadConfigLoader>>,
|
||||
fallback_cwd: Option<PathBuf>,
|
||||
host_name: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for ConfigBuilder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
codex_home: None,
|
||||
cli_overrides: None,
|
||||
harness_overrides: None,
|
||||
loader_overrides: None,
|
||||
cloud_requirements: CloudRequirementsLoader::default(),
|
||||
thread_config_loader: None,
|
||||
fallback_cwd: None,
|
||||
host_name: codex_config::host_name(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigBuilder {
|
||||
@@ -699,6 +715,11 @@ impl ConfigBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn host_name(mut self, host_name: Option<String>) -> Self {
|
||||
self.host_name = host_name;
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn build(self) -> std::io::Result<Config> {
|
||||
let Self {
|
||||
codex_home,
|
||||
@@ -708,6 +729,7 @@ impl ConfigBuilder {
|
||||
cloud_requirements,
|
||||
thread_config_loader,
|
||||
fallback_cwd,
|
||||
host_name,
|
||||
} = self;
|
||||
let codex_home = match codex_home {
|
||||
Some(codex_home) => AbsolutePathBuf::from_absolute_path(codex_home)?,
|
||||
@@ -732,6 +754,7 @@ impl ConfigBuilder {
|
||||
thread_config_loader
|
||||
.as_deref()
|
||||
.unwrap_or(&codex_config::NoopThreadConfigLoader),
|
||||
host_name.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
let merged_toml = config_layer_stack.effective_config();
|
||||
@@ -911,6 +934,7 @@ pub async fn load_config_as_toml_with_cli_and_loader_overrides(
|
||||
loader_overrides,
|
||||
CloudRequirementsLoader::default(),
|
||||
&codex_config::NoopThreadConfigLoader,
|
||||
/*host_name*/ None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1083,6 +1107,7 @@ pub async fn load_global_mcp_servers(
|
||||
LoaderOverrides::default(),
|
||||
CloudRequirementsLoader::default(),
|
||||
&codex_config::NoopThreadConfigLoader,
|
||||
/*host_name*/ None,
|
||||
)
|
||||
.await?;
|
||||
let merged_toml = config_layer_stack.effective_config();
|
||||
|
||||
@@ -115,6 +115,7 @@ pub struct ConfigService {
|
||||
cli_overrides: Vec<(String, TomlValue)>,
|
||||
loader_overrides: LoaderOverrides,
|
||||
cloud_requirements: CloudRequirementsLoader,
|
||||
host_name: Option<String>,
|
||||
}
|
||||
|
||||
impl ConfigService {
|
||||
@@ -123,12 +124,14 @@ impl ConfigService {
|
||||
cli_overrides: Vec<(String, TomlValue)>,
|
||||
loader_overrides: LoaderOverrides,
|
||||
cloud_requirements: CloudRequirementsLoader,
|
||||
host_name: Option<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
codex_home,
|
||||
cli_overrides,
|
||||
loader_overrides,
|
||||
cloud_requirements,
|
||||
host_name,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +141,7 @@ impl ConfigService {
|
||||
cli_overrides: Vec::new(),
|
||||
loader_overrides: LoaderOverrides::default(),
|
||||
cloud_requirements: CloudRequirementsLoader::default(),
|
||||
host_name: codex_config::host_name(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +152,7 @@ impl ConfigService {
|
||||
Vec::new(),
|
||||
LoaderOverrides::without_managed_config_for_tests(),
|
||||
CloudRequirementsLoader::default(),
|
||||
/*host_name*/ None,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -166,6 +171,7 @@ impl ConfigService {
|
||||
.loader_overrides(self.loader_overrides.clone())
|
||||
.fallback_cwd(Some(cwd.to_path_buf()))
|
||||
.cloud_requirements(self.cloud_requirements.clone())
|
||||
.host_name(self.host_name.clone())
|
||||
.build()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
@@ -432,6 +438,7 @@ impl ConfigService {
|
||||
self.loader_overrides.clone(),
|
||||
self.cloud_requirements.clone(),
|
||||
&codex_config::NoopThreadConfigLoader,
|
||||
self.host_name.as_deref(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -223,6 +223,7 @@ async fn read_includes_origins_and_layers() {
|
||||
vec![],
|
||||
LoaderOverrides::with_managed_config_path_for_tests(managed_path.clone()),
|
||||
CloudRequirementsLoader::default(),
|
||||
/*host_name*/ None,
|
||||
);
|
||||
|
||||
let response = service
|
||||
@@ -301,6 +302,7 @@ writable_roots = ["~/code"]
|
||||
vec![],
|
||||
loader_overrides,
|
||||
CloudRequirementsLoader::default(),
|
||||
/*host_name*/ None,
|
||||
);
|
||||
|
||||
let response = service
|
||||
@@ -341,6 +343,7 @@ async fn write_value_reports_override() {
|
||||
vec![],
|
||||
LoaderOverrides::with_managed_config_path_for_tests(managed_path.clone()),
|
||||
CloudRequirementsLoader::default(),
|
||||
/*host_name*/ None,
|
||||
);
|
||||
|
||||
let result = service
|
||||
@@ -440,6 +443,7 @@ async fn invalid_user_value_rejected_even_if_overridden_by_managed() {
|
||||
vec![],
|
||||
LoaderOverrides::with_managed_config_path_for_tests(managed_path.clone()),
|
||||
CloudRequirementsLoader::default(),
|
||||
/*host_name*/ None,
|
||||
);
|
||||
|
||||
let error = service
|
||||
@@ -507,6 +511,7 @@ async fn write_value_rejects_feature_requirement_conflict() {
|
||||
..Default::default()
|
||||
}))
|
||||
}),
|
||||
/*host_name*/ None,
|
||||
);
|
||||
|
||||
let error = service
|
||||
@@ -553,6 +558,7 @@ async fn write_value_rejects_profile_feature_requirement_conflict() {
|
||||
..Default::default()
|
||||
}))
|
||||
}),
|
||||
/*host_name*/ None,
|
||||
);
|
||||
|
||||
let error = service
|
||||
@@ -603,6 +609,7 @@ async fn read_reports_managed_overrides_user_and_session_flags() {
|
||||
cli_overrides,
|
||||
LoaderOverrides::with_managed_config_path_for_tests(managed_path.clone()),
|
||||
CloudRequirementsLoader::default(),
|
||||
/*host_name*/ None,
|
||||
);
|
||||
|
||||
let response = service
|
||||
@@ -656,6 +663,7 @@ async fn write_value_reports_managed_override() {
|
||||
vec![],
|
||||
LoaderOverrides::with_managed_config_path_for_tests(managed_path.clone()),
|
||||
CloudRequirementsLoader::default(),
|
||||
/*host_name*/ None,
|
||||
);
|
||||
|
||||
let result = service
|
||||
|
||||
Reference in new issue
Block a user