Add yolo blocklist-only network mode

- Add yolo_only_enforce_blocklist to user config, requirements, API schemas, and debug output.

- Make full-access proxy runtime enforce denylist-only mode while preserving deny precedence.

- Prevent managed allowlist-only requirements from being bypassed by user blocklist-only yolo settings.

- Keep managed-config ignore helper test-only so release builds do not ship an env-var bypass.
This commit is contained in:
rreichel3-oai
2026-03-24 10:01:44 -04:00
parent 18f1a08bc9
commit 274dd16057
21 changed files with 299 additions and 12 deletions
@@ -9133,6 +9133,12 @@
"integer",
"null"
]
},
"yoloOnlyEnforceBlocklist": {
"type": [
"boolean",
"null"
]
}
},
"type": "object"
@@ -5881,6 +5881,12 @@
"integer",
"null"
]
},
"yoloOnlyEnforceBlocklist": {
"type": [
"boolean",
"null"
]
}
},
"type": "object"
@@ -176,6 +176,12 @@
"integer",
"null"
]
},
"yoloOnlyEnforceBlocklist": {
"type": [
"boolean",
"null"
]
}
},
"type": "object"
@@ -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 = { enabled: boolean | null, httpPort: number | null, socksPort: number | null, allowUpstreamProxy: boolean | null, dangerouslyAllowNonLoopbackProxy: boolean | null, dangerouslyAllowAllUnixSockets: boolean | null, yoloOnlyEnforceBlocklist: boolean | null, allowedDomains: Array<string> | null, deniedDomains: Array<string> | null, allowUnixSockets: Array<string> | null, allowLocalBinding: boolean | null, };
@@ -871,6 +871,7 @@ pub struct NetworkRequirements {
pub allow_upstream_proxy: Option<bool>,
pub dangerously_allow_non_loopback_proxy: Option<bool>,
pub dangerously_allow_all_unix_sockets: Option<bool>,
pub yolo_only_enforce_blocklist: Option<bool>,
pub allowed_domains: Option<Vec<String>>,
pub denied_domains: Option<Vec<String>>,
pub allow_unix_sockets: Option<Vec<String>>,
+3
View File
@@ -231,6 +231,7 @@ fn map_network_requirements_to_api(
allow_upstream_proxy: network.allow_upstream_proxy,
dangerously_allow_non_loopback_proxy: network.dangerously_allow_non_loopback_proxy,
dangerously_allow_all_unix_sockets: network.dangerously_allow_all_unix_sockets,
yolo_only_enforce_blocklist: network.yolo_only_enforce_blocklist,
allowed_domains: network.allowed_domains,
denied_domains: network.denied_domains,
allow_unix_sockets: network.allow_unix_sockets,
@@ -316,6 +317,7 @@ mod tests {
allow_upstream_proxy: Some(false),
dangerously_allow_non_loopback_proxy: Some(false),
dangerously_allow_all_unix_sockets: Some(true),
yolo_only_enforce_blocklist: Some(true),
allowed_domains: Some(vec!["api.openai.com".to_string()]),
managed_allowed_domains_only: Some(false),
denied_domains: Some(vec!["example.com".to_string()]),
@@ -361,6 +363,7 @@ mod tests {
allow_upstream_proxy: Some(false),
dangerously_allow_non_loopback_proxy: Some(false),
dangerously_allow_all_unix_sockets: Some(true),
yolo_only_enforce_blocklist: Some(true),
allowed_domains: Some(vec!["api.openai.com".to_string()]),
denied_domains: Some(vec!["example.com".to_string()]),
allow_unix_sockets: Some(vec!["/tmp/proxy.sock".to_string()]),
@@ -140,6 +140,9 @@ pub struct NetworkRequirementsToml {
pub allow_upstream_proxy: Option<bool>,
pub dangerously_allow_non_loopback_proxy: Option<bool>,
pub dangerously_allow_all_unix_sockets: Option<bool>,
/// When true, yolo/full-access sandbox mode only enforces `denied_domains`
/// and allows all other requests through.
pub yolo_only_enforce_blocklist: Option<bool>,
pub allowed_domains: Option<Vec<String>>,
/// When true, only managed `allowed_domains` are respected while managed
/// network enforcement is active. User allowlist entries are ignored.
@@ -158,6 +161,9 @@ pub struct NetworkConstraints {
pub allow_upstream_proxy: Option<bool>,
pub dangerously_allow_non_loopback_proxy: Option<bool>,
pub dangerously_allow_all_unix_sockets: Option<bool>,
/// When true, yolo/full-access sandbox mode only enforces `denied_domains`
/// and allows all other requests through.
pub yolo_only_enforce_blocklist: Option<bool>,
pub allowed_domains: Option<Vec<String>>,
/// When true, only managed `allowed_domains` are respected while managed
/// network enforcement is active. User allowlist entries are ignored.
@@ -176,6 +182,7 @@ impl From<NetworkRequirementsToml> for NetworkConstraints {
allow_upstream_proxy,
dangerously_allow_non_loopback_proxy,
dangerously_allow_all_unix_sockets,
yolo_only_enforce_blocklist,
allowed_domains,
managed_allowed_domains_only,
denied_domains,
@@ -189,6 +196,7 @@ impl From<NetworkRequirementsToml> for NetworkConstraints {
allow_upstream_proxy,
dangerously_allow_non_loopback_proxy,
dangerously_allow_all_unix_sockets,
yolo_only_enforce_blocklist,
allowed_domains,
managed_allowed_domains_only,
denied_domains,
+4
View File
@@ -955,6 +955,10 @@
},
"socks_url": {
"type": "string"
},
"yolo_only_enforce_blocklist": {
"description": "Only applies in yolo/full-access sandbox mode. When enabled, the network proxy enforces `denied_domains` but allows all other requests through.",
"type": "boolean"
}
},
"type": "object"
+1
View File
@@ -319,6 +319,7 @@ allowed_domains = ["openai.com"]
dangerously_allow_non_loopback_proxy: None,
dangerously_allow_all_unix_sockets: None,
mode: None,
yolo_only_enforce_blocklist: None,
allowed_domains: Some(vec!["openai.com".to_string()]),
denied_domains: None,
allow_unix_sockets: None,
+16 -1
View File
@@ -92,7 +92,7 @@ impl NetworkProxySpec {
let hard_deny_allowlist_misses = requirements
.as_ref()
.is_some_and(Self::managed_allowed_domains_only);
let (config, constraints) = if let Some(requirements) = requirements {
let (mut config, constraints) = if let Some(requirements) = requirements {
Self::apply_requirements(
config,
&requirements,
@@ -102,6 +102,12 @@ impl NetworkProxySpec {
} else {
(config, NetworkProxyConstraints::default())
};
// The blocklist-only escape hatch is intentionally scoped to yolo mode,
// regardless of whether it came from user config or managed requirements.
if !matches!(sandbox_policy, SandboxPolicy::DangerFullAccess) {
config.network.yolo_only_enforce_blocklist = false;
}
validate_policy_against_constraints(&config, &constraints).map_err(|err| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
@@ -225,6 +231,15 @@ impl NetworkProxySpec {
constraints.dangerously_allow_all_unix_sockets =
Some(dangerously_allow_all_unix_sockets);
}
if let Some(yolo_only_enforce_blocklist) = requirements.yolo_only_enforce_blocklist {
config.network.yolo_only_enforce_blocklist = yolo_only_enforce_blocklist;
constraints.yolo_only_enforce_blocklist = Some(yolo_only_enforce_blocklist);
} else if hard_deny_allowlist_misses {
// Managed allowlist-only mode must not be bypassable by a user
// opting into blocklist-only yolo behavior.
config.network.yolo_only_enforce_blocklist = false;
constraints.yolo_only_enforce_blocklist = Some(false);
}
let managed_allowed_domains = if hard_deny_allowlist_misses {
Some(requirements.allowed_domains.clone().unwrap_or_default())
} else {
@@ -175,6 +175,89 @@ fn managed_allowed_domains_only_blocks_all_user_domains_in_full_access_without_m
assert!(spec.hard_deny_allowlist_misses);
}
#[test]
fn yolo_only_enforce_blocklist_is_ignored_outside_full_access() {
let mut config = NetworkProxyConfig::default();
config.network.yolo_only_enforce_blocklist = true;
let spec = NetworkProxySpec::from_config_and_constraints(
config,
None,
&SandboxPolicy::new_workspace_write_policy(),
)
.expect("non-yolo modes should load with blocklist-only disabled");
assert!(!spec.config.network.yolo_only_enforce_blocklist);
}
#[test]
fn requirements_can_enable_yolo_only_enforce_blocklist_in_full_access() {
let requirements = NetworkConstraints {
yolo_only_enforce_blocklist: Some(true),
denied_domains: Some(vec!["blocked.example.com".to_string()]),
..Default::default()
};
let spec = NetworkProxySpec::from_config_and_constraints(
NetworkProxyConfig::default(),
Some(requirements),
&SandboxPolicy::DangerFullAccess,
)
.expect("requirements blocklist-only mode should load in yolo");
assert!(spec.config.network.yolo_only_enforce_blocklist);
assert_eq!(spec.constraints.yolo_only_enforce_blocklist, Some(true));
assert_eq!(
spec.config.network.denied_domains,
vec!["blocked.example.com".to_string()]
);
}
#[test]
fn requirements_yolo_only_enforce_blocklist_is_ignored_outside_full_access() {
let requirements = NetworkConstraints {
yolo_only_enforce_blocklist: Some(true),
..Default::default()
};
let spec = NetworkProxySpec::from_config_and_constraints(
NetworkProxyConfig::default(),
Some(requirements),
&SandboxPolicy::new_workspace_write_policy(),
)
.expect("requirements blocklist-only should be disabled outside yolo");
assert!(!spec.config.network.yolo_only_enforce_blocklist);
}
#[test]
fn managed_allowed_domains_only_disables_user_blocklist_only_yolo_without_explicit_requirement() {
let mut config = NetworkProxyConfig::default();
config.network.yolo_only_enforce_blocklist = true;
config.network.allowed_domains = vec!["user.example.com".to_string()];
let requirements = NetworkConstraints {
allowed_domains: Some(vec!["managed.example.com".to_string()]),
managed_allowed_domains_only: Some(true),
..Default::default()
};
let spec = NetworkProxySpec::from_config_and_constraints(
config,
Some(requirements),
&SandboxPolicy::DangerFullAccess,
)
.expect("managed allowlist-only should disable user blocklist-only yolo");
assert!(!spec.config.network.yolo_only_enforce_blocklist);
assert_eq!(spec.constraints.yolo_only_enforce_blocklist, Some(false));
assert_eq!(
spec.config.network.allowed_domains,
vec!["managed.example.com".to_string()]
);
assert!(spec.hard_deny_allowlist_misses);
}
#[test]
fn requirements_denied_domains_are_a_baseline_for_default_mode() {
let mut config = NetworkProxyConfig::default();
+6
View File
@@ -69,6 +69,9 @@ pub struct NetworkToml {
pub dangerously_allow_all_unix_sockets: Option<bool>,
#[schemars(with = "Option<NetworkModeSchema>")]
pub mode: Option<NetworkMode>,
/// Only applies in yolo/full-access sandbox mode. When enabled, the network
/// proxy enforces `denied_domains` but allows all other requests through.
pub yolo_only_enforce_blocklist: Option<bool>,
pub allowed_domains: Option<Vec<String>>,
pub denied_domains: Option<Vec<String>>,
pub allow_unix_sockets: Option<Vec<String>>,
@@ -114,6 +117,9 @@ impl NetworkToml {
if let Some(mode) = self.mode {
config.network.mode = mode;
}
if let Some(yolo_only_enforce_blocklist) = self.yolo_only_enforce_blocklist {
config.network.yolo_only_enforce_blocklist = yolo_only_enforce_blocklist;
}
if let Some(allowed_domains) = self.allowed_domains.as_ref() {
config.network.allowed_domains = allowed_domains.clone();
}
@@ -39,6 +39,18 @@ pub(super) async fn load_config_layers_internal(
codex_home: &Path,
overrides: LoaderOverrides,
) -> io::Result<LoadedConfigLayers> {
if super::should_ignore_managed_config() {
#[cfg(test)]
tracing::info!(
"Ignoring managed config layers because {} is set",
super::CODEX_IGNORE_MANAGED_CONFIG_ENV_VAR
);
return Ok(LoadedConfigLayers {
managed_config: None,
managed_config_from_mdm: None,
});
}
#[cfg(target_os = "macos")]
let LoaderOverrides {
managed_config_path,
+28 -7
View File
@@ -68,6 +68,20 @@ pub const SYSTEM_CONFIG_TOML_FILE_UNIX: &str = "/etc/codex/config.toml";
const DEFAULT_PROGRAM_DATA_DIR_WINDOWS: &str = r"C:\ProgramData";
const DEFAULT_PROJECT_ROOT_MARKERS: &[&str] = &[".git"];
#[cfg(test)]
/// Local test escape hatch for ignoring managed admin configuration on
/// machines with MDM or system managed config installed.
pub(super) const CODEX_IGNORE_MANAGED_CONFIG_ENV_VAR: &str = "CODEX_IGNORE_MANAGED_CONFIG";
#[cfg(test)]
pub(super) fn should_ignore_managed_config() -> bool {
std::env::var_os(CODEX_IGNORE_MANAGED_CONFIG_ENV_VAR).is_some()
}
#[cfg(not(test))]
pub(super) fn should_ignore_managed_config() -> bool {
false
}
pub(crate) async fn first_layer_config_error(layers: &ConfigLayerStack) -> Option<ConfigError> {
codex_config::first_layer_config_error::<ConfigToml>(layers, CONFIG_TOML_FILE).await
@@ -126,13 +140,20 @@ pub async fn load_config_layers_state(
}
#[cfg(target_os = "macos")]
macos::load_managed_admin_requirements_toml(
&mut config_requirements_toml,
overrides
.macos_managed_config_requirements_base64
.as_deref(),
)
.await?;
if should_ignore_managed_config() {
#[cfg(test)]
tracing::info!(
"Ignoring managed requirements from MDM because {CODEX_IGNORE_MANAGED_CONFIG_ENV_VAR} is set"
);
} else {
macos::load_managed_admin_requirements_toml(
&mut config_requirements_toml,
overrides
.macos_managed_config_requirements_base64
.as_deref(),
)
.await?;
}
// Honor the system requirements.toml location.
let requirements_toml_file = system_requirements_toml_file()?;
+38
View File
@@ -12,6 +12,7 @@ use crate::config_loader::ConfigLoadError;
use crate::config_loader::ConfigRequirements;
use crate::config_loader::ConfigRequirementsToml;
use crate::config_loader::ConfigRequirementsWithSources;
use crate::config_loader::NetworkRequirementsToml;
use crate::config_loader::RequirementSource;
use crate::config_loader::load_requirements_toml;
use crate::config_loader::version_for_toml;
@@ -577,6 +578,43 @@ personality = true
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn load_requirements_toml_parses_yolo_only_enforce_blocklist() -> anyhow::Result<()> {
let tmp = tempdir()?;
let requirements_file = tmp.path().join("requirements.toml");
tokio::fs::write(
&requirements_file,
r#"
[experimental_network]
yolo_only_enforce_blocklist = true
denied_domains = ["blocked.example.com"]
"#,
)
.await?;
let mut config_requirements_toml = ConfigRequirementsWithSources::default();
load_requirements_toml(&mut config_requirements_toml, &requirements_file).await?;
assert_eq!(
config_requirements_toml.network.as_deref().cloned(),
Some(NetworkRequirementsToml {
enabled: None,
http_port: None,
socks_port: None,
allow_upstream_proxy: None,
dangerously_allow_non_loopback_proxy: None,
dangerously_allow_all_unix_sockets: None,
yolo_only_enforce_blocklist: Some(true),
allowed_domains: None,
managed_allowed_domains_only: None,
denied_domains: Some(vec!["blocked.example.com".to_string()]),
allow_unix_sockets: None,
allow_local_binding: None,
})
);
Ok(())
}
#[cfg(target_os = "macos")]
#[tokio::test]
async fn cloud_requirements_take_precedence_over_mdm_requirements() -> anyhow::Result<()> {
+6
View File
@@ -33,6 +33,9 @@ allow_upstream_proxy = true
# If you want to expose these listeners beyond localhost, you must opt in explicitly.
dangerously_allow_non_loopback_proxy = false
mode = "full" # default when unset; use "limited" for read-only mode
# In yolo/full-access sandbox mode only: ignore the allowlist and local/private-network guard,
# enforcing only `denied_domains`.
yolo_only_enforce_blocklist = false
# When true, HTTPS CONNECT can be terminated so limited-mode method policy still applies.
mitm = false
# CA cert/key are managed internally under $CODEX_HOME/proxy/ (ca.pem + ca.key).
@@ -191,6 +194,9 @@ what it can reasonably guarantee.
- Allowlist-first policy: if `allowed_domains` is empty, requests are blocked until an allowlist is configured.
- Domain patterns: exact hosts plus scoped wildcards (`*.example.com`, `**.example.com`) are supported; the global `*` wildcard is rejected.
- Deny wins: entries in `denied_domains` always override the allowlist.
- YOLO blocklist-only mode: with `yolo_only_enforce_blocklist = true` in full-access sandbox mode,
the proxy allows requests that are not explicitly denied, including hosts outside the allowlist
and local/private destinations. Use only when that broader egress is acceptable.
- Local/private network protection: when `allow_local_binding = false`, the proxy blocks loopback
and common private/link-local ranges. Explicit allowlisting of local IP literals (or `localhost`)
is required to permit them; hostnames that resolve to local/private IPs are still blocked even if
+6
View File
@@ -34,6 +34,10 @@ pub struct NetworkProxySettings {
pub dangerously_allow_all_unix_sockets: bool,
#[serde(default)]
pub mode: NetworkMode,
/// In yolo/full-access sandbox mode, ignore the allowlist and local-network
/// guard so only `denied_domains` remains enforced.
#[serde(default)]
pub yolo_only_enforce_blocklist: bool,
#[serde(default)]
pub allowed_domains: Vec<String>,
#[serde(default)]
@@ -57,6 +61,7 @@ impl Default for NetworkProxySettings {
dangerously_allow_non_loopback_proxy: false,
dangerously_allow_all_unix_sockets: false,
mode: NetworkMode::default(),
yolo_only_enforce_blocklist: false,
allowed_domains: Vec::new(),
denied_domains: Vec::new(),
allow_unix_sockets: Vec::new(),
@@ -371,6 +376,7 @@ mod tests {
dangerously_allow_non_loopback_proxy: false,
dangerously_allow_all_unix_sockets: false,
mode: NetworkMode::Full,
yolo_only_enforce_blocklist: false,
allowed_domains: Vec::new(),
denied_domains: Vec::new(),
allow_unix_sockets: Vec::new(),
+37 -3
View File
@@ -340,12 +340,20 @@ impl NetworkProxyState {
Ok(host) => host,
Err(_) => return Ok(HostBlockDecision::Blocked(HostBlockReason::NotAllowed)),
};
let (deny_set, allow_set, allow_local_binding, allowed_domains_empty, allowed_domains) = {
let (
deny_set,
allow_set,
allow_local_binding,
yolo_only_enforce_blocklist,
allowed_domains_empty,
allowed_domains,
) = {
let guard = self.state.read().await;
(
guard.deny_set.clone(),
guard.allow_set.clone(),
guard.config.network.allow_local_binding,
guard.config.network.yolo_only_enforce_blocklist,
guard.config.network.allowed_domains.is_empty(),
guard.config.network.allowed_domains.clone(),
)
@@ -355,11 +363,15 @@ impl NetworkProxyState {
// Decision order matters:
// 1) explicit deny always wins
// 2) local/private networking is opt-in (defense-in-depth)
// 3) allowlist is enforced when configured
// 2) yolo blocklist-only mode bypasses allowlist and local/private guards
// 3) local/private networking is opt-in (defense-in-depth)
// 4) allowlist is enforced when configured
if deny_set.is_match(host_str) {
return Ok(HostBlockDecision::Blocked(HostBlockReason::Denied));
}
if yolo_only_enforce_blocklist {
return Ok(HostBlockDecision::Allowed);
}
let is_allowlisted = allow_set.is_match(host_str);
if !allow_local_binding {
@@ -858,6 +870,28 @@ mod tests {
);
}
#[tokio::test]
async fn host_blocked_yolo_only_enforce_blocklist_allows_non_denied_hosts_without_allowlist() {
let state = network_proxy_state_for_policy(NetworkProxySettings {
yolo_only_enforce_blocklist: true,
denied_domains: vec!["blocked.example.com".to_string()],
..NetworkProxySettings::default()
});
assert_eq!(
state.host_blocked("blocked.example.com", 80).await.unwrap(),
HostBlockDecision::Blocked(HostBlockReason::Denied)
);
assert_eq!(
state.host_blocked("8.8.8.8", 80).await.unwrap(),
HostBlockDecision::Allowed
);
assert_eq!(
state.host_blocked("127.0.0.1", 80).await.unwrap(),
HostBlockDecision::Allowed
);
}
#[tokio::test]
async fn add_allowed_domain_removes_matching_deny_entry() {
let state = network_proxy_state_for_policy(NetworkProxySettings {
+19
View File
@@ -23,6 +23,7 @@ pub struct NetworkProxyConstraints {
pub allow_upstream_proxy: Option<bool>,
pub dangerously_allow_non_loopback_proxy: Option<bool>,
pub dangerously_allow_all_unix_sockets: Option<bool>,
pub yolo_only_enforce_blocklist: Option<bool>,
pub allowed_domains: Option<Vec<String>>,
pub allowlist_expansion_enabled: Option<bool>,
pub denied_domains: Option<Vec<String>>,
@@ -44,6 +45,7 @@ pub struct PartialNetworkConfig {
pub allow_upstream_proxy: Option<bool>,
pub dangerously_allow_non_loopback_proxy: Option<bool>,
pub dangerously_allow_all_unix_sockets: Option<bool>,
pub yolo_only_enforce_blocklist: Option<bool>,
#[serde(default)]
pub allowed_domains: Option<Vec<String>>,
#[serde(default)]
@@ -193,6 +195,23 @@ pub fn validate_policy_against_constraints(
},
)?;
if let Some(yolo_only_enforce_blocklist) = constraints.yolo_only_enforce_blocklist {
validate(
config.network.yolo_only_enforce_blocklist,
move |candidate| {
if *candidate && !yolo_only_enforce_blocklist {
Err(invalid_value(
"network.yolo_only_enforce_blocklist",
"true",
"false (disabled by managed config)",
))
} else {
Ok(())
}
},
)?;
}
if let Some(allow_local_binding) = constraints.allow_local_binding {
validate(config.network.allow_local_binding, move |candidate| {
if *candidate && !allow_local_binding {
+6
View File
@@ -333,6 +333,7 @@ fn format_network_constraints(network: &NetworkConstraints) -> String {
allow_upstream_proxy,
dangerously_allow_non_loopback_proxy,
dangerously_allow_all_unix_sockets,
yolo_only_enforce_blocklist,
allowed_domains,
managed_allowed_domains_only,
denied_domains,
@@ -362,6 +363,11 @@ fn format_network_constraints(network: &NetworkConstraints) -> String {
"dangerously_allow_all_unix_sockets={dangerously_allow_all_unix_sockets}"
));
}
if let Some(yolo_only_enforce_blocklist) = yolo_only_enforce_blocklist {
parts.push(format!(
"yolo_only_enforce_blocklist={yolo_only_enforce_blocklist}"
));
}
if let Some(allowed_domains) = allowed_domains {
parts.push(format!("allowed_domains=[{}]", allowed_domains.join(", ")));
}
@@ -333,6 +333,7 @@ fn format_network_constraints(network: &NetworkConstraints) -> String {
allow_upstream_proxy,
dangerously_allow_non_loopback_proxy,
dangerously_allow_all_unix_sockets,
yolo_only_enforce_blocklist,
allowed_domains,
managed_allowed_domains_only,
denied_domains,
@@ -362,6 +363,11 @@ fn format_network_constraints(network: &NetworkConstraints) -> String {
"dangerously_allow_all_unix_sockets={dangerously_allow_all_unix_sockets}"
));
}
if let Some(yolo_only_enforce_blocklist) = yolo_only_enforce_blocklist {
parts.push(format!(
"yolo_only_enforce_blocklist={yolo_only_enforce_blocklist}"
));
}
if let Some(allowed_domains) = allowed_domains {
parts.push(format!("allowed_domains=[{}]", allowed_domains.join(", ")));
}