Compare commits

...

11 Commits

Author SHA1 Message Date
viyatb-oai
56819e643f Merge remote-tracking branch 'origin/main' into codex/viyatb/network-proxy-feature-flag
# Conflicts:
#	codex-rs/core/src/features.rs
2026-03-09 21:09:17 -07:00
viyatb-oai
f2324da88c fix: remove stale network constraints field from guardian test 2026-03-09 19:18:20 -07:00
viyatb-oai
2a456de456 Merge remote-tracking branch 'origin/main' into codex/viyatb/network-proxy-feature-flag 2026-03-07 23:52:18 -08:00
viyatb-oai
3376bd7f35 Merge remote-tracking branch 'origin/main' into codex/viyatb/network-proxy-feature-flag
# Conflicts:
#	codex-rs/core/config.schema.json
#	codex-rs/core/src/config/mod.rs
#	codex-rs/core/src/config/permissions.rs
#	codex-rs/core/src/network_proxy_loader.rs
#	codex-rs/core/tests/suite/approvals.rs
#	codex-rs/network-proxy/README.md
2026-03-06 18:17:10 -08:00
viyatb-oai
2a5c24ef46 test: align network approval test with runtime proxy config 2026-03-06 14:35:21 -08:00
viyatb-oai
2cd0f743e1 chore: refresh config schema 2026-03-06 14:24:06 -08:00
viyatb-oai
6ec64bac90 fix: preserve disabled legacy network requirements 2026-03-06 14:13:19 -08:00
viyatb-oai
8019948014 Merge remote-tracking branch 'origin/main' into codex/viyatb/network-proxy-feature-flag
# Conflicts:
#	codex-rs/core/src/config/mod.rs
2026-03-06 14:08:53 -08:00
viyatb-oai
6e82567294 docs: remove top-level network config references 2026-03-06 10:11:33 -08:00
viyatb-oai
2bda01552a refactor: drop top-level network proxy loader config 2026-03-06 10:03:52 -08:00
viyatb-oai
aee0f23c6b feat: gate network proxy behind experimental feature 2026-03-06 09:44:18 -08:00
23 changed files with 4522 additions and 81 deletions

View File

@@ -12206,12 +12206,6 @@
"null"
]
},
"enabled": {
"type": [
"boolean",
"null"
]
},
"httpPort": {
"format": "uint16",
"minimum": 0.0,

View File

@@ -8508,12 +8508,6 @@
"null"
]
},
"enabled": {
"type": [
"boolean",
"null"
]
},
"httpPort": {
"format": "uint16",
"minimum": 0.0,

View File

@@ -151,12 +151,6 @@
"null"
]
},
"enabled": {
"type": [
"boolean",
"null"
]
},
"httpPort": {
"format": "uint16",
"minimum": 0.0,

View File

@@ -2,4 +2,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type NetworkRequirements = { enabled: boolean | null, httpPort: number | null, socksPort: number | null, allowUpstreamProxy: boolean | null, dangerouslyAllowNonLoopbackProxy: boolean | null, dangerouslyAllowAllUnixSockets: boolean | null, allowedDomains: Array<string> | null, deniedDomains: Array<string> | null, allowUnixSockets: Array<string> | null, allowLocalBinding: boolean | null, };
export type NetworkRequirements = { httpPort: number | null, socksPort: number | null, allowUpstreamProxy: boolean | null, dangerouslyAllowNonLoopbackProxy: boolean | null, dangerouslyAllowAllUnixSockets: boolean | null, allowedDomains: Array<string> | null, deniedDomains: Array<string> | null, allowUnixSockets: Array<string> | null, allowLocalBinding: boolean | null, };

View File

@@ -637,7 +637,6 @@ pub struct ConfigRequirements {
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct NetworkRequirements {
pub enabled: Option<bool>,
pub http_port: Option<u16>,
pub socks_port: Option<u16>,
pub allow_upstream_proxy: Option<bool>,

View File

@@ -191,7 +191,6 @@ fn map_network_requirements_to_api(
network: codex_core::config_loader::NetworkRequirementsToml,
) -> NetworkRequirements {
NetworkRequirements {
enabled: network.enabled,
http_port: network.http_port,
socks_port: network.socks_port,
allow_upstream_proxy: network.allow_upstream_proxy,
@@ -273,7 +272,6 @@ mod tests {
rules: None,
enforce_residency: Some(CoreResidencyRequirement::Us),
network: Some(CoreNetworkRequirementsToml {
enabled: Some(true),
http_port: Some(8080),
socks_port: Some(1080),
allow_upstream_proxy: Some(false),
@@ -318,7 +316,6 @@ mod tests {
assert_eq!(
mapped.network,
Some(NetworkRequirements {
enabled: Some(true),
http_port: Some(8080),
socks_port: Some(1080),
allow_upstream_proxy: Some(false),

View File

@@ -83,7 +83,7 @@ pub struct ConfigRequirements {
pub mcp_servers: Option<Sourced<BTreeMap<String, McpServerRequirement>>>,
pub exec_policy: Option<Sourced<RequirementsExecPolicy>>,
pub enforce_residency: ConstrainedWithSource<Option<ResidencyRequirement>>,
/// Managed network constraints derived from requirements.
/// Network constraints derived from requirements.
pub network: Option<Sourced<NetworkConstraints>>,
}
@@ -130,8 +130,8 @@ pub struct McpServerRequirement {
}
#[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct NetworkRequirementsToml {
pub enabled: Option<bool>,
pub http_port: Option<u16>,
pub socks_port: Option<u16>,
pub allow_upstream_proxy: Option<bool>,
@@ -149,7 +149,6 @@ pub struct NetworkRequirementsToml {
/// Normalized network constraints derived from requirements TOML.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct NetworkConstraints {
pub enabled: Option<bool>,
pub http_port: Option<u16>,
pub socks_port: Option<u16>,
pub allow_upstream_proxy: Option<bool>,
@@ -167,7 +166,6 @@ pub struct NetworkConstraints {
impl From<NetworkRequirementsToml> for NetworkConstraints {
fn from(value: NetworkRequirementsToml) -> Self {
let NetworkRequirementsToml {
enabled,
http_port,
socks_port,
allow_upstream_proxy,
@@ -180,7 +178,6 @@ impl From<NetworkRequirementsToml> for NetworkConstraints {
allow_local_binding,
} = value;
Self {
enabled,
http_port,
socks_port,
allow_upstream_proxy,
@@ -1122,7 +1119,6 @@ mod tests {
fn network_requirements_are_preserved_as_constraints_with_source() -> Result<()> {
let toml_str = r#"
[experimental_network]
enabled = true
allow_upstream_proxy = false
dangerously_allow_all_unix_sockets = true
allowed_domains = ["api.example.com", "*.openai.com"]
@@ -1142,7 +1138,6 @@ mod tests {
.expect("network requirements should be preserved as constraints");
assert_eq!(sourced_network.source, source);
assert_eq!(sourced_network.value.enabled, Some(true));
assert_eq!(sourced_network.value.allow_upstream_proxy, Some(false));
assert_eq!(
sourced_network.value.dangerously_allow_all_unix_sockets,
@@ -1172,6 +1167,21 @@ mod tests {
Ok(())
}
#[test]
fn network_requirements_reject_enabled_flag() {
let toml_str = r#"
[experimental_network]
enabled = true
"#;
let err =
from_str::<ConfigRequirementsToml>(toml_str).expect_err("enabled flag should fail");
assert!(
err.to_string().contains("unknown field `enabled`"),
"unexpected error: {err}"
);
}
#[test]
fn deserialize_mcp_server_requirements() -> Result<()> {
let toml_str = r#"

View File

@@ -350,6 +350,9 @@
"enable_experimental_windows_sandbox": {
"type": "boolean"
},
"enable_network_proxy": {
"type": "boolean"
},
"enable_request_compression": {
"type": "boolean"
},
@@ -1833,6 +1836,9 @@
"enable_experimental_windows_sandbox": {
"type": "boolean"
},
"enable_network_proxy": {
"type": "boolean"
},
"enable_request_compression": {
"type": "boolean"
},

View File

@@ -1071,7 +1071,7 @@ impl Session {
audit_metadata,
)
.await
.map_err(|err| anyhow::anyhow!("failed to start managed network proxy: {err}"))?;
.map_err(|err| anyhow::anyhow!("failed to start network proxy: {err}"))?;
let session_network_proxy = {
let proxy = network_proxy.proxy();
SessionNetworkProxyRuntime {

View File

@@ -266,13 +266,17 @@ allowed_domains = ["openai.com"]
}
#[test]
fn permissions_profiles_network_populates_runtime_network_proxy_spec() -> std::io::Result<()> {
fn permissions_profiles_network_populates_runtime_network_proxy_spec_when_feature_enabled()
-> 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 {
features: Some(FeaturesToml {
entries: BTreeMap::from([("enable_network_proxy".to_string(), true)]),
}),
default_permissions: Some("workspace".to_string()),
permissions: Some(PermissionsToml {
entries: BTreeMap::from([(
@@ -313,7 +317,8 @@ fn permissions_profiles_network_populates_runtime_network_proxy_spec() -> std::i
}
#[test]
fn permissions_profiles_network_disabled_by_default_does_not_start_proxy() -> std::io::Result<()> {
fn permissions_profiles_network_does_not_start_proxy_without_feature_flag()
-> std::io::Result<()> {
let codex_home = TempDir::new()?;
let cwd = TempDir::new()?;
std::fs::write(cwd.path().join(".git"), "gitdir: nowhere")?;
@@ -332,6 +337,7 @@ fn permissions_profiles_network_disabled_by_default_does_not_start_proxy() -> st
)]),
}),
network: Some(NetworkToml {
enabled: Some(true),
allowed_domains: Some(vec!["openai.com".to_string()]),
..Default::default()
}),

File diff suppressed because it is too large Load Diff

View File

@@ -69,10 +69,6 @@ impl ConfigReloader for StaticNetworkProxyReloader {
}
impl NetworkProxySpec {
pub(crate) fn enabled(&self) -> bool {
self.config.network.enabled
}
pub fn proxy_host_and_port(&self) -> String {
host_and_port_from_network_addr(&self.config.network.proxy_url, 3128)
}
@@ -178,10 +174,7 @@ impl NetworkProxySpec {
Self::allowlist_expansion_enabled(sandbox_policy, hard_deny_allowlist_misses);
let denylist_expansion_enabled = Self::denylist_expansion_enabled(sandbox_policy);
if let Some(enabled) = requirements.enabled {
config.network.enabled = enabled;
constraints.enabled = Some(enabled);
}
config.network.enabled = true;
if let Some(http_port) = requirements.http_port {
config.network.proxy_url = format!("http://127.0.0.1:{http_port}");
}

View File

@@ -84,9 +84,6 @@ enum NetworkModeSchema {
impl NetworkToml {
pub(crate) fn apply_to_network_proxy_config(&self, config: &mut NetworkProxyConfig) {
if let Some(enabled) = self.enabled {
config.network.enabled = enabled;
}
if let Some(proxy_url) = self.proxy_url.as_ref() {
config.network.proxy_url = proxy_url.clone();
}

View File

@@ -150,12 +150,13 @@ fn parse_managed_config_base64(encoded: &str) -> io::Result<ManagedAdminConfigLa
}
fn parse_managed_requirements_base64(encoded: &str) -> io::Result<ConfigRequirementsToml> {
toml::from_str::<ConfigRequirementsToml>(&decode_managed_preferences_base64(encoded)?).map_err(
|err| {
tracing::error!("Failed to parse managed requirements TOML: {err}");
io::Error::new(io::ErrorKind::InvalidData, err)
},
super::parse_requirements_toml_with_legacy_network_enabled_compat(
&decode_managed_preferences_base64(encoded)?,
)
.map_err(|err| {
tracing::error!("Failed to parse managed requirements TOML: {err}");
io::Error::new(io::ErrorKind::InvalidData, err)
})
}
fn decode_managed_preferences_base64(encoded: &str) -> io::Result<String> {

View File

@@ -351,16 +351,18 @@ async fn load_requirements_toml(
AbsolutePathBuf::from_absolute_path(requirements_toml_file.as_ref())?;
match tokio::fs::read_to_string(&requirements_toml_file).await {
Ok(contents) => {
let requirements_config: ConfigRequirementsToml =
toml::from_str(&contents).map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Error parsing requirements file {}: {e}",
requirements_toml_file.as_ref().display(),
),
)
})?;
let requirements_config = parse_requirements_toml_with_legacy_network_enabled_compat(
&contents,
)
.map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Error parsing requirements file {}: {e}",
requirements_toml_file.as_ref().display(),
),
)
})?;
config_requirements_toml.merge_unset_fields(
RequirementSource::SystemRequirementsToml {
file: requirements_toml_file.clone(),
@@ -384,6 +386,44 @@ async fn load_requirements_toml(
Ok(())
}
fn parse_requirements_toml_with_legacy_network_enabled_compat(
contents: &str,
) -> Result<ConfigRequirementsToml, toml::de::Error> {
let mut raw_requirements: TomlValue = toml::from_str(contents)?;
let should_remove_network_table = raw_requirements
.as_table_mut()
.and_then(|table| table.get_mut("experimental_network"))
.and_then(TomlValue::as_table_mut)
.and_then(|network_table| network_table.remove("enabled"))
.map(|enabled| match enabled {
TomlValue::Boolean(false) => {
tracing::warn!(
"Ignoring disabled legacy experimental_network table in requirements input"
);
true
}
TomlValue::Boolean(true) => {
tracing::warn!(
"Ignoring legacy experimental_network.enabled in requirements input"
);
false
}
value => {
tracing::warn!(
?value,
"Ignoring malformed legacy experimental_network.enabled in requirements input"
);
false
}
})
.unwrap_or(false);
if should_remove_network_table && let Some(requirements_table) = raw_requirements.as_table_mut()
{
requirements_table.remove("experimental_network");
}
raw_requirements.try_into()
}
#[cfg(unix)]
fn system_requirements_toml_file() -> io::Result<AbsolutePathBuf> {
AbsolutePathBuf::from_absolute_path(Path::new("/etc/codex/requirements.toml"))

View File

@@ -440,6 +440,45 @@ allowed_sandbox_modes = ["read-only"]
Ok(())
}
#[cfg(target_os = "macos")]
#[tokio::test]
async fn managed_preferences_requirements_ignore_legacy_network_enabled() -> anyhow::Result<()> {
use base64::Engine;
let tmp = tempdir()?;
let state = load_config_layers_state(
tmp.path(),
Some(AbsolutePathBuf::try_from(tmp.path())?),
&[] as &[(String, TomlValue)],
LoaderOverrides {
managed_config_path: Some(tmp.path().join("managed_config.toml")),
managed_preferences_base64: Some(String::new()),
macos_managed_config_requirements_base64: Some(
base64::prelude::BASE64_STANDARD.encode(
r#"
[experimental_network]
enabled = true
allow_local_binding = true
"#
.as_bytes(),
),
),
},
CloudRequirementsLoader::default(),
)
.await?;
let network = state
.requirements()
.network
.as_ref()
.expect("network requirements");
assert_eq!(network.value.allow_local_binding, Some(true));
Ok(())
}
#[cfg(target_os = "macos")]
#[tokio::test]
async fn managed_preferences_requirements_take_precedence() -> anyhow::Result<()> {
@@ -633,6 +672,54 @@ allowed_approval_policies = ["on-request"]
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn load_requirements_toml_ignores_legacy_network_enabled() -> anyhow::Result<()> {
let tmp = tempdir()?;
let requirements_file = tmp.path().join("requirements.toml");
tokio::fs::write(
&requirements_file,
r#"
[experimental_network]
enabled = true
allow_local_binding = true
"#,
)
.await?;
let mut config_requirements_toml = ConfigRequirementsWithSources::default();
load_requirements_toml(&mut config_requirements_toml, &requirements_file).await?;
let network = config_requirements_toml
.network
.as_ref()
.expect("network requirements");
assert_eq!(network.value.allow_local_binding, Some(true));
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn load_requirements_toml_ignores_disabled_legacy_network_table() -> anyhow::Result<()> {
let tmp = tempdir()?;
let requirements_file = tmp.path().join("requirements.toml");
tokio::fs::write(
&requirements_file,
r#"
[experimental_network]
enabled = false
allow_local_binding = true
"#,
)
.await?;
let mut config_requirements_toml = ConfigRequirementsWithSources::default();
load_requirements_toml(&mut config_requirements_toml, &requirements_file).await?;
assert_eq!(config_requirements_toml.network, None);
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn cloud_requirements_are_not_overwritten_by_system_requirements() -> anyhow::Result<()> {
let tmp = tempdir()?;

View File

@@ -97,6 +97,8 @@ pub enum Feature {
RequestPermissions,
/// Expose the built-in request_permissions tool.
RequestPermissionsTool,
/// Start the network proxy even without explicit `[permissions.<profile>.network]` config.
EnableNetworkProxy,
/// Allow the model to request web searches that fetch live content.
WebSearchRequest,
/// Allow the model to request web searches that fetch cached content.
@@ -597,6 +599,16 @@ pub const FEATURES: &[FeatureSpec] = &[
stage: Stage::UnderDevelopment,
default_enabled: false,
},
FeatureSpec {
id: Feature::EnableNetworkProxy,
key: "enable_network_proxy",
stage: Stage::Experimental {
name: "Network proxy",
menu_description: "Start Codex's network proxy with default settings even without explicit [permissions.<profile>.network] config. Use the selected permissions profile's [network] table to override the proxy defaults.",
announcement: "NEW: Network proxy can now be enabled from /experimental. Restart Codex after enabling it.",
},
default_enabled: false,
},
FeatureSpec {
id: Feature::UseLinuxSandboxBwrap,
key: "use_linux_sandbox_bwrap",
@@ -960,6 +972,15 @@ mod tests {
assert_eq!(Feature::ImageGeneration.default_enabled(), false);
}
#[test]
fn enable_network_proxy_is_experimental() {
assert!(matches!(
Feature::EnableNetworkProxy.stage(),
Stage::Experimental { .. }
));
assert_eq!(Feature::EnableNetworkProxy.default_enabled(), false);
}
#[test]
fn collab_is_legacy_alias_for_multi_agent() {
assert_eq!(feature_for_key("multi_agent"), Some(Feature::Collab));

View File

@@ -357,7 +357,6 @@ fn guardian_subagent_config_preserves_parent_network_proxy() {
let network = NetworkProxySpec::from_config_and_constraints(
NetworkProxyConfig::default(),
Some(NetworkConstraints {
enabled: Some(true),
allowed_domains: Some(vec!["github.com".to_string()]),
..Default::default()
}),

View File

@@ -81,7 +81,7 @@ pub(crate) fn denied_network_policy_message(blocked: &BlockedRequest) -> Option<
"not_allowed" => "domain is not on the allowlist for the current sandbox mode",
"not_allowed_local" => "local/private network addresses are blocked by policy",
"method_not_allowed" => "request method is blocked by the current network mode",
"proxy_disabled" => "managed network proxy is disabled",
"proxy_disabled" => "network proxy is disabled",
_ => "request is blocked by network policy",
};

View File

@@ -127,9 +127,6 @@ fn network_constraints_from_trusted_layers(
}
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);
}
@@ -348,6 +345,36 @@ allowed_domains = ["higher.example.com"]
assert_eq!(config.network.allowed_domains, vec!["higher.example.com"]);
}
#[test]
fn top_level_network_table_is_ignored() {
let lower_permissions: toml::Value = toml::from_str(
r#"
default_permissions = "workspace"
[permissions.workspace.network]
allowed_domains = ["lower.example.com"]
"#,
)
.expect("lower layer should parse");
let higher_network: toml::Value =
toml::from_str(r#"network = { allowed_domains = ["higher.example.com"] }"#)
.expect("higher layer should parse");
let mut config = NetworkProxyConfig::default();
apply_network_tables(
&mut config,
network_tables_from_toml(&lower_permissions).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.network.allowed_domains, vec!["lower.example.com"]);
}
#[test]
fn execpolicy_network_rules_overlay_network_lists() {
let mut config = NetworkProxyConfig::default();

View File

@@ -2236,11 +2236,13 @@ async fn denying_network_policy_amendment_persists_policy_and_skips_future_netwo
home.path().join("config.toml"),
r#"default_permissions = "workspace"
[features]
enable_network_proxy = true
[permissions.workspace.filesystem]
":minimal" = "read"
[permissions.workspace.network]
enabled = true
mode = "limited"
allow_local_binding = true
"#,
@@ -2266,7 +2268,6 @@ allow_local_binding = true
let mut requirements = config.config_layer_stack.requirements().clone();
requirements.network = Some(Sourced::new(
NetworkConstraints {
enabled: Some(true),
allow_local_binding: Some(true),
..Default::default()
},
@@ -2274,7 +2275,6 @@ allow_local_binding = true
));
let mut requirements_toml = config.config_layer_stack.requirements_toml().clone();
requirements_toml.network = Some(NetworkRequirementsToml {
enabled: Some(true),
allow_local_binding: Some(true),
..Default::default()
});

View File

@@ -18,14 +18,20 @@ Network settings live under the selected permissions profile. Example config:
```toml
default_permissions = "workspace"
[features]
enable_network_proxy = true
[permissions.workspace.network]
# `enabled` controls sandbox network access for the selected profile.
# The proxy itself starts when `enable_network_proxy` or requirements enable it.
enabled = true
proxy_url = "http://127.0.0.1:3128"
# SOCKS5 listener (enabled by default).
enable_socks5 = true
socks_url = "http://127.0.0.1:8081"
enable_socks5_udp = true
# When `enabled` is false, the proxy no-ops and does not bind listeners.
# Use the `enable_network_proxy` experimental feature or requirements config
# to activate the proxy when running through Codex.
# When true, respect HTTP(S)_PROXY/ALL_PROXY for upstream requests (HTTP(S) proxies only),
# including CONNECT tunnels in full mode.
allow_upstream_proxy = true
@@ -205,7 +211,8 @@ what it can reasonably guarantee.
proxy into a remote bridge into local daemons.
- `dangerously_allow_all_unix_sockets = true` bypasses the unix socket allowlist entirely (still
macOS-only and absolute-path-only). Use only in tightly controlled environments.
- `enabled` is enforced at runtime; when false the proxy no-ops and does not bind listeners.
- Proxy startup is gated by `enable_network_proxy` or requirements, not by
`[permissions.<profile>.network].enabled`.
Limitations:
- DNS rebinding is hard to fully prevent without pinning the resolved IP(s) all the way down to the

View File

@@ -324,7 +324,6 @@ fn format_network_constraints(network: &NetworkConstraints) -> String {
let mut parts = Vec::new();
let NetworkConstraints {
enabled,
http_port,
socks_port,
allow_upstream_proxy,
@@ -337,9 +336,6 @@ fn format_network_constraints(network: &NetworkConstraints) -> String {
allow_local_binding,
} = network;
if let Some(enabled) = enabled {
parts.push(format!("enabled={enabled}"));
}
if let Some(http_port) = http_port {
parts.push(format!("http_port={http_port}"));
}
@@ -512,7 +508,6 @@ mod tests {
),
network: Some(Sourced::new(
NetworkConstraints {
enabled: Some(true),
allowed_domains: Some(vec!["example.com".to_string()]),
..Default::default()
},
@@ -575,7 +570,7 @@ mod tests {
assert!(rendered.contains("mcp_servers: docs (source: MDM managed_config.toml (legacy))"));
assert!(rendered.contains("enforce_residency: us (source: cloud requirements)"));
assert!(rendered.contains(
"experimental_network: enabled=true, allowed_domains=[example.com] (source: cloud requirements)"
"experimental_network: allowed_domains=[example.com] (source: cloud requirements)"
));
assert!(!rendered.contains(" - rules:"));
}