mirror of
https://github.com/openai/codex.git
synced 2026-11-03 16:23:51 +00:00
feat: add network proxy feature flag (#20147)
## Why The permissions migration is making `permissions.<profile>.network.enabled` the canonical sandbox network bit, while proxy startup is a separate concern. Enabling network access should not implicitly start the proxy, and users who are still on legacy sandbox modes need a separate place to opt into proxy startup and provide proxy-specific settings. This follow-up to #19900 gives the network proxy its own feature surface instead of overloading permission-profile network semantics. ## What changed - Add an experimental `network_proxy` feature with a configurable `[features.network_proxy]` table. - Overlay `features.network_proxy` settings onto the configured proxy state after permission-profile selection, so the proxy only starts when the active `NetworkSandboxPolicy` already allows network access. - Preserve `[experimental_network]` startup behavior independently of the new feature flag. ## Behavior and examples There are now three related knobs: - `permissions.<profile>.network.enabled` controls whether the active permission profile has network access at all. - `features.network_proxy` enables proxy restrictions for an already-network-enabled profile. - Legacy `sandbox_mode` plus `[sandbox_workspace_write].network_access` still control whether legacy `workspace-write` has network access at all. The rule is: - network off + proxy flag on -> network stays off, proxy is a no-op - network on + proxy flag off -> unrestricted direct network - network on + proxy flag on -> network stays on, with proxy restrictions applied For permission profiles, the feature toggle adds proxy restrictions only when network access is already enabled: ```toml default_permissions = "workspace" [permissions.workspace.filesystem] ":minimal" = "read" [permissions.workspace.network] enabled = true [features] network_proxy = true ``` If `network.enabled = false`, the same feature flag is a no-op: network remains off and the proxy does not start. For legacy sandbox config, `network_access` remains the master switch: ```toml sandbox_mode = "workspace-write" [sandbox_workspace_write] network_access = true [features] network_proxy = true ``` That keeps legacy `workspace-write` network access on, but routes it through the proxy policy. If `network_access = false`, the proxy feature is a no-op and legacy `workspace-write` remains offline. The same proxy opt-in can be supplied from the CLI: ```bash codex -c 'features.network_proxy=true' ``` Additional proxy settings can be supplied when a table is needed: ```bash codex \ -c 'features.network_proxy.enabled=true' \ -c 'features.network_proxy.enable_socks5=false' ``` The intended behavior matrix is: | Config surface | Network setting | `features.network_proxy` | Direct sandbox network | Proxy | | --- | --- | --- | --- | --- | | Permission profile | `network.enabled = false` | off | restricted | off | | Permission profile | `network.enabled = false` | on | restricted | off | | Permission profile | `network.enabled = true` | off | enabled | off | | Permission profile | `network.enabled = true` | on | enabled | on | | Legacy `workspace-write` | `network_access = false` | off | restricted | off | | Legacy `workspace-write` | `network_access = false` | on | restricted | off | | Legacy `workspace-write` | `network_access = true` | off | enabled | off | | Legacy `workspace-write` | `network_access = true` | on | enabled | on | `[experimental_network]` requirements remain separate from the user feature toggle and still start the proxy on their own. Relevant code: - [`features/src/feature_configs.rs`](https://github.com/openai/codex/blob/43785aff47/codex-rs/features/src/feature_configs.rs#L58-L117) defines the feature-specific proxy config. - [`core/src/config/mod.rs`](https://github.com/openai/codex/blob/43785aff47/codex-rs/core/src/config/mod.rs#L1959-L1964) reads the feature table, and [later applies it only when network access is already enabled](https://github.com/openai/codex/blob/43785aff47/codex-rs/core/src/config/mod.rs#L2448-L2458). ## Verification Added focused coverage for: - keeping the proxy off when `features.network_proxy` is enabled but sandbox network access is disabled - the full permission-profile and legacy `workspace-write` matrix above - preserving `[experimental_network]` startup without the feature - reusing profile-supplied proxy settings when the feature is enabled Ran: - `cargo test -p codex-features` - `cargo test -p codex-core network_proxy_feature` - `cargo test -p codex-core experimental_network_requirements_enable_proxy_without_feature`
This commit is contained in:
1 parent
54ec99cb54
commit
c7b55cdc46
10 files changed
+718
-124
No files matched your search
@@ -792,59 +792,12 @@ allow_upstream_proxy = false
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn permissions_profiles_network_enabled_allows_runtime_network_without_proxy()
|
||||
async fn permissions_profiles_proxy_policy_does_not_start_managed_network_proxy_without_feature()
|
||||
-> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let cwd = TempDir::new()?;
|
||||
std::fs::write(cwd.path().join(".git"), "gitdir: nowhere")?;
|
||||
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
ConfigToml {
|
||||
default_permissions: Some("workspace".to_string()),
|
||||
permissions: Some(PermissionsToml {
|
||||
entries: BTreeMap::from([(
|
||||
"workspace".to_string(),
|
||||
PermissionProfileToml {
|
||||
filesystem: Some(FilesystemPermissionsToml {
|
||||
glob_scan_max_depth: None,
|
||||
entries: BTreeMap::from([(
|
||||
":minimal".to_string(),
|
||||
FilesystemPermissionToml::Access(FileSystemAccessMode::Read),
|
||||
)]),
|
||||
}),
|
||||
network: Some(NetworkToml {
|
||||
enabled: Some(true),
|
||||
..Default::default()
|
||||
}),
|
||||
},
|
||||
)]),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
ConfigOverrides {
|
||||
cwd: Some(cwd.path().to_path_buf()),
|
||||
..Default::default()
|
||||
},
|
||||
codex_home.abs(),
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(
|
||||
config.permissions.network_sandbox_policy(),
|
||||
NetworkSandboxPolicy::Enabled
|
||||
);
|
||||
assert!(
|
||||
config.permissions.network.is_none(),
|
||||
"bare profile network.enabled should not start the managed network proxy"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn permissions_profiles_proxy_policy_starts_managed_network_proxy() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let cwd = TempDir::new()?;
|
||||
std::fs::write(cwd.path().join(".git"), "gitdir: nowhere")?;
|
||||
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
ConfigToml {
|
||||
default_permissions: Some("workspace".to_string()),
|
||||
@@ -877,6 +830,227 @@ async fn permissions_profiles_proxy_policy_starts_managed_network_proxy() -> std
|
||||
codex_home.abs(),
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(
|
||||
config.permissions.network_sandbox_policy(),
|
||||
NetworkSandboxPolicy::Enabled
|
||||
);
|
||||
assert!(
|
||||
config.permissions.network.is_none(),
|
||||
"profile proxy policy should not start the managed network proxy without the feature"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn network_proxy_feature_is_no_op_without_sandbox_network() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let cwd = TempDir::new()?;
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
ConfigToml {
|
||||
features: Some(toml::from_str("network_proxy = true").expect("valid features")),
|
||||
..Default::default()
|
||||
},
|
||||
ConfigOverrides {
|
||||
cwd: Some(cwd.path().to_path_buf()),
|
||||
..Default::default()
|
||||
},
|
||||
codex_home.abs(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
config.permissions.network_sandbox_policy(),
|
||||
NetworkSandboxPolicy::Restricted
|
||||
);
|
||||
assert!(
|
||||
config.permissions.network.is_none(),
|
||||
"network_proxy should not start the managed network proxy while network access is off"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn network_proxy_feature_matrix_preserves_sandbox_network_semantics() -> std::io::Result<()> {
|
||||
#[derive(Clone, Copy)]
|
||||
enum Surface {
|
||||
PermissionProfile,
|
||||
LegacyWorkspaceWrite,
|
||||
}
|
||||
|
||||
struct Case {
|
||||
name: &'static str,
|
||||
surface: Surface,
|
||||
network_enabled: bool,
|
||||
proxy_enabled: bool,
|
||||
expected_network_policy: NetworkSandboxPolicy,
|
||||
}
|
||||
|
||||
let cases = [
|
||||
Case {
|
||||
name: "permission profile network disabled without proxy",
|
||||
surface: Surface::PermissionProfile,
|
||||
network_enabled: false,
|
||||
proxy_enabled: false,
|
||||
expected_network_policy: NetworkSandboxPolicy::Restricted,
|
||||
},
|
||||
Case {
|
||||
name: "permission profile network disabled with proxy",
|
||||
surface: Surface::PermissionProfile,
|
||||
network_enabled: false,
|
||||
proxy_enabled: true,
|
||||
expected_network_policy: NetworkSandboxPolicy::Restricted,
|
||||
},
|
||||
Case {
|
||||
name: "permission profile network enabled without proxy",
|
||||
surface: Surface::PermissionProfile,
|
||||
network_enabled: true,
|
||||
proxy_enabled: false,
|
||||
expected_network_policy: NetworkSandboxPolicy::Enabled,
|
||||
},
|
||||
Case {
|
||||
name: "permission profile network enabled with proxy",
|
||||
surface: Surface::PermissionProfile,
|
||||
network_enabled: true,
|
||||
proxy_enabled: true,
|
||||
expected_network_policy: NetworkSandboxPolicy::Enabled,
|
||||
},
|
||||
Case {
|
||||
name: "legacy workspace write network disabled without proxy",
|
||||
surface: Surface::LegacyWorkspaceWrite,
|
||||
network_enabled: false,
|
||||
proxy_enabled: false,
|
||||
expected_network_policy: NetworkSandboxPolicy::Restricted,
|
||||
},
|
||||
Case {
|
||||
name: "legacy workspace write network disabled with proxy",
|
||||
surface: Surface::LegacyWorkspaceWrite,
|
||||
network_enabled: false,
|
||||
proxy_enabled: true,
|
||||
expected_network_policy: NetworkSandboxPolicy::Restricted,
|
||||
},
|
||||
Case {
|
||||
name: "legacy workspace write network enabled without proxy",
|
||||
surface: Surface::LegacyWorkspaceWrite,
|
||||
network_enabled: true,
|
||||
proxy_enabled: false,
|
||||
expected_network_policy: NetworkSandboxPolicy::Enabled,
|
||||
},
|
||||
Case {
|
||||
name: "legacy workspace write network enabled with proxy",
|
||||
surface: Surface::LegacyWorkspaceWrite,
|
||||
network_enabled: true,
|
||||
proxy_enabled: true,
|
||||
expected_network_policy: NetworkSandboxPolicy::Enabled,
|
||||
},
|
||||
];
|
||||
|
||||
for case in cases {
|
||||
let codex_home = TempDir::new()?;
|
||||
let cwd = TempDir::new()?;
|
||||
std::fs::write(cwd.path().join(".git"), "gitdir: nowhere")?;
|
||||
let features = case
|
||||
.proxy_enabled
|
||||
.then(|| toml::from_str("network_proxy = true").expect("valid features"));
|
||||
let base_config = match case.surface {
|
||||
Surface::PermissionProfile => ConfigToml {
|
||||
default_permissions: Some("workspace".to_string()),
|
||||
permissions: Some(PermissionsToml {
|
||||
entries: BTreeMap::from([(
|
||||
"workspace".to_string(),
|
||||
PermissionProfileToml {
|
||||
filesystem: Some(FilesystemPermissionsToml {
|
||||
glob_scan_max_depth: None,
|
||||
entries: BTreeMap::from([(
|
||||
":minimal".to_string(),
|
||||
FilesystemPermissionToml::Access(FileSystemAccessMode::Read),
|
||||
)]),
|
||||
}),
|
||||
network: Some(NetworkToml {
|
||||
enabled: Some(case.network_enabled),
|
||||
..Default::default()
|
||||
}),
|
||||
},
|
||||
)]),
|
||||
}),
|
||||
features,
|
||||
..Default::default()
|
||||
},
|
||||
Surface::LegacyWorkspaceWrite => ConfigToml {
|
||||
sandbox_mode: Some(SandboxMode::WorkspaceWrite),
|
||||
sandbox_workspace_write: Some(SandboxWorkspaceWrite {
|
||||
network_access: case.network_enabled,
|
||||
..Default::default()
|
||||
}),
|
||||
windows: Some(WindowsToml {
|
||||
sandbox: Some(WindowsSandboxModeToml::Elevated),
|
||||
sandbox_private_desktop: None,
|
||||
}),
|
||||
features,
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
base_config,
|
||||
ConfigOverrides {
|
||||
cwd: Some(cwd.path().to_path_buf()),
|
||||
..Default::default()
|
||||
},
|
||||
codex_home.abs(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
config.permissions.network_sandbox_policy(),
|
||||
case.expected_network_policy,
|
||||
"{}",
|
||||
case.name
|
||||
);
|
||||
assert_eq!(
|
||||
config.permissions.network.is_some(),
|
||||
case.network_enabled && case.proxy_enabled,
|
||||
"{}",
|
||||
case.name
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn network_proxy_cli_overrides_merge_toggle_with_proxy_config() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let cwd = TempDir::new()?;
|
||||
std::fs::write(
|
||||
codex_home.path().join(CONFIG_TOML_FILE),
|
||||
r#"
|
||||
sandbox_mode = "workspace-write"
|
||||
|
||||
[sandbox_workspace_write]
|
||||
network_access = true
|
||||
|
||||
[windows]
|
||||
sandbox = "elevated"
|
||||
"#,
|
||||
)?;
|
||||
let config = ConfigBuilder::without_managed_config_for_tests()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.cli_overrides(vec![
|
||||
(
|
||||
"features.network_proxy.enabled".to_string(),
|
||||
toml::Value::Boolean(true),
|
||||
),
|
||||
(
|
||||
"features.network_proxy.enable_socks5".to_string(),
|
||||
toml::Value::Boolean(false),
|
||||
),
|
||||
])
|
||||
.harness_overrides(ConfigOverrides {
|
||||
cwd: Some(cwd.path().to_path_buf()),
|
||||
..Default::default()
|
||||
})
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
config.permissions.network_sandbox_policy(),
|
||||
NetworkSandboxPolicy::Enabled
|
||||
@@ -885,11 +1059,187 @@ async fn permissions_profiles_proxy_policy_starts_managed_network_proxy() -> std
|
||||
.permissions
|
||||
.network
|
||||
.as_ref()
|
||||
.expect("profile proxy policy should start the managed network proxy");
|
||||
assert_eq!(network.proxy_host_and_port(), "127.0.0.1:43128");
|
||||
.expect("network_proxy should start the managed network proxy");
|
||||
assert_eq!(network.proxy_host_and_port(), "127.0.0.1:3128");
|
||||
assert!(!network.socks_enabled());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn experimental_network_requirements_enable_proxy_without_feature() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let config = ConfigBuilder::without_managed_config_for_tests()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.fallback_cwd(Some(codex_home.path().to_path_buf()))
|
||||
.cloud_requirements(CloudRequirementsLoader::new(async {
|
||||
Ok(Some(codex_config::ConfigRequirementsToml {
|
||||
network: Some(codex_config::NetworkRequirementsToml {
|
||||
enabled: Some(true),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
}))
|
||||
}))
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
assert!(!config.features.enabled(Feature::NetworkProxy));
|
||||
assert!(config.managed_network_requirements_enabled());
|
||||
assert!(
|
||||
!network.socks_enabled(),
|
||||
"profile proxy policy should preserve SOCKS config"
|
||||
config
|
||||
.permissions
|
||||
.network
|
||||
.as_ref()
|
||||
.expect("experimental_network should configure the managed proxy")
|
||||
.enabled()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn network_proxy_feature_uses_profile_network_proxy_settings() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let cwd = TempDir::new()?;
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
ConfigToml {
|
||||
features: Some(toml::from_str("network_proxy = true").expect("valid features")),
|
||||
default_permissions: Some("workspace".to_string()),
|
||||
permissions: Some(PermissionsToml {
|
||||
entries: BTreeMap::from([(
|
||||
"workspace".to_string(),
|
||||
PermissionProfileToml {
|
||||
filesystem: Some(FilesystemPermissionsToml {
|
||||
glob_scan_max_depth: None,
|
||||
entries: BTreeMap::from([(
|
||||
":minimal".to_string(),
|
||||
FilesystemPermissionToml::Access(FileSystemAccessMode::Read),
|
||||
)]),
|
||||
}),
|
||||
network: Some(NetworkToml {
|
||||
enabled: Some(true),
|
||||
proxy_url: Some("http://127.0.0.1:43128".to_string()),
|
||||
enable_socks5: Some(false),
|
||||
..Default::default()
|
||||
}),
|
||||
},
|
||||
)]),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
ConfigOverrides {
|
||||
cwd: Some(cwd.path().to_path_buf()),
|
||||
..Default::default()
|
||||
},
|
||||
codex_home.abs(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
config.permissions.network_sandbox_policy(),
|
||||
NetworkSandboxPolicy::Enabled
|
||||
);
|
||||
let network = config
|
||||
.permissions
|
||||
.network
|
||||
.as_ref()
|
||||
.expect("network_proxy should start the managed network proxy");
|
||||
assert_eq!(network.proxy_host_and_port(), "127.0.0.1:43128");
|
||||
assert!(!network.socks_enabled());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn profile_network_proxy_disable_ignores_base_feature_config() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let cwd = TempDir::new()?;
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
ConfigToml {
|
||||
features: Some(
|
||||
toml::from_str(
|
||||
r#"
|
||||
[network_proxy]
|
||||
enabled = true
|
||||
proxy_url = "http://127.0.0.1:43128"
|
||||
"#,
|
||||
)
|
||||
.expect("valid base features"),
|
||||
),
|
||||
profiles: HashMap::from([(
|
||||
"no_proxy".to_string(),
|
||||
ConfigProfile {
|
||||
features: Some(
|
||||
toml::from_str("network_proxy = false").expect("valid profile features"),
|
||||
),
|
||||
..Default::default()
|
||||
},
|
||||
)]),
|
||||
profile: Some("no_proxy".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
ConfigOverrides {
|
||||
cwd: Some(cwd.path().to_path_buf()),
|
||||
..Default::default()
|
||||
},
|
||||
codex_home.abs(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert!(!config.features.enabled(Feature::NetworkProxy));
|
||||
assert!(config.permissions.network.is_none());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn disabled_network_proxy_feature_does_not_start_profile_proxy_policy() -> std::io::Result<()>
|
||||
{
|
||||
let codex_home = TempDir::new()?;
|
||||
let cwd = TempDir::new()?;
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
ConfigToml {
|
||||
features: Some(
|
||||
toml::from_str(
|
||||
r#"
|
||||
[network_proxy]
|
||||
enabled = false
|
||||
"#,
|
||||
)
|
||||
.expect("valid features"),
|
||||
),
|
||||
default_permissions: Some("workspace".to_string()),
|
||||
permissions: Some(PermissionsToml {
|
||||
entries: BTreeMap::from([(
|
||||
"workspace".to_string(),
|
||||
PermissionProfileToml {
|
||||
filesystem: Some(FilesystemPermissionsToml {
|
||||
glob_scan_max_depth: None,
|
||||
entries: BTreeMap::from([(
|
||||
":minimal".to_string(),
|
||||
FilesystemPermissionToml::Access(FileSystemAccessMode::Read),
|
||||
)]),
|
||||
}),
|
||||
network: Some(NetworkToml {
|
||||
enabled: Some(true),
|
||||
proxy_url: Some("http://127.0.0.1:43128".to_string()),
|
||||
enable_socks5: Some(false),
|
||||
..Default::default()
|
||||
}),
|
||||
},
|
||||
)]),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
ConfigOverrides {
|
||||
cwd: Some(cwd.path().to_path_buf()),
|
||||
..Default::default()
|
||||
},
|
||||
codex_home.abs(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert!(!config.features.enabled(Feature::NetworkProxy));
|
||||
assert!(
|
||||
config.permissions.network.is_none(),
|
||||
"disabled feature should keep profile proxy policy from starting the managed proxy"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ use codex_features::FeatureToml;
|
||||
use codex_features::Features;
|
||||
use codex_features::FeaturesToml;
|
||||
use codex_features::MultiAgentV2ConfigToml;
|
||||
use codex_features::NetworkProxyConfigToml;
|
||||
use codex_git_utils::resolve_root_git_project_for_trust;
|
||||
use codex_login::AuthManagerConfig;
|
||||
use codex_mcp::McpConfig;
|
||||
@@ -112,6 +113,7 @@ use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::config::permissions::BUILT_IN_WORKSPACE_PROFILE;
|
||||
use crate::config::permissions::apply_network_proxy_feature_config;
|
||||
use crate::config::permissions::builtin_permission_profile;
|
||||
use crate::config::permissions::compile_permission_profile_selection;
|
||||
use crate::config::permissions::default_builtin_permission_profile_name;
|
||||
@@ -2026,6 +2028,13 @@ fn apps_mcp_path_override_toml_config(
|
||||
}
|
||||
}
|
||||
|
||||
fn network_proxy_toml_config(features: Option<&FeaturesToml>) -> Option<&NetworkProxyConfigToml> {
|
||||
match features?.network_proxy.as_ref()? {
|
||||
FeatureToml::Enabled(_) => None,
|
||||
FeatureToml::Config(config) => Some(config),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_web_search_mode_for_turn(
|
||||
web_search_mode: &Constrained<WebSearchMode>,
|
||||
permission_profile: &PermissionProfile,
|
||||
@@ -2216,6 +2225,7 @@ impl Config {
|
||||
feature_requirements,
|
||||
&mut startup_warnings,
|
||||
)?;
|
||||
let enable_network_proxy = features.enabled(Feature::NetworkProxy);
|
||||
let windows_sandbox_mode = resolve_windows_sandbox_mode(&cfg, &config_profile);
|
||||
let windows_sandbox_private_desktop =
|
||||
resolve_windows_sandbox_private_desktop(&cfg, &config_profile);
|
||||
@@ -2299,7 +2309,7 @@ impl Config {
|
||||
let using_implicit_builtin_profile =
|
||||
permission_config_syntax.is_none() && default_permissions.is_none();
|
||||
let (
|
||||
configured_network_proxy_config,
|
||||
mut configured_network_proxy_config,
|
||||
permission_profile,
|
||||
file_system_sandbox_policy,
|
||||
mut active_permission_profile,
|
||||
@@ -2512,6 +2522,22 @@ impl Config {
|
||||
None,
|
||||
)
|
||||
};
|
||||
if enable_network_proxy && permission_profile.network_sandbox_policy().is_enabled() {
|
||||
if let Some(network_proxy) = network_proxy_toml_config(cfg.features.as_ref()) {
|
||||
apply_network_proxy_feature_config(
|
||||
&mut configured_network_proxy_config,
|
||||
network_proxy,
|
||||
);
|
||||
}
|
||||
if let Some(network_proxy) = network_proxy_toml_config(config_profile.features.as_ref())
|
||||
{
|
||||
apply_network_proxy_feature_config(
|
||||
&mut configured_network_proxy_config,
|
||||
network_proxy,
|
||||
);
|
||||
}
|
||||
configured_network_proxy_config.network.enabled = true;
|
||||
}
|
||||
let approval_policy_was_explicit = approval_policy_override.is_some()
|
||||
|| config_profile.approval_policy.is_some()
|
||||
|| cfg.approval_policy.is_some();
|
||||
|
||||
@@ -6,10 +6,19 @@ use std::path::PathBuf;
|
||||
|
||||
use codex_config::permissions_toml::FilesystemPermissionToml;
|
||||
use codex_config::permissions_toml::FilesystemPermissionsToml;
|
||||
use codex_config::permissions_toml::NetworkDomainPermissionToml;
|
||||
use codex_config::permissions_toml::NetworkDomainPermissionsToml;
|
||||
use codex_config::permissions_toml::NetworkToml;
|
||||
use codex_config::permissions_toml::NetworkUnixSocketPermissionToml;
|
||||
use codex_config::permissions_toml::NetworkUnixSocketPermissionsToml;
|
||||
use codex_config::permissions_toml::PermissionProfileToml;
|
||||
use codex_config::permissions_toml::PermissionsToml;
|
||||
use codex_config::types::SandboxWorkspaceWrite;
|
||||
use codex_features::NetworkProxyConfigToml;
|
||||
use codex_features::NetworkProxyDomainPermissionToml;
|
||||
use codex_features::NetworkProxyModeToml;
|
||||
use codex_features::NetworkProxyUnixSocketPermissionToml;
|
||||
use codex_network_proxy::NetworkMode;
|
||||
use codex_network_proxy::NetworkProxyConfig;
|
||||
#[cfg(test)]
|
||||
use codex_network_proxy::NetworkUnixSocketPermission as ProxyNetworkUnixSocketPermission;
|
||||
@@ -108,35 +117,70 @@ pub(crate) fn network_proxy_config_from_profile_network(
|
||||
NetworkProxyConfig::default,
|
||||
NetworkToml::to_network_proxy_config,
|
||||
);
|
||||
// Profile `network.enabled` controls sandbox network access. Do not start a
|
||||
// managed proxy for that bit alone, but keep the proxy enabled when the
|
||||
// profile also supplied policy that only the proxy can enforce.
|
||||
config.network.enabled = network.is_some_and(profile_network_requires_proxy);
|
||||
// Profile `network.enabled` controls sandbox network access. Profiles may
|
||||
// provide proxy settings for the feature gate to consume when that network
|
||||
// access is enabled, but they do not start the managed proxy on their own.
|
||||
config.network.enabled = false;
|
||||
config
|
||||
}
|
||||
|
||||
fn profile_network_requires_proxy(network: &NetworkToml) -> bool {
|
||||
if network.enabled != Some(true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
network.proxy_url.is_some()
|
||||
|| network.enable_socks5 == Some(true)
|
||||
|| network.socks_url.is_some()
|
||||
|| network.enable_socks5_udp == Some(true)
|
||||
|| network.allow_upstream_proxy == Some(true)
|
||||
|| network.dangerously_allow_non_loopback_proxy == Some(true)
|
||||
|| network.dangerously_allow_all_unix_sockets == Some(true)
|
||||
|| network.mode.is_some()
|
||||
|| network
|
||||
pub(crate) fn apply_network_proxy_feature_config(
|
||||
config: &mut NetworkProxyConfig,
|
||||
feature_config: &NetworkProxyConfigToml,
|
||||
) {
|
||||
NetworkToml {
|
||||
enabled: feature_config.enabled,
|
||||
proxy_url: feature_config.proxy_url.clone(),
|
||||
enable_socks5: feature_config.enable_socks5,
|
||||
socks_url: feature_config.socks_url.clone(),
|
||||
enable_socks5_udp: feature_config.enable_socks5_udp,
|
||||
allow_upstream_proxy: feature_config.allow_upstream_proxy,
|
||||
dangerously_allow_non_loopback_proxy: feature_config.dangerously_allow_non_loopback_proxy,
|
||||
dangerously_allow_all_unix_sockets: feature_config.dangerously_allow_all_unix_sockets,
|
||||
mode: feature_config.mode.map(|mode| match mode {
|
||||
NetworkProxyModeToml::Limited => NetworkMode::Limited,
|
||||
NetworkProxyModeToml::Full => NetworkMode::Full,
|
||||
}),
|
||||
domains: feature_config
|
||||
.domains
|
||||
.as_ref()
|
||||
.is_some_and(|domains| !domains.is_empty())
|
||||
|| network
|
||||
.unix_sockets
|
||||
.as_ref()
|
||||
.is_some_and(|unix_sockets| !unix_sockets.is_empty())
|
||||
|| network.allow_local_binding == Some(true)
|
||||
.map(|domains| NetworkDomainPermissionsToml {
|
||||
entries: domains
|
||||
.iter()
|
||||
.map(|(pattern, permission)| {
|
||||
let permission = match permission {
|
||||
NetworkProxyDomainPermissionToml::Allow => {
|
||||
NetworkDomainPermissionToml::Allow
|
||||
}
|
||||
NetworkProxyDomainPermissionToml::Deny => {
|
||||
NetworkDomainPermissionToml::Deny
|
||||
}
|
||||
};
|
||||
(pattern.clone(), permission)
|
||||
})
|
||||
.collect(),
|
||||
}),
|
||||
unix_sockets: feature_config.unix_sockets.as_ref().map(|unix_sockets| {
|
||||
NetworkUnixSocketPermissionsToml {
|
||||
entries: unix_sockets
|
||||
.iter()
|
||||
.map(|(path, permission)| {
|
||||
let permission = match permission {
|
||||
NetworkProxyUnixSocketPermissionToml::Allow => {
|
||||
NetworkUnixSocketPermissionToml::Allow
|
||||
}
|
||||
NetworkProxyUnixSocketPermissionToml::None => {
|
||||
NetworkUnixSocketPermissionToml::None
|
||||
}
|
||||
};
|
||||
(path.clone(), permission)
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}),
|
||||
allow_local_binding: feature_config.allow_local_binding,
|
||||
}
|
||||
.apply_to_network_proxy_config(config);
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_permission_profile<'a>(
|
||||
|
||||
@@ -247,7 +247,7 @@ fn profile_network_proxy_config_keeps_proxy_disabled_for_bare_network_access() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_network_proxy_config_enables_proxy_for_proxy_policy() {
|
||||
fn profile_network_proxy_config_keeps_proxy_disabled_for_proxy_policy() {
|
||||
let config = network_proxy_config_from_profile_network(Some(&NetworkToml {
|
||||
enabled: Some(true),
|
||||
proxy_url: Some("http://127.0.0.1:43128".to_string()),
|
||||
@@ -261,7 +261,7 @@ fn profile_network_proxy_config_enables_proxy_for_proxy_policy() {
|
||||
..Default::default()
|
||||
}));
|
||||
|
||||
assert!(config.network.enabled);
|
||||
assert!(!config.network.enabled);
|
||||
assert_eq!(config.network.proxy_url, "http://127.0.0.1:43128");
|
||||
assert!(!config.network.enable_socks5);
|
||||
assert_eq!(
|
||||
|
||||
Reference in new issue
Block a user