mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
## Why
Permission profiles that extend a built-in profile should behave like
other TOML inheritance: parent entries provide defaults, and child keys
override matching fields before the profile is compiled.
That was not true for `:workspace`. Previously, a profile with `extends
= ":workspace"` seeded the compiled runtime
`PermissionProfile::workspace_write()` policy and then appended child
filesystem entries. A child override such as `":tmpdir" = "read"`
therefore left the inherited `":tmpdir" = "write"` entry in the final
policy. Since same-target `write` wins over `read` during runtime
resolution, the child override was ineffective.
This also needs a clear source of truth for the built-in profiles. The
protocol-level sandbox policy constructors now define the raw built-in
filesystem entries, and both `PermissionProfile` presets and
config-profile inheritance derive from those same values.
## What Changed
- Add a canonical `FileSystemSandboxPolicy::read_only()` constructor
while keeping the read-only and workspace-write raw filesystem entries
explicit and independent.
- Derive `PermissionProfile::read_only()` from
`FileSystemSandboxPolicy::read_only()`;
`PermissionProfile::workspace_write()` continues to derive from
`FileSystemSandboxPolicy::workspace_write()`.
- Build extensible `:read-only` and `:workspace` parent profiles by
projecting those canonical sandbox policies into
`PermissionProfileToml`, then merge user overrides at the TOML layer
before compilation.
- Add config parsing support for `:slash_tmp` so the built-in
`:workspace` parent can be expressed in the same TOML-shaped filesystem
table as user profiles.
- Document that `PermissionsToml::resolve_profile()` returns an
already-merged `PermissionProfileToml`, and return that profile directly
after removing the resolved-profile wrapper.
- Extend the config test for `extends = ":workspace"` to assert that
inherited `":slash_tmp" = "write"` is preserved and that a child
`":tmpdir" = "read"` entry replaces the inherited `write` entry.
## Verification
- `just test -p codex-config`
- `just test -p codex-protocol`
- `just test -p codex-core
permissions_profiles_resolve_extends_parent_first_with_child_overrides`
- `just test -p codex-core
default_permissions_profile_can_extend_builtin_workspace`
- `just test -p codex-core`
- Result: 2596 passed, 4 failed, 1 timed out.
- The failures were existing sandbox/environment-sensitive tests
unrelated to this permissions change:
`suite::user_shell_cmd::user_shell_command_does_not_set_network_sandbox_env_var`,
`suite::user_shell_cmd::user_shell_command_history_is_persisted_and_shared_with_model`,
`suite::abort_tasks::interrupt_persists_turn_aborted_marker_in_next_request`,
`suite::abort_tasks::interrupt_tool_records_history_entries`, and
`thread_manager::tests::start_thread_uses_all_default_environments_from_codex_home`.
636 lines
22 KiB
Rust
636 lines
22 KiB
Rust
use std::collections::BTreeMap;
|
|
|
|
use crate::merge::merge_toml_values;
|
|
use codex_network_proxy::InjectedHeaderConfig;
|
|
use codex_network_proxy::MitmHookActionsConfig;
|
|
use codex_network_proxy::MitmHookBodyConfig;
|
|
use codex_network_proxy::MitmHookConfig;
|
|
use codex_network_proxy::MitmHookMatchConfig;
|
|
use codex_network_proxy::NetworkDomainPermission as ProxyNetworkDomainPermission;
|
|
use codex_network_proxy::NetworkMode;
|
|
use codex_network_proxy::NetworkProxyConfig;
|
|
use codex_network_proxy::NetworkUnixSocketPermission as ProxyNetworkUnixSocketPermission;
|
|
use codex_network_proxy::normalize_host;
|
|
use codex_protocol::permissions::FileSystemAccessMode;
|
|
use indexmap::IndexMap;
|
|
use schemars::JsonSchema;
|
|
use serde::Deserialize;
|
|
use serde::Serialize;
|
|
use thiserror::Error;
|
|
use toml::Value as TomlValue;
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
|
pub struct PermissionsToml {
|
|
#[serde(flatten)]
|
|
pub entries: BTreeMap<String, PermissionProfileToml>,
|
|
}
|
|
|
|
impl PermissionsToml {
|
|
pub fn is_empty(&self) -> bool {
|
|
self.entries.is_empty()
|
|
}
|
|
|
|
/// Resolve `profile_name` and all of its `extends` ancestors into one TOML
|
|
/// profile.
|
|
///
|
|
/// Parent profiles are merged before their children, so child keys override
|
|
/// matching parent keys before callers compile the profile into runtime
|
|
/// permissions. The returned profile keeps the selected profile's
|
|
/// declaration metadata, such as `description` and `extends`.
|
|
pub fn resolve_profile<F>(
|
|
&self,
|
|
profile_name: &str,
|
|
mut parent_profile: F,
|
|
) -> Result<PermissionProfileToml, PermissionProfileResolutionError>
|
|
where
|
|
F: FnMut(&str) -> Option<PermissionProfileToml>,
|
|
{
|
|
let mut profile_names = Vec::new();
|
|
let mut profiles = Vec::new();
|
|
let mut next_profile_name = profile_name.to_string();
|
|
let mut referenced_by: Option<String> = None;
|
|
|
|
loop {
|
|
if let Some(cycle_start) = profile_names
|
|
.iter()
|
|
.position(|name| name == &next_profile_name)
|
|
{
|
|
let cycle = profile_names[cycle_start..]
|
|
.iter()
|
|
.cloned()
|
|
.chain(std::iter::once(next_profile_name))
|
|
.collect::<Vec<_>>();
|
|
return Err(PermissionProfileResolutionError::Cycle { cycle });
|
|
}
|
|
|
|
let profile = self
|
|
.entries
|
|
.get(&next_profile_name)
|
|
.cloned()
|
|
.or_else(|| parent_profile(&next_profile_name))
|
|
.ok_or_else(|| {
|
|
referenced_by.as_deref().map_or_else(
|
|
|| PermissionProfileResolutionError::UndefinedProfile {
|
|
profile_name: next_profile_name.clone(),
|
|
},
|
|
|referenced_by| {
|
|
if next_profile_name.starts_with(':') {
|
|
PermissionProfileResolutionError::UnsupportedBuiltInParent {
|
|
profile_name: referenced_by.to_string(),
|
|
parent_profile_name: next_profile_name.clone(),
|
|
}
|
|
} else {
|
|
PermissionProfileResolutionError::UndefinedParent {
|
|
profile_name: referenced_by.to_string(),
|
|
parent_profile_name: next_profile_name.clone(),
|
|
}
|
|
}
|
|
},
|
|
)
|
|
})?;
|
|
let parent_profile_name = profile.extends.clone();
|
|
|
|
profile_names.push(next_profile_name.clone());
|
|
|
|
if let Some(parent_profile_name) = parent_profile_name {
|
|
profiles.push(profile);
|
|
referenced_by = Some(next_profile_name);
|
|
next_profile_name = parent_profile_name;
|
|
continue;
|
|
}
|
|
|
|
let profile = profiles
|
|
.into_iter()
|
|
.rev()
|
|
.try_fold(profile, merge_permission_profiles)?;
|
|
return Ok(profile);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
|
#[schemars(deny_unknown_fields)]
|
|
pub struct PermissionProfileToml {
|
|
pub description: Option<String>,
|
|
pub extends: Option<String>,
|
|
pub workspace_roots: Option<WorkspaceRootsToml>,
|
|
pub filesystem: Option<FilesystemPermissionsToml>,
|
|
pub network: Option<NetworkToml>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Error)]
|
|
pub enum PermissionProfileResolutionError {
|
|
#[error("default_permissions refers to undefined profile `{profile_name}`")]
|
|
UndefinedProfile { profile_name: String },
|
|
#[error(
|
|
"permissions profile `{profile_name}` extends undefined profile `{parent_profile_name}`"
|
|
)]
|
|
UndefinedParent {
|
|
profile_name: String,
|
|
parent_profile_name: String,
|
|
},
|
|
#[error(
|
|
"permissions profile `{profile_name}` cannot extend unsupported built-in profile `{parent_profile_name}`"
|
|
)]
|
|
UnsupportedBuiltInParent {
|
|
profile_name: String,
|
|
parent_profile_name: String,
|
|
},
|
|
#[error(
|
|
"permissions profile inheritance cycle detected: {}",
|
|
cycle.join(" -> ")
|
|
)]
|
|
Cycle { cycle: Vec<String> },
|
|
#[error("failed to serialize permissions profile while resolving inheritance: {source}")]
|
|
SerializeProfileToml {
|
|
#[source]
|
|
source: toml::ser::Error,
|
|
},
|
|
#[error(
|
|
"failed to deserialize merged permissions profile while resolving inheritance: {source}"
|
|
)]
|
|
DeserializeProfileToml {
|
|
#[source]
|
|
source: toml::de::Error,
|
|
},
|
|
}
|
|
|
|
fn merge_permission_profiles(
|
|
mut parent: PermissionProfileToml,
|
|
mut child: PermissionProfileToml,
|
|
) -> Result<PermissionProfileToml, PermissionProfileResolutionError> {
|
|
let merges_network_domains = parent
|
|
.network
|
|
.as_ref()
|
|
.and_then(|network| network.domains.as_ref())
|
|
.is_some()
|
|
&& child
|
|
.network
|
|
.as_ref()
|
|
.and_then(|network| network.domains.as_ref())
|
|
.is_some();
|
|
|
|
// Description and inheritance metadata belong to the selected profile
|
|
// declaration, so an inherited profile must not fill those gaps.
|
|
parent.description = None;
|
|
parent.extends = None;
|
|
|
|
if merges_network_domains {
|
|
normalize_profile_network_domains(&mut parent);
|
|
normalize_profile_network_domains(&mut child);
|
|
}
|
|
|
|
let mut merged = TomlValue::try_from(parent)
|
|
.map_err(|source| PermissionProfileResolutionError::SerializeProfileToml { source })?;
|
|
let child = TomlValue::try_from(child)
|
|
.map_err(|source| PermissionProfileResolutionError::SerializeProfileToml { source })?;
|
|
merge_toml_values(&mut merged, &child);
|
|
merged
|
|
.try_into()
|
|
.map_err(|source| PermissionProfileResolutionError::DeserializeProfileToml { source })
|
|
}
|
|
|
|
fn normalize_profile_network_domains(profile: &mut PermissionProfileToml) {
|
|
let Some(domains) = profile
|
|
.network
|
|
.as_mut()
|
|
.and_then(|network| network.domains.as_mut())
|
|
else {
|
|
return;
|
|
};
|
|
|
|
let entries = std::mem::take(&mut domains.entries);
|
|
domains.entries = entries
|
|
.into_iter()
|
|
.map(|(pattern, permission)| (normalize_host(&pattern), permission))
|
|
.collect();
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
|
pub struct WorkspaceRootsToml {
|
|
#[serde(flatten)]
|
|
pub entries: BTreeMap<String, bool>,
|
|
}
|
|
|
|
impl WorkspaceRootsToml {
|
|
pub fn enabled_roots(&self) -> impl Iterator<Item = &String> {
|
|
self.entries
|
|
.iter()
|
|
.filter_map(|(path, enabled)| (*enabled).then_some(path))
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
|
pub struct FilesystemPermissionsToml {
|
|
/// Optional maximum depth for expanding unreadable glob patterns on
|
|
/// platforms that snapshot glob matches before sandbox startup.
|
|
#[schemars(range(min = 1))]
|
|
pub glob_scan_max_depth: Option<usize>,
|
|
#[serde(flatten)]
|
|
pub entries: BTreeMap<String, FilesystemPermissionToml>,
|
|
}
|
|
|
|
impl FilesystemPermissionsToml {
|
|
pub fn is_empty(&self) -> bool {
|
|
self.entries.is_empty()
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)]
|
|
#[serde(untagged)]
|
|
pub enum FilesystemPermissionToml {
|
|
Access(FileSystemAccessMode),
|
|
Scoped(BTreeMap<String, FileSystemAccessMode>),
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
|
pub struct NetworkDomainPermissionsToml {
|
|
#[serde(flatten)]
|
|
pub entries: BTreeMap<String, NetworkDomainPermissionToml>,
|
|
}
|
|
|
|
impl NetworkDomainPermissionsToml {
|
|
pub fn is_empty(&self) -> bool {
|
|
self.entries.is_empty()
|
|
}
|
|
|
|
pub fn allowed_domains(&self) -> Option<Vec<String>> {
|
|
let allowed_domains: Vec<String> = self
|
|
.entries
|
|
.iter()
|
|
.filter(|(_, permission)| matches!(permission, NetworkDomainPermissionToml::Allow))
|
|
.map(|(pattern, _)| pattern.clone())
|
|
.collect();
|
|
(!allowed_domains.is_empty()).then_some(allowed_domains)
|
|
}
|
|
|
|
pub fn denied_domains(&self) -> Option<Vec<String>> {
|
|
let denied_domains: Vec<String> = self
|
|
.entries
|
|
.iter()
|
|
.filter(|(_, permission)| matches!(permission, NetworkDomainPermissionToml::Deny))
|
|
.map(|(pattern, _)| pattern.clone())
|
|
.collect();
|
|
(!denied_domains.is_empty()).then_some(denied_domains)
|
|
}
|
|
}
|
|
|
|
#[derive(
|
|
Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, JsonSchema,
|
|
)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum NetworkDomainPermissionToml {
|
|
Allow,
|
|
Deny,
|
|
}
|
|
|
|
impl std::fmt::Display for NetworkDomainPermissionToml {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
let permission = match self {
|
|
Self::Allow => "allow",
|
|
Self::Deny => "deny",
|
|
};
|
|
f.write_str(permission)
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
|
pub struct NetworkUnixSocketPermissionsToml {
|
|
#[serde(flatten)]
|
|
pub entries: BTreeMap<String, NetworkUnixSocketPermissionToml>,
|
|
}
|
|
|
|
impl NetworkUnixSocketPermissionsToml {
|
|
pub fn is_empty(&self) -> bool {
|
|
self.entries.is_empty()
|
|
}
|
|
|
|
pub fn allow_unix_sockets(&self) -> Vec<String> {
|
|
self.entries
|
|
.iter()
|
|
.filter(|(_, permission)| matches!(permission, NetworkUnixSocketPermissionToml::Allow))
|
|
.map(|(path, _)| path.clone())
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
#[derive(
|
|
Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, JsonSchema,
|
|
)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum NetworkUnixSocketPermissionToml {
|
|
Allow,
|
|
Deny,
|
|
}
|
|
|
|
impl std::fmt::Display for NetworkUnixSocketPermissionToml {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
let permission = match self {
|
|
Self::Allow => "allow",
|
|
Self::Deny => "deny",
|
|
};
|
|
f.write_str(permission)
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
|
#[schemars(deny_unknown_fields)]
|
|
pub struct NetworkToml {
|
|
pub enabled: Option<bool>,
|
|
pub proxy_url: Option<String>,
|
|
pub enable_socks5: Option<bool>,
|
|
pub socks_url: Option<String>,
|
|
pub enable_socks5_udp: Option<bool>,
|
|
pub allow_upstream_proxy: Option<bool>,
|
|
pub dangerously_allow_non_loopback_proxy: Option<bool>,
|
|
pub dangerously_allow_all_unix_sockets: Option<bool>,
|
|
#[schemars(with = "Option<NetworkModeSchema>")]
|
|
pub mode: Option<NetworkMode>,
|
|
pub domains: Option<NetworkDomainPermissionsToml>,
|
|
pub unix_sockets: Option<NetworkUnixSocketPermissionsToml>,
|
|
pub allow_local_binding: Option<bool>,
|
|
pub mitm: Option<NetworkMitmToml>,
|
|
}
|
|
|
|
#[derive(Serialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
|
#[schemars(deny_unknown_fields)]
|
|
pub struct NetworkMitmToml {
|
|
#[schemars(with = "Option<BTreeMap<String, NetworkMitmHookToml>>")]
|
|
pub hooks: Option<IndexMap<String, NetworkMitmHookToml>>,
|
|
#[schemars(with = "Option<BTreeMap<String, NetworkMitmActionToml>>")]
|
|
pub actions: Option<IndexMap<String, NetworkMitmActionToml>>,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
struct NetworkMitmTomlUnchecked {
|
|
pub hooks: Option<IndexMap<String, NetworkMitmHookToml>>,
|
|
pub actions: Option<IndexMap<String, NetworkMitmActionToml>>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
|
#[schemars(deny_unknown_fields)]
|
|
pub struct NetworkMitmHookToml {
|
|
pub host: String,
|
|
pub methods: Vec<String>,
|
|
pub path_prefixes: Vec<String>,
|
|
#[serde(default)]
|
|
pub query: BTreeMap<String, Vec<String>>,
|
|
#[serde(default)]
|
|
pub headers: BTreeMap<String, Vec<String>>,
|
|
#[schemars(with = "Option<MitmHookBodyConfigSchema>")]
|
|
pub body: Option<MitmHookBodyConfig>,
|
|
pub action: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
|
#[serde(rename_all = "lowercase")]
|
|
enum NetworkModeSchema {
|
|
Limited,
|
|
Full,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
|
#[serde(default)]
|
|
pub struct NetworkMitmActionToml {
|
|
pub strip_request_headers: Vec<String>,
|
|
pub inject_request_headers: Vec<NetworkMitmInjectedHeaderToml>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
|
#[serde(default)]
|
|
pub struct NetworkMitmInjectedHeaderToml {
|
|
pub name: String,
|
|
pub secret_env_var: Option<String>,
|
|
pub secret_file: Option<String>,
|
|
pub prefix: Option<String>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)]
|
|
#[serde(transparent)]
|
|
struct MitmHookBodyConfigSchema(pub serde_json::Value);
|
|
|
|
impl<'de> Deserialize<'de> for NetworkMitmToml {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
let unchecked = NetworkMitmTomlUnchecked::deserialize(deserializer)?;
|
|
let mitm = Self {
|
|
hooks: unchecked.hooks,
|
|
actions: unchecked.actions,
|
|
};
|
|
mitm.validate_action_definitions()
|
|
.map_err(serde::de::Error::custom)?;
|
|
Ok(mitm)
|
|
}
|
|
}
|
|
|
|
impl NetworkMitmToml {
|
|
pub fn validate_action_definitions(&self) -> Result<(), String> {
|
|
if let Some(actions) = self.actions.as_ref() {
|
|
for (action_name, action) in actions {
|
|
if action.is_empty() {
|
|
return Err(format!(
|
|
"network.mitm.actions.{action_name} must define at least one operation"
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
let Some(hooks) = self.hooks.as_ref() else {
|
|
return Ok(());
|
|
};
|
|
|
|
for (hook_name, hook) in hooks {
|
|
if hook.action.is_empty() {
|
|
return Err(format!(
|
|
"network.mitm.hooks.{hook_name}.action must not be empty"
|
|
));
|
|
}
|
|
}
|
|
|
|
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>>,
|
|
) -> Vec<MitmHookConfig> {
|
|
self.hooks
|
|
.as_ref()
|
|
.map(|hooks| {
|
|
hooks
|
|
.values()
|
|
.map(|hook| hook.to_runtime(actions_by_name))
|
|
.collect()
|
|
})
|
|
.unwrap_or_default()
|
|
}
|
|
}
|
|
|
|
impl NetworkMitmActionToml {
|
|
pub fn is_empty(&self) -> bool {
|
|
self.strip_request_headers.is_empty() && self.inject_request_headers.is_empty()
|
|
}
|
|
}
|
|
|
|
impl NetworkToml {
|
|
pub 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();
|
|
}
|
|
if let Some(enable_socks5) = self.enable_socks5 {
|
|
config.network.enable_socks5 = enable_socks5;
|
|
}
|
|
if let Some(socks_url) = self.socks_url.as_ref() {
|
|
config.network.socks_url = socks_url.clone();
|
|
}
|
|
if let Some(enable_socks5_udp) = self.enable_socks5_udp {
|
|
config.network.enable_socks5_udp = enable_socks5_udp;
|
|
}
|
|
if let Some(allow_upstream_proxy) = self.allow_upstream_proxy {
|
|
config.network.allow_upstream_proxy = allow_upstream_proxy;
|
|
}
|
|
if let Some(dangerously_allow_non_loopback_proxy) =
|
|
self.dangerously_allow_non_loopback_proxy
|
|
{
|
|
config.network.dangerously_allow_non_loopback_proxy =
|
|
dangerously_allow_non_loopback_proxy;
|
|
}
|
|
if let Some(dangerously_allow_all_unix_sockets) = self.dangerously_allow_all_unix_sockets {
|
|
config.network.dangerously_allow_all_unix_sockets = dangerously_allow_all_unix_sockets;
|
|
}
|
|
if let Some(mode) = self.mode {
|
|
config.network.mode = mode;
|
|
}
|
|
if let Some(domains) = self.domains.as_ref() {
|
|
overlay_network_domain_permissions(config, domains);
|
|
}
|
|
if let Some(unix_sockets) = self.unix_sockets.as_ref() {
|
|
let mut proxy_unix_sockets = config.network.unix_sockets.take().unwrap_or_default();
|
|
for (path, permission) in &unix_sockets.entries {
|
|
let permission = match permission {
|
|
NetworkUnixSocketPermissionToml::Allow => {
|
|
ProxyNetworkUnixSocketPermission::Allow
|
|
}
|
|
NetworkUnixSocketPermissionToml::Deny => ProxyNetworkUnixSocketPermission::Deny,
|
|
};
|
|
proxy_unix_sockets.entries.insert(path.clone(), permission);
|
|
}
|
|
config.network.unix_sockets =
|
|
(!proxy_unix_sockets.entries.is_empty()).then_some(proxy_unix_sockets);
|
|
}
|
|
if let Some(allow_local_binding) = self.allow_local_binding {
|
|
config.network.allow_local_binding = allow_local_binding;
|
|
}
|
|
if let Some(mitm) = self.mitm.as_ref() {
|
|
config.network.mitm_hooks = mitm.to_runtime_hooks(mitm.actions.as_ref());
|
|
}
|
|
config.network.mitm =
|
|
config.network.mode == NetworkMode::Limited || !config.network.mitm_hooks.is_empty();
|
|
}
|
|
|
|
pub fn to_network_proxy_config(&self) -> NetworkProxyConfig {
|
|
let mut config = NetworkProxyConfig::default();
|
|
self.apply_to_network_proxy_config(&mut config);
|
|
config
|
|
}
|
|
}
|
|
|
|
impl NetworkMitmHookToml {
|
|
fn to_runtime(
|
|
&self,
|
|
actions_by_name: Option<&IndexMap<String, NetworkMitmActionToml>>,
|
|
) -> MitmHookConfig {
|
|
MitmHookConfig {
|
|
host: self.host.clone(),
|
|
matcher: MitmHookMatchConfig {
|
|
methods: self.methods.clone(),
|
|
path_prefixes: self.path_prefixes.clone(),
|
|
query: self.query.clone(),
|
|
headers: self.headers.clone(),
|
|
body: self.body.clone(),
|
|
},
|
|
actions: self.selected_actions(actions_by_name),
|
|
}
|
|
}
|
|
|
|
fn selected_actions(
|
|
&self,
|
|
actions_by_name: Option<&IndexMap<String, NetworkMitmActionToml>>,
|
|
) -> MitmHookActionsConfig {
|
|
let Some(actions_by_name) = actions_by_name else {
|
|
return MitmHookActionsConfig::default();
|
|
};
|
|
|
|
let mut selected = MitmHookActionsConfig::default();
|
|
for action_name in &self.action {
|
|
if let Some(action) = actions_by_name.get(action_name) {
|
|
selected
|
|
.strip_request_headers
|
|
.extend(action.strip_request_headers.clone());
|
|
selected.inject_request_headers.extend(
|
|
action
|
|
.inject_request_headers
|
|
.iter()
|
|
.map(NetworkMitmInjectedHeaderToml::to_runtime),
|
|
);
|
|
}
|
|
}
|
|
selected
|
|
}
|
|
}
|
|
|
|
impl NetworkMitmInjectedHeaderToml {
|
|
fn to_runtime(&self) -> InjectedHeaderConfig {
|
|
InjectedHeaderConfig {
|
|
name: self.name.clone(),
|
|
secret_env_var: self.secret_env_var.clone(),
|
|
secret_file: self.secret_file.clone(),
|
|
prefix: self.prefix.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn overlay_network_domain_permissions(
|
|
config: &mut NetworkProxyConfig,
|
|
domains: &NetworkDomainPermissionsToml,
|
|
) {
|
|
for (pattern, permission) in &domains.entries {
|
|
let permission = match permission {
|
|
NetworkDomainPermissionToml::Allow => ProxyNetworkDomainPermission::Allow,
|
|
NetworkDomainPermissionToml::Deny => ProxyNetworkDomainPermission::Deny,
|
|
};
|
|
config
|
|
.network
|
|
.upsert_domain_permission(pattern.clone(), permission, normalize_host);
|
|
}
|
|
}
|