Remove the unused network proxy loader (#33446)

## What changed

- Remove the standalone network proxy config loader, its mtime-based reloader, and their tests.
- Remove the helper exports and MITM action-reference validator used only by that loader.

GitOrigin-RevId: 60895fb4e3461e8c3f19db70869050410a4f5f07
This commit is contained in:
Charlie Marsh
2026-07-15 23:18:13 +00:00
committed by copyberry
parent 4bc2c723ef
commit 78d4a563f9
5 changed files with 0 additions and 1130 deletions

View File

@@ -453,29 +453,6 @@ impl NetworkMitmToml {
Ok(())
}
pub fn validate_action_references(
&self,
actions_by_name: &IndexMap<String, NetworkMitmActionToml>,
) -> Result<(), String> {
self.validate_action_definitions()?;
let Some(hooks) = self.hooks.as_ref() else {
return Ok(());
};
for (hook_name, hook) in hooks {
for action_name in &hook.action {
if !actions_by_name.contains_key(action_name) {
return Err(format!(
"network.mitm.hooks.{hook_name}.action references undefined action `{action_name}`"
));
}
}
}
Ok(())
}
pub fn to_runtime_hooks(
&self,
actions_by_name: Option<&IndexMap<String, NetworkMitmActionToml>>,

View File

@@ -173,8 +173,6 @@ use permission_profile_catalog::permission_profile_catalog_from_permissions;
use permission_profile_catalog::permission_profile_is_allowed;
use permission_profile_catalog::validate_permission_profile_for_deny_read;
pub(crate) use permissions::is_builtin_permission_profile_name;
pub(crate) use permissions::reject_unknown_builtin_permission_profile;
pub(crate) use permissions::resolve_permission_profile;
pub use resolved_permission_profile::PermissionProfileSnapshot;
pub(crate) use resolved_permission_profile::PermissionProfileState;

View File

@@ -59,11 +59,7 @@ mod mcp_skill_dependencies;
mod mcp_tool_approval_templates;
mod mcp_tool_exposure;
mod network_policy_decision;
pub(crate) mod network_proxy_loader;
pub use mcp::McpManager;
pub use network_proxy_loader::MtimeConfigReloader;
pub use network_proxy_loader::build_network_proxy_state;
pub use network_proxy_loader::build_network_proxy_state_and_reloader;
mod original_image_detail;
pub use codex_mcp::CodexAppsToolsCache;
pub use codex_mcp::SandboxState;

View File

@@ -1,397 +0,0 @@
use crate::config::find_codex_home;
use crate::config::is_builtin_permission_profile_name;
use crate::config::reject_unknown_builtin_permission_profile;
use crate::config::resolve_permission_profile;
use crate::exec_policy::format_exec_policy_error_with_source;
use crate::exec_policy::load_exec_policy_with_warning;
use anyhow::Context;
use anyhow::Result;
use codex_config::CONFIG_TOML_FILE;
use codex_config::ConfigLayerSource;
use codex_config::ConfigLayerStack;
use codex_config::ConfigLayerStackOrdering;
use codex_config::LoaderOverrides;
use codex_config::loader::load_config_layers_state;
use codex_config::merge_toml_values;
use codex_config::permissions_toml::NetworkMitmActionToml;
use codex_config::permissions_toml::NetworkMitmHookToml;
use codex_config::permissions_toml::NetworkMitmToml;
use codex_config::permissions_toml::NetworkToml;
use codex_config::permissions_toml::PermissionsToml;
use codex_config::permissions_toml::overlay_network_domain_permissions;
use codex_exec_server::LOCAL_FS;
use codex_network_proxy::ConfigReloader;
use codex_network_proxy::ConfigReloaderFuture;
use codex_network_proxy::ConfigState;
use codex_network_proxy::NetworkMode;
use codex_network_proxy::NetworkProxyConfig;
use codex_network_proxy::NetworkProxyConstraintError;
use codex_network_proxy::NetworkProxyConstraints;
use codex_network_proxy::NetworkProxyState;
use codex_network_proxy::build_config_state;
use codex_network_proxy::normalize_host;
use codex_network_proxy::validate_policy_against_constraints;
use codex_utils_absolute_path::AbsolutePathBuf;
use indexmap::IndexMap;
use serde::Deserialize;
use std::sync::Arc;
use tokio::sync::RwLock;
pub async fn build_network_proxy_state() -> Result<NetworkProxyState> {
let (state, reloader) = build_network_proxy_state_and_reloader().await?;
Ok(NetworkProxyState::with_reloader(state, Arc::new(reloader)))
}
pub async fn build_network_proxy_state_and_reloader() -> Result<(ConfigState, MtimeConfigReloader)>
{
let (state, layer_mtimes) = build_config_state_with_mtimes().await?;
Ok((state, MtimeConfigReloader::new(layer_mtimes)))
}
async fn build_config_state_with_mtimes() -> Result<(ConfigState, Vec<LayerMtime>)> {
let codex_home = find_codex_home().context("failed to resolve CODEX_HOME")?;
let cli_overrides = Vec::new();
let overrides = LoaderOverrides::default();
let config_layer_stack = load_config_layers_state(
LOCAL_FS.as_ref(),
&codex_home,
/*cwd*/ None,
&cli_overrides,
overrides,
&codex_config::NoopThreadConfigLoader,
)
.await
.context("failed to load Codex config")?;
let layer_mtimes = collect_layer_mtimes(&config_layer_stack);
let state = build_config_state_from_layers(&config_layer_stack).await?;
Ok((state, layer_mtimes))
}
async fn build_config_state_from_layers(
config_layer_stack: &ConfigLayerStack,
) -> Result<ConfigState> {
let (exec_policy, warning) = load_exec_policy_with_warning(config_layer_stack).await?;
if let Some(err) = warning.as_ref() {
tracing::warn!(
"failed to parse execpolicy while building network proxy state: {}",
format_exec_policy_error_with_source(err)
);
}
let config = config_from_layers(config_layer_stack, &exec_policy)?;
let constraints = enforce_trusted_constraints(config_layer_stack, &config)?;
build_config_state(config, constraints)
}
fn collect_layer_mtimes(stack: &ConfigLayerStack) -> Vec<LayerMtime> {
stack
.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ false,
)
.iter()
.filter_map(|layer| {
let path = match &layer.name {
ConfigLayerSource::System { file } => Some(file.clone()),
ConfigLayerSource::User { file, .. } => Some(file.clone()),
ConfigLayerSource::Project { dot_codex_folder } => {
Some(dot_codex_folder.join(CONFIG_TOML_FILE))
}
ConfigLayerSource::LegacyManagedConfigTomlFromFile { file } => Some(file.clone()),
_ => None,
};
path.map(LayerMtime::new)
})
.collect()
}
fn enforce_trusted_constraints(
layers: &ConfigLayerStack,
config: &NetworkProxyConfig,
) -> Result<NetworkProxyConstraints> {
let constraints = network_constraints_from_trusted_layers(layers)?;
validate_policy_against_constraints(config, &constraints)
.map_err(NetworkProxyConstraintError::into_anyhow)
.context("network proxy constraints")?;
Ok(constraints)
}
fn network_constraints_from_trusted_layers(
layers: &ConfigLayerStack,
) -> Result<NetworkProxyConstraints> {
let mut constraints = NetworkProxyConstraints::default();
let mut merged = toml::Value::Table(toml::map::Map::new());
for layer in layers.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ false,
) {
if is_user_controlled_layer(&layer.name) {
continue;
}
merge_toml_values(&mut merged, &layer.config);
}
let parsed = network_tables_from_toml(&merged)?;
if let Some(network) = selected_network_from_tables(parsed)? {
apply_network_constraints(network, &mut constraints);
}
Ok(constraints)
}
fn apply_network_constraints(network: NetworkToml, constraints: &mut NetworkProxyConstraints) {
if let Some(enabled) = network.enabled {
constraints.enabled = Some(enabled);
}
if let Some(mode) = network.mode {
constraints.mode = Some(mode);
}
if let Some(allow_upstream_proxy) = network.allow_upstream_proxy {
constraints.allow_upstream_proxy = Some(allow_upstream_proxy);
}
if let Some(dangerously_allow_non_loopback_proxy) = network.dangerously_allow_non_loopback_proxy
{
constraints.dangerously_allow_non_loopback_proxy =
Some(dangerously_allow_non_loopback_proxy);
}
if let Some(dangerously_allow_all_unix_sockets) = network.dangerously_allow_all_unix_sockets {
constraints.dangerously_allow_all_unix_sockets = Some(dangerously_allow_all_unix_sockets);
}
if let Some(domains) = network.domains.as_ref() {
let mut config = NetworkProxyConfig::default();
if let Some(allowed_domains) = constraints.allowed_domains.take() {
config.set_allowed_domains(allowed_domains);
}
if let Some(denied_domains) = constraints.denied_domains.take() {
config.set_denied_domains(denied_domains);
}
overlay_network_domain_permissions(&mut config, domains);
constraints.allowed_domains = config.allowed_domains();
constraints.denied_domains = config.denied_domains();
}
if let Some(unix_sockets) = network.unix_sockets.as_ref() {
let allow_unix_sockets = unix_sockets.allow_unix_sockets();
constraints.allow_unix_sockets = Some(allow_unix_sockets);
}
if let Some(allow_local_binding) = network.allow_local_binding {
constraints.allow_local_binding = Some(allow_local_binding);
}
}
#[derive(Debug, Clone, Default, Deserialize)]
struct NetworkTablesToml {
default_permissions: Option<String>,
permissions: Option<PermissionsToml>,
}
fn network_tables_from_toml(value: &toml::Value) -> Result<NetworkTablesToml> {
value
.clone()
.try_into()
.context("failed to deserialize network tables from config")
}
fn selected_network_from_tables(parsed: NetworkTablesToml) -> Result<Option<NetworkToml>> {
let Some(default_permissions) = parsed.default_permissions else {
return Ok(None);
};
if is_builtin_permission_profile_name(&default_permissions) {
return Ok(None);
}
reject_unknown_builtin_permission_profile(&default_permissions)?;
let permissions = parsed
.permissions
.context("default_permissions requires a `[permissions]` table for network settings")?;
let profile = resolve_permission_profile(&permissions, &default_permissions)
.map_err(anyhow::Error::from)?;
Ok(profile.network)
}
#[cfg(test)]
fn apply_network_tables(config: &mut NetworkProxyConfig, parsed: NetworkTablesToml) -> Result<()> {
if let Some(network) = selected_network_from_tables(parsed)? {
network.apply_to_network_proxy_config(config);
}
Ok(())
}
#[derive(Default)]
struct NetworkConfigAccumulator {
config: NetworkProxyConfig,
mitm_hooks: IndexMap<String, NetworkMitmHookToml>,
mitm_actions: IndexMap<String, NetworkMitmActionToml>,
}
impl NetworkConfigAccumulator {
fn apply_network_tables(&mut self, parsed: NetworkTablesToml) -> Result<()> {
if let Some(network) = selected_network_from_tables(parsed)? {
self.apply_network(network);
}
Ok(())
}
fn apply_network(&mut self, mut network: NetworkToml) {
let mitm = network.mitm.take();
network.apply_to_network_proxy_config(&mut self.config);
if let Some(mitm) = mitm {
if let Some(actions) = mitm.actions {
self.mitm_actions.extend(actions);
}
if let Some(hooks) = mitm.hooks {
self.mitm_hooks.extend(hooks);
}
}
}
fn finish(mut self) -> Result<NetworkProxyConfig> {
if !self.mitm_hooks.is_empty() {
let actions = self.mitm_actions;
let mitm = NetworkMitmToml {
hooks: Some(self.mitm_hooks),
actions: Some(actions.clone()),
};
mitm.validate_action_references(&actions)
.map_err(anyhow::Error::msg)?;
self.config.mitm_hooks = mitm.to_runtime_hooks(Some(&actions));
}
self.config.mitm =
self.config.mode == NetworkMode::Limited || !self.config.mitm_hooks.is_empty();
Ok(self.config)
}
}
fn config_from_layers(
layers: &ConfigLayerStack,
exec_policy: &codex_execpolicy::Policy,
) -> Result<NetworkProxyConfig> {
let mut merged = toml::Value::Table(toml::map::Map::new());
for layer in layers.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ false,
) {
merge_toml_values(&mut merged, &layer.config);
}
let parsed = network_tables_from_toml(&merged)?;
let mut accumulator = NetworkConfigAccumulator::default();
accumulator.apply_network_tables(parsed)?;
let mut config = accumulator.finish()?;
apply_exec_policy_network_rules(&mut config, exec_policy);
Ok(config)
}
fn apply_exec_policy_network_rules(
config: &mut NetworkProxyConfig,
exec_policy: &codex_execpolicy::Policy,
) {
let (allowed_domains, denied_domains) = exec_policy.compiled_network_domains();
for host in allowed_domains {
upsert_network_domain(
config,
host,
codex_network_proxy::NetworkDomainPermission::Allow,
);
}
for host in denied_domains {
upsert_network_domain(
config,
host,
codex_network_proxy::NetworkDomainPermission::Deny,
);
}
}
fn upsert_network_domain(
config: &mut NetworkProxyConfig,
host: String,
permission: codex_network_proxy::NetworkDomainPermission,
) {
config.upsert_domain_permission(host, permission, normalize_host);
}
fn is_user_controlled_layer(layer: &ConfigLayerSource) -> bool {
matches!(
layer,
ConfigLayerSource::User { .. }
| ConfigLayerSource::Project { .. }
| ConfigLayerSource::SessionFlags
)
}
#[derive(Clone)]
struct LayerMtime {
path: AbsolutePathBuf,
mtime: Option<std::time::SystemTime>,
}
impl LayerMtime {
fn new(path: AbsolutePathBuf) -> Self {
let mtime = path.metadata().and_then(|m| m.modified()).ok();
Self { path, mtime }
}
}
pub struct MtimeConfigReloader {
layer_mtimes: RwLock<Vec<LayerMtime>>,
}
impl MtimeConfigReloader {
fn new(layer_mtimes: Vec<LayerMtime>) -> Self {
Self {
layer_mtimes: RwLock::new(layer_mtimes),
}
}
async fn needs_reload(&self) -> bool {
let guard = self.layer_mtimes.read().await;
guard.iter().any(|layer| {
let metadata = std::fs::metadata(&layer.path).ok();
match (metadata.and_then(|m| m.modified().ok()), layer.mtime) {
(Some(new_mtime), Some(old_mtime)) => new_mtime > old_mtime,
(Some(_), None) => true,
(None, Some(_)) => true,
(None, None) => false,
}
})
}
async fn maybe_reload(&self) -> Result<Option<ConfigState>> {
if !self.needs_reload().await {
return Ok(None);
}
let (state, layer_mtimes) = build_config_state_with_mtimes().await?;
let mut guard = self.layer_mtimes.write().await;
*guard = layer_mtimes;
Ok(Some(state))
}
async fn reload_now(&self) -> Result<ConfigState> {
let (state, layer_mtimes) = build_config_state_with_mtimes().await?;
let mut guard = self.layer_mtimes.write().await;
*guard = layer_mtimes;
Ok(state)
}
}
impl ConfigReloader for MtimeConfigReloader {
fn source_label(&self) -> String {
"config layers".to_string()
}
fn maybe_reload(&self) -> ConfigReloaderFuture<'_, Option<ConfigState>> {
Box::pin(MtimeConfigReloader::maybe_reload(self))
}
fn reload_now(&self) -> ConfigReloaderFuture<'_, ConfigState> {
Box::pin(MtimeConfigReloader::reload_now(self))
}
}
#[cfg(test)]
#[path = "network_proxy_loader_tests.rs"]
mod tests;

View File

@@ -1,704 +0,0 @@
use super::*;
use codex_config::ConfigLayerEntry;
use codex_config::ConfigLayerSource;
use codex_config::ConfigLayerStack;
use codex_config::ConfigRequirements;
use codex_config::ConfigRequirementsToml;
use codex_config::RequirementSource;
use codex_config::RequirementsExecPolicy;
use codex_config::Sourced;
use codex_config::permissions_toml::NetworkDomainPermissionToml;
use codex_config::permissions_toml::NetworkDomainPermissionsToml;
use codex_execpolicy::Decision;
use codex_execpolicy::NetworkRuleProtocol;
use codex_execpolicy::Policy;
use pretty_assertions::assert_eq;
use std::collections::BTreeMap;
use std::fs;
use tempfile::tempdir;
#[test]
fn higher_precedence_profile_network_overlays_domain_entries() {
let lower_network: toml::Value = toml::from_str(
r#"
default_permissions = "dev"
[permissions.dev.network]
[permissions.dev.network.domains]
"lower.example.com" = "allow"
"blocked.example.com" = "deny"
"#,
)
.expect("lower layer should parse");
let higher_network: toml::Value = toml::from_str(
r#"
default_permissions = "dev"
[permissions.dev.network]
[permissions.dev.network.domains]
"higher.example.com" = "allow"
"#,
)
.expect("higher layer should parse");
let mut config = NetworkProxyConfig::default();
apply_network_tables(
&mut config,
network_tables_from_toml(&lower_network).expect("lower layer should deserialize"),
)
.expect("lower layer should apply");
apply_network_tables(
&mut config,
network_tables_from_toml(&higher_network).expect("higher layer should deserialize"),
)
.expect("higher layer should apply");
assert_eq!(
config.allowed_domains(),
Some(vec![
"lower.example.com".to_string(),
"higher.example.com".to_string()
])
);
assert_eq!(
config.denied_domains(),
Some(vec!["blocked.example.com".to_string()])
);
}
#[test]
fn higher_precedence_profile_network_overrides_matching_domain_entries() {
let lower_network: toml::Value = toml::from_str(
r#"
default_permissions = "dev"
[permissions.dev.network]
[permissions.dev.network.domains]
"shared.example.com" = "deny"
"other.example.com" = "allow"
"#,
)
.expect("lower layer should parse");
let higher_network: toml::Value = toml::from_str(
r#"
default_permissions = "dev"
[permissions.dev.network]
[permissions.dev.network.domains]
"shared.example.com" = "allow"
"#,
)
.expect("higher layer should parse");
let mut config = NetworkProxyConfig::default();
apply_network_tables(
&mut config,
network_tables_from_toml(&lower_network).expect("lower layer should deserialize"),
)
.expect("lower layer should apply");
apply_network_tables(
&mut config,
network_tables_from_toml(&higher_network).expect("higher layer should deserialize"),
)
.expect("higher layer should apply");
assert_eq!(
config.allowed_domains(),
Some(vec![
"other.example.com".to_string(),
"shared.example.com".to_string()
])
);
assert_eq!(config.denied_domains(), None);
}
#[test]
fn higher_precedence_profile_network_overrides_named_mitm_actions() {
let lower_network: toml::Value = toml::from_str(
r#"
default_permissions = "workspace"
[permissions.workspace.network]
mode = "full"
[permissions.workspace.network.domains]
"lower.example.com" = "allow"
[permissions.workspace.network.mitm.hooks.github_write]
host = "api.github.com"
methods = ["POST"]
path_prefixes = ["/repos/openai/"]
action = ["strip_auth"]
[permissions.workspace.network.mitm.actions.strip_auth]
strip_request_headers = ["authorization"]
"#,
)
.expect("lower layer should parse");
let higher_network: toml::Value = toml::from_str(
r#"
default_permissions = "workspace"
[permissions.workspace.network]
mode = "full"
[permissions.workspace.network.domains]
"higher.example.com" = "allow"
[permissions.workspace.network.mitm.actions.strip_auth]
strip_request_headers = ["x-api-key"]
"#,
)
.expect("higher layer should parse");
let mut accumulator = NetworkConfigAccumulator::default();
accumulator
.apply_network_tables(
network_tables_from_toml(&lower_network).expect("lower layer should deserialize"),
)
.expect("lower layer should apply");
accumulator
.apply_network_tables(
network_tables_from_toml(&higher_network).expect("higher layer should deserialize"),
)
.expect("higher layer should apply");
let config = accumulator.finish().expect("merged config should build");
assert_eq!(config.mode, codex_network_proxy::NetworkMode::Full);
assert!(config.mitm);
assert_eq!(
config.allowed_domains(),
Some(vec![
"lower.example.com".to_string(),
"higher.example.com".to_string()
])
);
assert_eq!(config.mitm_hooks.len(), 1);
assert_eq!(config.mitm_hooks[0].host, "api.github.com");
assert_eq!(config.mitm_hooks[0].matcher.methods, vec!["POST"]);
assert_eq!(
config.mitm_hooks[0].actions.strip_request_headers,
vec!["x-api-key"]
);
}
#[test]
fn execpolicy_network_rules_overlay_network_lists() {
let mut config = NetworkProxyConfig::default();
config.set_allowed_domains(vec!["config.example.com".to_string()]);
config.set_denied_domains(vec!["blocked.example.com".to_string()]);
let mut exec_policy = Policy::empty();
exec_policy
.add_network_rule(
"blocked.example.com",
NetworkRuleProtocol::Https,
Decision::Allow,
/*justification*/ None,
)
.expect("allow rule should be valid");
exec_policy
.add_network_rule(
"api.example.com",
NetworkRuleProtocol::Http,
Decision::Forbidden,
/*justification*/ None,
)
.expect("deny rule should be valid");
apply_exec_policy_network_rules(&mut config, &exec_policy);
assert_eq!(
config.allowed_domains(),
Some(vec![
"config.example.com".to_string(),
"blocked.example.com".to_string()
])
);
assert_eq!(
config.denied_domains(),
Some(vec!["api.example.com".to_string()])
);
}
#[tokio::test]
async fn malformed_custom_rules_preserve_managed_denied_domain() {
let temp_dir = tempdir().expect("create temp dir");
let policy_dir = temp_dir.path().join("rules");
fs::create_dir_all(&policy_dir).expect("create policy dir");
fs::write(policy_dir.join("broken.rules"), "prefix_rule(").expect("write malformed policy");
let mut requirements_exec_policy = Policy::empty();
requirements_exec_policy
.add_network_rule(
"blocked.example.com",
NetworkRuleProtocol::Https,
Decision::Forbidden,
/*justification*/ None,
)
.expect("managed network rule should be valid");
let requirements = ConfigRequirements {
exec_policy: Some(Sourced::new(
RequirementsExecPolicy::new(requirements_exec_policy),
RequirementSource::Unknown,
)),
..ConfigRequirements::default()
};
let dot_codex_folder = AbsolutePathBuf::from_absolute_path(temp_dir.path())
.expect("dot codex folder should be absolute");
let layers = ConfigLayerStack::new(
vec![ConfigLayerEntry::new(
ConfigLayerSource::Project { dot_codex_folder },
toml::Value::Table(Default::default()),
)],
requirements,
ConfigRequirementsToml::default(),
)
.expect("layer stack should be valid");
let state = build_config_state_from_layers(&layers)
.await
.expect("proxy state should tolerate malformed custom rules");
assert_eq!(
state.config.denied_domains(),
Some(vec!["blocked.example.com".to_string()])
);
}
#[test]
fn apply_network_constraints_includes_allow_all_unix_sockets_flag() {
let config: toml::Value = toml::from_str(
r#"
default_permissions = "dev"
[permissions.dev.network]
dangerously_allow_all_unix_sockets = true
"#,
)
.expect("permissions profile should parse");
let network = selected_network_from_tables(
network_tables_from_toml(&config).expect("permissions profile should deserialize"),
)
.expect("permissions profile should select a network table")
.expect("network table should be present");
let mut constraints = NetworkProxyConstraints::default();
apply_network_constraints(network, &mut constraints);
assert_eq!(constraints.dangerously_allow_all_unix_sockets, Some(true));
}
#[test]
fn selected_network_from_tables_ignores_builtin_profile_without_permissions_table() {
let config: toml::Value = toml::from_str(
r#"
default_permissions = ":workspace"
"#,
)
.expect("built-in profile config should parse");
let network = selected_network_from_tables(
network_tables_from_toml(&config).expect("built-in profile config should deserialize"),
)
.expect("built-in profile selection should not require permissions tables");
assert_eq!(network, None);
}
#[test]
fn selected_network_from_tables_rejects_unknown_builtin_profile_without_permissions_table() {
let config: toml::Value = toml::from_str(
r#"
default_permissions = ":unknown"
"#,
)
.expect("unknown built-in config should parse");
let err = selected_network_from_tables(
network_tables_from_toml(&config).expect("unknown built-in config should deserialize"),
)
.expect_err("unknown built-in profile should be rejected");
assert_eq!(
err.to_string(),
"default_permissions refers to unknown built-in profile `:unknown`"
);
}
#[test]
fn selected_network_from_tables_resolves_builtin_workspace_parent() {
let config: toml::Value = toml::from_str(
r#"
default_permissions = "dev"
[permissions.dev]
extends = ":workspace"
[permissions.dev.network]
enabled = true
[permissions.dev.network.domains]
"child.example.com" = "allow"
"#,
)
.expect("dev extension config should parse");
let network = selected_network_from_tables(
network_tables_from_toml(&config).expect("dev extension config should deserialize"),
)
.expect("dev extension should resolve")
.expect("dev extension should expose child network config");
assert_eq!(
network,
NetworkToml {
enabled: Some(true),
domains: Some(NetworkDomainPermissionsToml {
entries: BTreeMap::from([(
"child.example.com".to_string(),
NetworkDomainPermissionToml::Allow,
)]),
}),
..Default::default()
}
);
}
#[test]
fn selected_network_from_tables_resolves_permission_profile_inheritance() {
let config: toml::Value = toml::from_str(
r#"
default_permissions = "dev"
[permissions.base.network]
enabled = true
dangerously_allow_all_unix_sockets = true
[permissions.base.network.domains]
"base.example.com" = "allow"
"shared.example.com" = "deny"
[permissions.dev]
extends = "base"
[permissions.dev.network]
allow_local_binding = true
[permissions.dev.network.domains]
"child.example.com" = "allow"
"shared.example.com" = "allow"
"#,
)
.expect("permissions profiles should parse");
let network = selected_network_from_tables(
network_tables_from_toml(&config).expect("permissions profiles should deserialize"),
)
.expect("permissions profiles should select a network table")
.expect("network table should be present");
assert_eq!(
network,
NetworkToml {
enabled: Some(true),
dangerously_allow_all_unix_sockets: Some(true),
allow_local_binding: Some(true),
domains: Some(NetworkDomainPermissionsToml {
entries: BTreeMap::from([
(
"base.example.com".to_string(),
NetworkDomainPermissionToml::Allow,
),
(
"child.example.com".to_string(),
NetworkDomainPermissionToml::Allow,
),
(
"shared.example.com".to_string(),
NetworkDomainPermissionToml::Allow,
),
]),
}),
..Default::default()
}
);
}
#[test]
fn config_from_layers_resolves_inherited_profiles_across_layers() {
let lower_layer = ConfigLayerEntry::new(
ConfigLayerSource::SessionFlags,
toml::toml! {
[permissions.base.network.domains]
"base.example.com" = "allow"
}
.into(),
);
let higher_layer = ConfigLayerEntry::new(
ConfigLayerSource::SessionFlags,
toml::toml! {
default_permissions = "dev"
[permissions.dev]
extends = "base"
[permissions.dev.network.domains]
"child.example.com" = "allow"
}
.into(),
);
let layers = ConfigLayerStack::new(
vec![lower_layer, higher_layer],
ConfigRequirements::default(),
ConfigRequirementsToml::default(),
)
.expect("layer stack should be valid");
let config =
config_from_layers(&layers, &Policy::empty()).expect("inherited profiles should load");
assert_eq!(
config.allowed_domains(),
Some(vec![
"base.example.com".to_string(),
"child.example.com".to_string(),
])
);
}
#[test]
fn config_from_layers_normalizes_profile_network_domains_before_merging_layers() {
let lower_layer = ConfigLayerEntry::new(
ConfigLayerSource::SessionFlags,
toml::toml! {
default_permissions = "dev"
[permissions.dev.network.domains]
"example.com" = "deny"
}
.into(),
);
let higher_layer = ConfigLayerEntry::new(
ConfigLayerSource::SessionFlags,
toml::toml! {
[permissions.dev.network.domains]
"EXAMPLE.COM" = "allow"
}
.into(),
);
let layers = ConfigLayerStack::new(
vec![lower_layer, higher_layer],
ConfigRequirements::default(),
ConfigRequirementsToml::default(),
)
.expect("layer stack should be valid");
let config = config_from_layers(&layers, &Policy::empty())
.expect("network domain layer precedence should load");
assert_eq!(
config.allowed_domains(),
Some(vec!["example.com".to_string()])
);
assert_eq!(config.denied_domains(), None);
}
#[test]
fn config_from_layers_uses_only_the_final_selected_profile_network() {
let lower_layer = ConfigLayerEntry::new(
ConfigLayerSource::SessionFlags,
toml::toml! {
default_permissions = "dev"
[permissions.dev.network.domains]
"lower.example.com" = "allow"
}
.into(),
);
let higher_layer = ConfigLayerEntry::new(
ConfigLayerSource::SessionFlags,
toml::toml! {
default_permissions = ":workspace"
}
.into(),
);
let layers = ConfigLayerStack::new(
vec![lower_layer, higher_layer],
ConfigRequirements::default(),
ConfigRequirementsToml::default(),
)
.expect("layer stack should be valid");
let config = config_from_layers(&layers, &Policy::empty())
.expect("final built-in profile selection should load");
assert_eq!(config.allowed_domains(), None);
assert_eq!(config.denied_domains(), None);
}
#[test]
fn trusted_constraints_use_only_the_final_selected_profile_network() {
let lower_layer = ConfigLayerEntry::new(
ConfigLayerSource::System {
file: AbsolutePathBuf::try_from(std::path::PathBuf::from("/tmp/system.toml"))
.expect("system config path should be absolute"),
},
toml::toml! {
default_permissions = "dev"
[permissions.dev.network.domains]
"managed.example.com" = "allow"
}
.into(),
);
let higher_layer = ConfigLayerEntry::new(
ConfigLayerSource::LegacyManagedConfigTomlFromFile {
file: AbsolutePathBuf::try_from(std::path::PathBuf::from("/tmp/managed.toml"))
.expect("managed config path should be absolute"),
},
toml::toml! {
default_permissions = ":workspace"
}
.into(),
);
let layers = ConfigLayerStack::new(
vec![lower_layer, higher_layer],
ConfigRequirements::default(),
ConfigRequirementsToml::default(),
)
.expect("layer stack should be valid");
let constraints = network_constraints_from_trusted_layers(&layers)
.expect("final built-in trusted selection should load");
assert_eq!(constraints.allowed_domains, None);
assert_eq!(constraints.denied_domains, None);
}
#[test]
fn trusted_constraints_normalize_profile_network_domains_before_merging_layers() {
let lower_layer = ConfigLayerEntry::new(
ConfigLayerSource::System {
file: AbsolutePathBuf::try_from(std::path::PathBuf::from("/tmp/system.toml"))
.expect("system config path should be absolute"),
},
toml::toml! {
default_permissions = "dev"
[permissions.dev.network.domains]
"example.com" = "deny"
}
.into(),
);
let higher_layer = ConfigLayerEntry::new(
ConfigLayerSource::LegacyManagedConfigTomlFromFile {
file: AbsolutePathBuf::try_from(std::path::PathBuf::from("/tmp/managed.toml"))
.expect("managed config path should be absolute"),
},
toml::toml! {
[permissions.dev.network.domains]
"EXAMPLE.COM" = "allow"
}
.into(),
);
let layers = ConfigLayerStack::new(
vec![lower_layer, higher_layer],
ConfigRequirements::default(),
ConfigRequirementsToml::default(),
)
.expect("layer stack should be valid");
let constraints = network_constraints_from_trusted_layers(&layers)
.expect("trusted network domain layer precedence should load");
assert_eq!(
constraints.allowed_domains,
Some(vec!["example.com".to_string()])
);
assert_eq!(constraints.denied_domains, None);
}
#[test]
fn apply_network_constraints_skips_empty_domain_sides() {
let config: toml::Value = toml::from_str(
r#"
default_permissions = "dev"
[permissions.dev.network]
[permissions.dev.network.domains]
"managed.example.com" = "allow"
"#,
)
.expect("permissions profile should parse");
let network = selected_network_from_tables(
network_tables_from_toml(&config).expect("permissions profile should deserialize"),
)
.expect("permissions profile should select a network table")
.expect("network table should be present");
let mut constraints = NetworkProxyConstraints::default();
apply_network_constraints(network, &mut constraints);
assert_eq!(
constraints.allowed_domains,
Some(vec!["managed.example.com".to_string()])
);
assert_eq!(constraints.denied_domains, None);
}
#[test]
fn apply_network_constraints_overlay_domain_entries() {
let lower_network: toml::Value = toml::from_str(
r#"
default_permissions = "dev"
[permissions.dev.network]
[permissions.dev.network.domains]
"blocked.example.com" = "deny"
"#,
)
.expect("lower layer should parse");
let higher_network: toml::Value = toml::from_str(
r#"
default_permissions = "dev"
[permissions.dev.network]
[permissions.dev.network.domains]
"api.example.com" = "allow"
"#,
)
.expect("higher layer should parse");
let lower_network = selected_network_from_tables(
network_tables_from_toml(&lower_network).expect("lower layer should deserialize"),
)
.expect("lower layer should select a network table")
.expect("lower network table should be present");
let higher_network = selected_network_from_tables(
network_tables_from_toml(&higher_network).expect("higher layer should deserialize"),
)
.expect("higher layer should select a network table")
.expect("higher network table should be present");
let mut constraints = NetworkProxyConstraints::default();
apply_network_constraints(lower_network, &mut constraints);
apply_network_constraints(higher_network, &mut constraints);
assert_eq!(
constraints.allowed_domains,
Some(vec!["api.example.com".to_string()])
);
assert_eq!(
constraints.denied_domains,
Some(vec!["blocked.example.com".to_string()])
);
}