Load cloud config for sandbox and remote exec-server commands (#57270)

## Why

Sandbox invocations without an explicit permissions profile and remote `exec-server` connections using ChatGPT auth need access to cloud-defined model providers and managed requirements. Cloud bootstrap requests must also respect local application network policy.

## What changed

- Bootstrap sandbox cloud config whenever managed config is included, even without an explicit profile. Skip bootstrap when `--sandbox-state-json` supplies the sandbox state.
- Load cloud config for remote executors beyond workload identity, while skipping forwarders and executors using stdin credentials, AWS SigV4, or agent identity auth. Honor `CODEX_API_KEY` selection so API key connections skip ChatGPT cloud config.
- Apply local application network policy during cloud bootstrap and activate effective application network policy before exec-server clients are created.

## Testing

Add coverage for cloud-defined providers in sandbox and remote exec-server invocations, API key bypass, local policy blocking bundle requests, cloud policy blocking remote registration, and bootstrap exclusions.

GitOrigin-RevId: e8bd6210751e96161f575ade309ebf41bf10f8b5
This commit is contained in:
Abhinav
2026-10-26 21:31:28 +00:00
committed by copyberry
parent 0818b91867
commit 51e3f94b2b
7 changed files with 273 additions and 79 deletions
@@ -1,3 +1,4 @@
use codex_app_server::in_process::EmbeddedNetworkPolicy;
use codex_cloud_config::chatgpt_config_bundle_loader_for_storage;
use codex_config::CloudConfigBundleLoader;
use codex_config::ConfigLoadOptions;
@@ -15,7 +16,7 @@ pub(super) async fn bootstrap_cloud_config_bundle(
resolve_codex_home: impl FnOnce() -> std::io::Result<AbsolutePathBuf>,
strict_config: bool,
) -> anyhow::Result<CloudConfigBundleLoader> {
if options.permissions_profile.is_none()
if options.sandbox_state.sandbox_state_json.is_some()
|| !matches!(
options.managed_requirements_mode,
ManagedRequirementsMode::Include
@@ -29,6 +30,7 @@ pub(super) async fn bootstrap_cloud_config_bundle(
Some(cwd) => AbsolutePathBuf::relative_to_current_dir(cwd)?,
None => AbsolutePathBuf::current_dir()?,
};
let network_policy = EmbeddedNetworkPolicy::load(&options.loader_overrides).await;
let bootstrap_config = load_config_toml_with_layer_stack(
codex_home.as_path(),
Some(&cwd),
@@ -41,7 +43,10 @@ pub(super) async fn bootstrap_cloud_config_bundle(
)
.await?;
Ok(chatgpt_config_bundle_loader_for_storage(
bootstrap_auth_config(codex_home.as_path(), &bootstrap_config)?,
network_policy.bind_bootstrap_auth(bootstrap_auth_config(
codex_home.as_path(),
&bootstrap_config,
)?),
/*enable_codex_api_key_env*/ false,
)
.await?)
@@ -1,3 +1,4 @@
use anyhow::Context;
use anyhow::Result;
use app_test_support::ChatGptAuthFixture;
use app_test_support::write_chatgpt_auth;
@@ -36,7 +37,7 @@ enabled = true
"#;
#[tokio::test]
async fn debug_sandbox_bootstraps_cloud_managed_permission_profile_from_backend() -> Result<()> {
async fn sandbox_fetches_managed_profile_only_when_local_policy_allows_backend() -> Result<()> {
let server = MockServer::start().await;
let expected_requirements = json!([{
"id": "req-managed-cloud",
@@ -74,20 +75,45 @@ async fn debug_sandbox_bootstraps_cloud_managed_permission_profile_from_backend(
AuthCredentialsStoreMode::File,
)?;
let options = DebugSandboxConfigOptions {
let mut options = DebugSandboxConfigOptions {
sandbox_state: Default::default(),
permissions_profile: Some("managed-cloud".to_string()),
permissions_profile: None,
cwd: Some(codex_home.path().to_path_buf()),
managed_requirements_mode: ManagedRequirementsMode::Include,
loader_overrides: LoaderOverrides::without_managed_config_for_tests(),
};
let cloud_config_bundle = bootstrap_cloud_config_bundle(
&[],
&options,
|| AbsolutePathBuf::from_absolute_path(codex_home.path()),
/*strict_config*/ false,
)
.await?;
let bootstrap = async |options: &DebugSandboxConfigOptions| {
bootstrap_cloud_config_bundle(
&[],
options,
|| AbsolutePathBuf::from_absolute_path(codex_home.path()),
/*strict_config*/ false,
)
.await
};
let requirements = codex_home.path().join("requirements.toml");
std::fs::write(&requirements, "[application.network]\nenabled = true\n")?;
options.loader_overrides.system_requirements_path = Some(requirements);
let denied = bootstrap(&options)
.await?
.get()
.await
.expect_err("local policy denies the bundle endpoint");
assert_eq!(
denied.to_string(),
"destination denied by application network policy"
);
assert!(
server
.received_requests()
.await
.context("mock request log")?
.is_empty()
);
options.permissions_profile = Some("managed-cloud".to_string());
options.loader_overrides = LoaderOverrides::without_managed_config_for_tests();
let cloud_config_bundle = bootstrap(&options).await?;
let config = load_debug_sandbox_config_with_codex_home(
Vec::new(),
/*codex_linux_sandbox_exe*/ None,
@@ -135,3 +161,25 @@ async fn debug_sandbox_bootstraps_cloud_managed_permission_profile_from_backend(
Ok(())
}
#[tokio::test]
async fn sandbox_state_skips_cloud_bootstrap() -> Result<()> {
let options = DebugSandboxConfigOptions {
sandbox_state: crate::SandboxStateArgs {
sandbox_state_json: Some("{}".into()),
..Default::default()
},
permissions_profile: None,
cwd: None,
managed_requirements_mode: ManagedRequirementsMode::Include,
loader_overrides: LoaderOverrides::without_managed_config_for_tests(),
};
bootstrap_cloud_config_bundle(
&[],
&options,
|| panic!("supplied state must skip CODEX_HOME resolution"),
/*strict_config*/ false,
)
.await?;
Ok(())
}