mirror of
https://github.com/openai/codex.git
synced 2026-06-03 03:41:58 +00:00
Wire managed MITM CA trust into child env (#22668)
## Stack 1. Parent PR: #18240 uses named MITM permissions config. 2. This PR wires managed MITM CA trust into spawned child processes. ## Why When Codex terminates HTTPS for limited mode or MITM hooks, child HTTPS clients need to trust Codex's managed MITM CA. Exporting proxy URLs alone is not enough, but blindly replacing user CA settings would be wrong: it can break custom enterprise/test roots, leak unreadable CA files into generated bundles, or make the child env disagree with its sandbox policy. ## Summary 1. Build immutable managed CA bundles under `$CODEX_HOME/proxy` that include native roots, the managed MITM CA, and only inherited or command-scoped CA bundles the child is allowed to read. 2. Export curated CA env vars alongside managed proxy env vars while preserving user CA override semantics, including nested Codex `SSL_CERT_FILE` precedence. 3. Thread generated CA bundle paths into child sandbox readable roots, including debug sandbox execution, so the exported env vars work inside sandboxed commands. 4. Remove only Codex-generated MITM CA bundle env when a child intentionally drops managed proxying for escalation or no-proxy retry. 5. Document the managed CA bundle behavior and cover env injection, per-child bundle generation, sandbox readable roots, and no-proxy cleanup in tests. ## Validation 1. Ran `just test -p codex-network-proxy`. 2. Ran `just test -p codex-protocol`. 3. Ran `just fix -p codex-network-proxy -p codex-protocol`. 4. Tried focused `codex-core` validation, but the crate currently fails to compile in `core/tests/suite/guardian_review.rs` because an existing `Op::UserInput` initializer is missing `additional_context`. --------- Co-authored-by: Eva Wong <evawong@openai.com>
This commit is contained in:
3
codex-rs/Cargo.lock
generated
3
codex-rs/Cargo.lock
generated
@@ -3359,6 +3359,7 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"clap",
|
||||
"codex-utils-absolute-path",
|
||||
@@ -3374,8 +3375,10 @@ dependencies = [
|
||||
"rama-tcp",
|
||||
"rama-tls-rustls",
|
||||
"rama-unix",
|
||||
"rustls-native-certs",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"tempfile",
|
||||
"thiserror 2.0.18",
|
||||
"time",
|
||||
|
||||
@@ -23,6 +23,7 @@ use codex_sandboxing::landlock::create_linux_sandbox_command_args_for_permission
|
||||
use codex_sandboxing::seatbelt::CreateSeatbeltCommandArgsParams;
|
||||
#[cfg(target_os = "macos")]
|
||||
use codex_sandboxing::seatbelt::create_seatbelt_command_args;
|
||||
use codex_sandboxing::with_managed_mitm_ca_readable_root;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_cli::CliConfigOverrides;
|
||||
use tokio::process::Child;
|
||||
@@ -257,12 +258,21 @@ async fn run_command_under_sandbox(
|
||||
let network = network_proxy
|
||||
.as_ref()
|
||||
.map(codex_core::config::StartedNetworkProxy::proxy);
|
||||
let managed_mitm_ca_trust_bundle_path = match network.as_ref() {
|
||||
Some(network) => network.managed_mitm_ca_trust_bundle_path(),
|
||||
None => None,
|
||||
};
|
||||
let runtime_permission_profile = with_managed_mitm_ca_readable_root(
|
||||
config.permissions.effective_permission_profile(),
|
||||
managed_mitm_ca_trust_bundle_path.as_ref(),
|
||||
sandbox_policy_cwd.as_path(),
|
||||
);
|
||||
|
||||
let mut child = match sandbox_type {
|
||||
#[cfg(target_os = "macos")]
|
||||
SandboxType::Seatbelt => {
|
||||
let file_system_sandbox_policy = config.permissions.file_system_sandbox_policy();
|
||||
let network_sandbox_policy = config.permissions.network_sandbox_policy();
|
||||
let (file_system_sandbox_policy, network_sandbox_policy) =
|
||||
runtime_permission_profile.to_runtime_permissions();
|
||||
let args = create_seatbelt_command_args(CreateSeatbeltCommandArgsParams {
|
||||
command,
|
||||
file_system_sandbox_policy: &file_system_sandbox_policy,
|
||||
@@ -294,11 +304,11 @@ async fn run_command_under_sandbox(
|
||||
.codex_linux_sandbox_exe
|
||||
.expect("codex-linux-sandbox executable not found");
|
||||
let use_legacy_landlock = config.features.use_legacy_landlock();
|
||||
let network_sandbox_policy = config.permissions.network_sandbox_policy();
|
||||
let network_sandbox_policy = runtime_permission_profile.network_sandbox_policy();
|
||||
let args = create_linux_sandbox_command_args_for_permission_profile(
|
||||
command,
|
||||
cwd.as_path(),
|
||||
&config.permissions.effective_permission_profile(),
|
||||
&runtime_permission_profile,
|
||||
sandbox_policy_cwd.as_path(),
|
||||
use_legacy_landlock,
|
||||
allow_network_for_proxy(managed_network_requirements_enabled),
|
||||
|
||||
@@ -4,9 +4,6 @@ use std::time::Duration;
|
||||
use codex_async_utils::CancelErr;
|
||||
use codex_async_utils::OrCancelExt;
|
||||
use codex_network_proxy::PROXY_ACTIVE_ENV_KEY;
|
||||
use codex_network_proxy::PROXY_ENV_KEYS;
|
||||
#[cfg(target_os = "macos")]
|
||||
use codex_network_proxy::PROXY_GIT_SSH_COMMAND_ENV_KEY;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::error;
|
||||
use uuid::Uuid;
|
||||
@@ -21,6 +18,7 @@ use crate::session::turn_context::TurnContext;
|
||||
use crate::state::TaskKind;
|
||||
use crate::tools::format_exec_output_str;
|
||||
use crate::tools::runtimes::maybe_wrap_shell_lc_with_snapshot;
|
||||
use crate::tools::runtimes::strip_managed_proxy_env;
|
||||
use crate::turn_timing::now_unix_timestamp_ms;
|
||||
use crate::user_shell_command::user_shell_command_record_item;
|
||||
use codex_protocol::exec_output::ExecToolCallOutput;
|
||||
@@ -131,18 +129,7 @@ pub(crate) async fn execute_user_shell_command(
|
||||
Some(session.conversation_id),
|
||||
);
|
||||
if exec_env_map.contains_key(PROXY_ACTIVE_ENV_KEY) {
|
||||
for key in PROXY_ENV_KEYS {
|
||||
exec_env_map.remove(*key);
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
if exec_env_map
|
||||
.get(PROXY_GIT_SSH_COMMAND_ENV_KEY)
|
||||
.is_some_and(|value| {
|
||||
value.starts_with(codex_network_proxy::CODEX_PROXY_GIT_SSH_COMMAND_MARKER)
|
||||
})
|
||||
{
|
||||
exec_env_map.remove(PROXY_GIT_SSH_COMMAND_ENV_KEY);
|
||||
}
|
||||
strip_managed_proxy_env(&mut exec_env_map);
|
||||
}
|
||||
let exec_command = maybe_wrap_shell_lc_with_snapshot(
|
||||
&display_command,
|
||||
|
||||
@@ -12,10 +12,12 @@ use crate::shell::ShellType;
|
||||
use crate::tools::sandboxing::ToolError;
|
||||
#[cfg(target_os = "macos")]
|
||||
use codex_network_proxy::CODEX_PROXY_GIT_SSH_COMMAND_MARKER;
|
||||
use codex_network_proxy::CUSTOM_CA_ENV_KEYS;
|
||||
use codex_network_proxy::PROXY_ACTIVE_ENV_KEY;
|
||||
use codex_network_proxy::PROXY_ENV_KEYS;
|
||||
#[cfg(target_os = "macos")]
|
||||
use codex_network_proxy::PROXY_GIT_SSH_COMMAND_ENV_KEY;
|
||||
use codex_network_proxy::is_managed_mitm_ca_trust_bundle_path;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::AdditionalPermissionProfile;
|
||||
use codex_sandboxing::SandboxCommand;
|
||||
@@ -57,21 +59,33 @@ pub(crate) fn exec_env_for_sandbox_permissions(
|
||||
if sandbox_permissions.requires_escalated_permissions()
|
||||
&& env.contains_key(PROXY_ACTIVE_ENV_KEY)
|
||||
{
|
||||
for key in PROXY_ENV_KEYS {
|
||||
env.remove(*key);
|
||||
}
|
||||
// Only macOS injects a Codex-owned SSH wrapper for the managed SOCKS proxy.
|
||||
#[cfg(target_os = "macos")]
|
||||
if env
|
||||
.get(PROXY_GIT_SSH_COMMAND_ENV_KEY)
|
||||
.is_some_and(|command| command.starts_with(CODEX_PROXY_GIT_SSH_COMMAND_MARKER))
|
||||
{
|
||||
env.remove(PROXY_GIT_SSH_COMMAND_ENV_KEY);
|
||||
}
|
||||
strip_managed_proxy_env(&mut env);
|
||||
}
|
||||
env
|
||||
}
|
||||
|
||||
pub(crate) fn strip_managed_proxy_env(env: &mut HashMap<String, String>) {
|
||||
for key in PROXY_ENV_KEYS {
|
||||
env.remove(*key);
|
||||
}
|
||||
for key in CUSTOM_CA_ENV_KEYS {
|
||||
if env
|
||||
.get(key)
|
||||
.is_some_and(|value| is_managed_mitm_ca_trust_bundle_path(value))
|
||||
{
|
||||
env.remove(key);
|
||||
}
|
||||
}
|
||||
// Only macOS injects a Codex-owned SSH wrapper for the managed SOCKS proxy.
|
||||
#[cfg(target_os = "macos")]
|
||||
if env
|
||||
.get(PROXY_GIT_SSH_COMMAND_ENV_KEY)
|
||||
.is_some_and(|command| command.starts_with(CODEX_PROXY_GIT_SSH_COMMAND_MARKER))
|
||||
{
|
||||
env.remove(PROXY_GIT_SSH_COMMAND_ENV_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn prepend_path_entry(env: &mut HashMap<String, String>, path_entry: &str) -> String {
|
||||
let updated_path = match env.get("PATH") {
|
||||
@@ -235,6 +249,7 @@ fn build_proxy_env_exports() -> (String, String) {
|
||||
let mut keys = PROXY_ENV_KEYS
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(CUSTOM_CA_ENV_KEYS)
|
||||
.filter(|key| is_valid_shell_variable_name(key))
|
||||
.collect::<Vec<_>>();
|
||||
keys.sort_unstable();
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::tools::sandboxing::SandboxAttempt;
|
||||
use crate::tools::sandboxing::managed_network_for_sandbox_permissions;
|
||||
#[cfg(target_os = "macos")]
|
||||
use codex_network_proxy::CODEX_PROXY_GIT_SSH_COMMAND_MARKER;
|
||||
use codex_network_proxy::CUSTOM_CA_ENV_KEYS;
|
||||
use codex_network_proxy::ConfigReloader;
|
||||
use codex_network_proxy::ConfigState;
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
@@ -134,6 +135,9 @@ async fn explicit_escalation_prepares_exec_without_managed_network() -> anyhow::
|
||||
for key in PROXY_ENV_KEYS {
|
||||
assert_eq!(exec_request.env.get(*key), None, "{key} should be unset");
|
||||
}
|
||||
for key in CUSTOM_CA_ENV_KEYS {
|
||||
assert_eq!(exec_request.env.get(key), None, "{key} should be unset");
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
assert_eq!(exec_request.env.get(PROXY_GIT_SSH_COMMAND_ENV_KEY), None);
|
||||
assert_eq!(
|
||||
@@ -144,6 +148,24 @@ async fn explicit_escalation_prepares_exec_without_managed_network() -> anyhow::
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_escalation_preserves_user_ca_env() {
|
||||
let env = HashMap::from([
|
||||
(PROXY_ACTIVE_ENV_KEY.to_string(), "1".to_string()),
|
||||
(
|
||||
"SSL_CERT_FILE".to_string(),
|
||||
"/tmp/custom-ca.pem".to_string(),
|
||||
),
|
||||
]);
|
||||
|
||||
let env = exec_env_for_sandbox_permissions(&env, SandboxPermissions::RequireEscalated);
|
||||
|
||||
assert_eq!(
|
||||
env.get("SSL_CERT_FILE"),
|
||||
Some(&"/tmp/custom-ca.pem".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn apply_zsh_fork_path_prepend_uses_shell_parent() {
|
||||
|
||||
@@ -15,6 +15,7 @@ workspace = true
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
chrono = { workspace = true }
|
||||
codex-utils-absolute-path = { workspace = true }
|
||||
@@ -35,6 +36,8 @@ rama-net = { version = "=0.3.0-alpha.4", features = ["http", "tls"] }
|
||||
rama-socks5 = { version = "=0.3.0-alpha.4" }
|
||||
rama-tcp = { version = "=0.3.0-alpha.4", features = ["http"] }
|
||||
rama-tls-rustls = { version = "=0.3.0-alpha.4", features = ["http"] }
|
||||
rustls-native-certs = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = { workspace = true }
|
||||
|
||||
@@ -35,6 +35,8 @@ dangerously_allow_non_loopback_proxy = false
|
||||
mode = "full" # default when unset; use "limited" for read-only mode
|
||||
# HTTPS MITM is enabled automatically when `mode = "limited"` or when MITM hooks are configured.
|
||||
# CA cert/key are managed internally under $CODEX_HOME/proxy/ (ca.pem + ca.key).
|
||||
# When MITM is active, spawned commands receive CA bundle env vars pointing at
|
||||
# immutable bundles under $CODEX_HOME/proxy/ so common HTTPS clients trust the managed CA.
|
||||
|
||||
# If false, local/private networking is rejected. Explicit allowlisting of local IP literals
|
||||
# (or `localhost`) is required to permit them.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use anyhow::Context as _;
|
||||
use anyhow::Result;
|
||||
use anyhow::anyhow;
|
||||
use base64::Engine as _;
|
||||
use codex_utils_home_dir::find_codex_home;
|
||||
use rama_net::tls::ApplicationProtocol;
|
||||
use rama_tls_rustls::dep::pki_types::CertificateDer;
|
||||
@@ -19,6 +20,9 @@ use rama_tls_rustls::dep::rcgen::PKCS_ECDSA_P256_SHA256;
|
||||
use rama_tls_rustls::dep::rcgen::SanType;
|
||||
use rama_tls_rustls::dep::rustls;
|
||||
use rama_tls_rustls::server::TlsAcceptorData;
|
||||
use sha2::Digest as _;
|
||||
use sha2::Sha256;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::fs::OpenOptions;
|
||||
@@ -29,6 +33,7 @@ use std::path::PathBuf;
|
||||
use std::time::SystemTime;
|
||||
use std::time::UNIX_EPOCH;
|
||||
use tracing::info;
|
||||
use tracing::warn;
|
||||
|
||||
pub(super) struct ManagedMitmCa {
|
||||
issuer: Issuer<'static, KeyPair>,
|
||||
@@ -95,6 +100,29 @@ fn issue_host_certificate_pem(
|
||||
const MANAGED_MITM_CA_DIR: &str = "proxy";
|
||||
const MANAGED_MITM_CA_CERT: &str = "ca.pem";
|
||||
const MANAGED_MITM_CA_KEY: &str = "ca.key";
|
||||
const MANAGED_MITM_CA_TRUST_BUNDLE_PREFIX: &str = "ca-bundle";
|
||||
|
||||
// Best-effort compatibility set for common child toolchains that accept a CA bundle path.
|
||||
// This is intentionally curated rather than pretending to cover every TLS client.
|
||||
pub const CUSTOM_CA_ENV_KEYS: [&str; 10] = [
|
||||
"CODEX_CA_CERTIFICATE",
|
||||
"SSL_CERT_FILE",
|
||||
"REQUESTS_CA_BUNDLE",
|
||||
"CURL_CA_BUNDLE",
|
||||
"NODE_EXTRA_CA_CERTS",
|
||||
"GIT_SSL_CAINFO",
|
||||
"PIP_CERT",
|
||||
"BUNDLE_SSL_CA_CERT",
|
||||
"npm_config_cafile",
|
||||
"NPM_CONFIG_CAFILE",
|
||||
];
|
||||
|
||||
/// Immutable managed MITM CA bundle path plus startup TLS env values.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct ManagedMitmCaTrustBundle {
|
||||
pub(crate) path: PathBuf,
|
||||
pub(crate) startup_env_values: HashMap<&'static str, String>,
|
||||
}
|
||||
|
||||
fn managed_ca_paths() -> Result<(PathBuf, PathBuf)> {
|
||||
let codex_home =
|
||||
@@ -106,6 +134,135 @@ fn managed_ca_paths() -> Result<(PathBuf, PathBuf)> {
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn managed_ca_trust_bundle(
|
||||
env: &HashMap<&'static str, String>,
|
||||
) -> Result<ManagedMitmCaTrustBundle> {
|
||||
load_or_create_ca()?;
|
||||
let (cert_path, _) = managed_ca_paths()?;
|
||||
managed_ca_trust_bundle_for_cert_path(&cert_path, env)
|
||||
}
|
||||
|
||||
fn managed_ca_trust_bundle_for_cert_path(
|
||||
cert_path: &Path,
|
||||
env: &HashMap<&'static str, String>,
|
||||
) -> Result<ManagedMitmCaTrustBundle> {
|
||||
let startup_env_values = CUSTOM_CA_ENV_KEYS
|
||||
.into_iter()
|
||||
.filter_map(|key| {
|
||||
env.get(key)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(|value| (key, value.clone()))
|
||||
})
|
||||
.collect();
|
||||
let trust_bundle = build_managed_ca_trust_bundle(cert_path)?;
|
||||
let path = persist_managed_ca_trust_bundle(cert_path, &trust_bundle)?;
|
||||
|
||||
Ok(ManagedMitmCaTrustBundle {
|
||||
path,
|
||||
startup_env_values,
|
||||
})
|
||||
}
|
||||
|
||||
fn build_managed_ca_trust_bundle(managed_ca_cert_path: &Path) -> Result<String> {
|
||||
let mut trust_bundle = String::new();
|
||||
let rustls_native_certs::CertificateResult { certs, errors, .. } =
|
||||
rustls_native_certs::load_native_certs();
|
||||
if !errors.is_empty() {
|
||||
warn!(
|
||||
native_root_error_count = errors.len(),
|
||||
"encountered errors while loading native root certificates for MITM trust bundle"
|
||||
);
|
||||
}
|
||||
for cert in certs {
|
||||
push_certificate_pem(&mut trust_bundle, cert.as_ref());
|
||||
}
|
||||
append_pem_file(&mut trust_bundle, managed_ca_cert_path)?;
|
||||
Ok(trust_bundle)
|
||||
}
|
||||
|
||||
fn is_current_generated_trust_bundle_path(path: &Path, managed_ca_cert_path: &Path) -> bool {
|
||||
let Some(proxy_dir) = managed_ca_cert_path.parent() else {
|
||||
return false;
|
||||
};
|
||||
let Some(file_name) = path.file_name().and_then(|file_name| file_name.to_str()) else {
|
||||
return false;
|
||||
};
|
||||
if path.parent() != Some(proxy_dir)
|
||||
|| !file_name.starts_with(MANAGED_MITM_CA_TRUST_BUNDLE_PREFIX)
|
||||
|| !file_name.ends_with(".pem")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let Ok(trust_bundle) = fs::read(path) else {
|
||||
return false;
|
||||
};
|
||||
let Ok(managed_ca_cert) = fs::read(managed_ca_cert_path) else {
|
||||
return false;
|
||||
};
|
||||
!managed_ca_cert.is_empty()
|
||||
&& trust_bundle
|
||||
.windows(managed_ca_cert.len())
|
||||
.any(|window| window == managed_ca_cert)
|
||||
}
|
||||
|
||||
/// Returns whether `path` points at a current Codex-generated MITM CA bundle.
|
||||
pub fn is_managed_mitm_ca_trust_bundle_path(path: &str) -> bool {
|
||||
let Ok((managed_ca_cert_path, _)) = managed_ca_paths() else {
|
||||
return false;
|
||||
};
|
||||
is_current_generated_trust_bundle_path(Path::new(path), &managed_ca_cert_path)
|
||||
}
|
||||
|
||||
fn persist_managed_ca_trust_bundle(
|
||||
managed_ca_cert_path: &Path,
|
||||
trust_bundle: &str,
|
||||
) -> Result<PathBuf> {
|
||||
let proxy_dir = managed_ca_cert_path
|
||||
.parent()
|
||||
.ok_or_else(|| anyhow!("managed MITM CA cert path is missing a parent"))?;
|
||||
fs::create_dir_all(proxy_dir)
|
||||
.with_context(|| format!("failed to create {}", proxy_dir.display()))?;
|
||||
let hash = Sha256::digest(trust_bundle.as_bytes());
|
||||
let trust_bundle_path = proxy_dir.join(format!(
|
||||
"{MANAGED_MITM_CA_TRUST_BUNDLE_PREFIX}-{hash:x}.pem"
|
||||
));
|
||||
write_atomic_create_new_or_reuse(
|
||||
&trust_bundle_path,
|
||||
trust_bundle.as_bytes(),
|
||||
/*mode*/ 0o644,
|
||||
)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed to persist managed MITM CA trust bundle {}",
|
||||
trust_bundle_path.display()
|
||||
)
|
||||
})?;
|
||||
Ok(trust_bundle_path)
|
||||
}
|
||||
|
||||
fn append_pem_file(bundle: &mut String, path: &Path) -> Result<()> {
|
||||
if !bundle.ends_with('\n') {
|
||||
bundle.push('\n');
|
||||
}
|
||||
let pem = fs::read_to_string(path)
|
||||
.with_context(|| format!("failed to read CA bundle {}", path.display()))?;
|
||||
bundle.push_str(&pem);
|
||||
if !bundle.ends_with('\n') {
|
||||
bundle.push('\n');
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn push_certificate_pem(bundle: &mut String, der: &[u8]) {
|
||||
bundle.push_str("-----BEGIN CERTIFICATE-----\n");
|
||||
let encoded = base64::engine::general_purpose::STANDARD.encode(der);
|
||||
for chunk in encoded.as_bytes().chunks(64) {
|
||||
bundle.push_str(&String::from_utf8_lossy(chunk));
|
||||
bundle.push('\n');
|
||||
}
|
||||
bundle.push_str("-----END CERTIFICATE-----\n");
|
||||
}
|
||||
|
||||
fn load_or_create_ca() -> Result<(String, String)> {
|
||||
let (cert_path, key_path) = managed_ca_paths()?;
|
||||
|
||||
@@ -230,14 +387,47 @@ fn write_atomic_create_new(path: &Path, contents: &[u8], mode: u32) -> Result<()
|
||||
}
|
||||
}
|
||||
|
||||
sync_parent_dir(parent)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn sync_parent_dir(parent: &Path) -> Result<()> {
|
||||
// Best-effort durability: ensure the directory entry is persisted too.
|
||||
let dir = File::open(parent).with_context(|| format!("failed to open {}", parent.display()))?;
|
||||
dir.sync_all()
|
||||
.with_context(|| format!("failed to fsync {}", parent.display()))?;
|
||||
.with_context(|| format!("failed to fsync {}", parent.display()))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn sync_parent_dir(_parent: &Path) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_atomic_create_new_or_reuse(path: &Path, contents: &[u8], mode: u32) -> Result<()> {
|
||||
if fs::symlink_metadata(path)
|
||||
.ok()
|
||||
.is_some_and(|metadata| metadata.file_type().is_symlink())
|
||||
{
|
||||
return Err(anyhow!("refusing to reuse symlink {}", path.display()));
|
||||
}
|
||||
if fs::read(path).ok().as_deref() == Some(contents) {
|
||||
return Ok(());
|
||||
}
|
||||
if path.exists() {
|
||||
return Err(anyhow!(
|
||||
"refusing to reuse existing mismatched file {}",
|
||||
path.display()
|
||||
));
|
||||
}
|
||||
match write_atomic_create_new(path, contents, mode) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(_err) if fs::read(path).ok().as_deref() == Some(contents) => Ok(()),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn validate_existing_ca_key_file(path: &Path) -> Result<()> {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
@@ -294,13 +484,44 @@ fn open_create_new_with_mode(path: &Path, _mode: u32) -> Result<File> {
|
||||
.with_context(|| format!("failed to create {}", path.display()))
|
||||
}
|
||||
|
||||
#[cfg(all(test, unix))]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[cfg(unix)]
|
||||
use pretty_assertions::assert_eq;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn current_generated_trust_bundle_path_rejects_stale_bundle() {
|
||||
let dir = tempdir().unwrap();
|
||||
let managed_ca_cert_path = dir.path().join("ca.pem");
|
||||
let trust_bundle_path = dir.path().join("ca-bundle-123.pem");
|
||||
fs::write(&managed_ca_cert_path, "managed ca\n").unwrap();
|
||||
fs::write(&trust_bundle_path, "stale managed bundle\n").unwrap();
|
||||
assert!(!is_current_generated_trust_bundle_path(
|
||||
&trust_bundle_path,
|
||||
&managed_ca_cert_path,
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn managed_ca_trust_bundle_records_startup_ca_env_values() {
|
||||
let dir = tempdir().unwrap();
|
||||
let managed_ca_cert_path = dir.path().join("ca.pem");
|
||||
fs::write(&managed_ca_cert_path, "managed ca\n").unwrap();
|
||||
let env = HashMap::from([("SSL_CERT_FILE", "/tmp/startup-ca.pem".to_string())]);
|
||||
let trust_bundle =
|
||||
managed_ca_trust_bundle_for_cert_path(&managed_ca_cert_path, &env).unwrap();
|
||||
assert_eq!(
|
||||
trust_bundle.startup_env_values,
|
||||
HashMap::from([("SSL_CERT_FILE", "/tmp/startup-ca.pem".to_string())])
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn validate_existing_ca_key_file_rejects_group_world_permissions() {
|
||||
let dir = tempdir().unwrap();
|
||||
@@ -315,6 +536,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn validate_existing_ca_key_file_rejects_symlink() {
|
||||
use std::os::unix::fs::symlink;
|
||||
@@ -332,6 +554,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn validate_existing_ca_key_file_allows_private_permissions() {
|
||||
let dir = tempdir().unwrap();
|
||||
@@ -341,4 +564,23 @@ mod tests {
|
||||
|
||||
validate_existing_ca_key_file(&key_path).unwrap();
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn write_atomic_create_new_or_reuse_rejects_matching_symlink_target() {
|
||||
use std::os::unix::fs::symlink;
|
||||
|
||||
let dir = tempdir().unwrap();
|
||||
let target = dir.path().join("real-bundle.pem");
|
||||
let link = dir.path().join("ca-bundle.pem");
|
||||
fs::write(&target, "bundle").unwrap();
|
||||
symlink(&target, &link).unwrap();
|
||||
|
||||
let err = write_atomic_create_new_or_reuse(&link, b"bundle", /*mode*/ 0o644).unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
format!("refusing to reuse symlink {}", link.display())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ mod socks5;
|
||||
mod state;
|
||||
mod upstream;
|
||||
|
||||
pub use certs::CUSTOM_CA_ENV_KEYS;
|
||||
pub use certs::is_managed_mitm_ca_trust_bundle_path;
|
||||
pub use config::NetworkDomainPermission;
|
||||
pub use config::NetworkDomainPermissionEntry;
|
||||
pub use config::NetworkDomainPermissions;
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::state::NetworkProxyState;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use std::net::TcpListener as StdTcpListener;
|
||||
@@ -223,7 +224,7 @@ impl NetworkProxyBuilder {
|
||||
socks_enabled: current_cfg.network.enable_socks5,
|
||||
runtime_settings: Arc::new(RwLock::new(NetworkProxyRuntimeSettings::from_config(
|
||||
¤t_cfg,
|
||||
))),
|
||||
)?)),
|
||||
reserved_listeners,
|
||||
policy_decider: self.policy_decider,
|
||||
})
|
||||
@@ -299,15 +300,26 @@ struct NetworkProxyRuntimeSettings {
|
||||
allow_local_binding: bool,
|
||||
allow_unix_sockets: Arc<[String]>,
|
||||
dangerously_allow_all_unix_sockets: bool,
|
||||
mitm_ca_trust_bundle: Option<crate::certs::ManagedMitmCaTrustBundle>,
|
||||
}
|
||||
|
||||
impl NetworkProxyRuntimeSettings {
|
||||
fn from_config(config: &config::NetworkProxyConfig) -> Self {
|
||||
Self {
|
||||
fn from_config(config: &config::NetworkProxyConfig) -> Result<Self> {
|
||||
let mitm_ca_trust_bundle = if config.network.mitm {
|
||||
let env = crate::certs::CUSTOM_CA_ENV_KEYS
|
||||
.into_iter()
|
||||
.filter_map(|key| std::env::var(key).ok().map(|value| (key, value)))
|
||||
.collect();
|
||||
Some(crate::certs::managed_ca_trust_bundle(&env)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(Self {
|
||||
allow_local_binding: config.network.allow_local_binding,
|
||||
allow_unix_sockets: config.network.allow_unix_sockets().into(),
|
||||
dangerously_allow_all_unix_sockets: config.network.dangerously_allow_all_unix_sockets,
|
||||
}
|
||||
mitm_ca_trust_bundle,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,6 +489,7 @@ fn apply_proxy_env_overrides(
|
||||
socks_addr: SocketAddr,
|
||||
socks_enabled: bool,
|
||||
allow_local_binding: bool,
|
||||
mitm_ca_trust_bundle: Option<&crate::certs::ManagedMitmCaTrustBundle>,
|
||||
) {
|
||||
let http_proxy_url = format!("http://{http_addr}");
|
||||
let socks_proxy_url = format!("socks5h://{socks_addr}");
|
||||
@@ -556,6 +569,27 @@ fn apply_proxy_env_overrides(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(mitm_ca_trust_bundle) = mitm_ca_trust_bundle {
|
||||
let managed_path = mitm_ca_trust_bundle.path.to_string_lossy().into_owned();
|
||||
for key in crate::certs::CUSTOM_CA_ENV_KEYS {
|
||||
if env
|
||||
.get(key)
|
||||
.filter(|value| !value.is_empty())
|
||||
.is_some_and(|value| {
|
||||
value != &managed_path
|
||||
&& mitm_ca_trust_bundle.startup_env_values.get(key) != Some(value)
|
||||
})
|
||||
{
|
||||
// TODO(winston): Materialize policy-checked per-child bundles for readable
|
||||
// startup and command-scoped CA overrides. For now startup overrides are
|
||||
// replaced with the default bundle and later command-scoped overrides are
|
||||
// preserved, either of which can make intercepted TLS fail.
|
||||
continue;
|
||||
}
|
||||
env.insert(key.to_string(), managed_path.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NetworkProxy {
|
||||
@@ -595,16 +629,28 @@ impl NetworkProxy {
|
||||
self.runtime_settings().dangerously_allow_all_unix_sockets
|
||||
}
|
||||
|
||||
/// Returns the generated MITM CA bundle path child sandboxes should expose to TLS clients.
|
||||
pub fn managed_mitm_ca_trust_bundle_path(&self) -> Option<AbsolutePathBuf> {
|
||||
self.runtime_settings()
|
||||
.mitm_ca_trust_bundle
|
||||
.and_then(|bundle| {
|
||||
AbsolutePathBuf::from_absolute_path(bundle.path)
|
||||
.map_err(|err| warn!("managed MITM CA trust bundle path is invalid: {err}"))
|
||||
.ok()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn apply_to_env(&self, env: &mut HashMap<String, String>) {
|
||||
let allow_local_binding = self.allow_local_binding();
|
||||
// Enforce proxying for child processes. We intentionally override existing values so
|
||||
// command-level environment cannot bypass the managed proxy endpoint.
|
||||
let runtime_settings = self.runtime_settings();
|
||||
// Enforce proxying for child processes. Proxy endpoint values are always rewritten;
|
||||
// managed MITM CA vars preserve child-scoped overrides after proxy startup.
|
||||
apply_proxy_env_overrides(
|
||||
env,
|
||||
self.http_addr,
|
||||
self.socks_addr,
|
||||
self.socks_enabled,
|
||||
allow_local_binding,
|
||||
runtime_settings.allow_local_binding,
|
||||
runtime_settings.mitm_ca_trust_bundle.as_ref(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -631,7 +677,7 @@ impl NetworkProxy {
|
||||
"cannot update network.enable_socks5_udp on a running proxy"
|
||||
);
|
||||
|
||||
let settings = NetworkProxyRuntimeSettings::from_config(&new_state.config);
|
||||
let settings = NetworkProxyRuntimeSettings::from_config(&new_state.config)?;
|
||||
self.state.replace_config_state(new_state).await?;
|
||||
let mut guard = self
|
||||
.runtime_settings
|
||||
@@ -791,6 +837,7 @@ mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::net::IpAddr;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::path::Path;
|
||||
|
||||
#[tokio::test]
|
||||
async fn managed_proxy_builder_uses_loopback_ports() {
|
||||
@@ -979,6 +1026,7 @@ mod tests {
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8081),
|
||||
/*socks_enabled*/ true,
|
||||
/*allow_local_binding*/ false,
|
||||
/*mitm_ca_trust_bundle*/ None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -1042,6 +1090,7 @@ mod tests {
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8081),
|
||||
/*socks_enabled*/ true,
|
||||
/*allow_local_binding*/ false,
|
||||
/*mitm_ca_trust_bundle*/ None,
|
||||
);
|
||||
|
||||
for key in env.keys() {
|
||||
@@ -1054,6 +1103,60 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_proxy_env_overrides_sets_mitm_ca_trust_bundle_vars() {
|
||||
let mut env = HashMap::new();
|
||||
let mitm_ca_trust_bundle_path = Path::new("/tmp/codex-proxy/ca-bundle.pem");
|
||||
let mitm_ca_trust_bundle = crate::certs::ManagedMitmCaTrustBundle {
|
||||
path: mitm_ca_trust_bundle_path.to_path_buf(),
|
||||
startup_env_values: HashMap::new(),
|
||||
};
|
||||
apply_proxy_env_overrides(
|
||||
&mut env,
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 3128),
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8081),
|
||||
/*socks_enabled*/ true,
|
||||
/*allow_local_binding*/ false,
|
||||
Some(&mitm_ca_trust_bundle),
|
||||
);
|
||||
|
||||
for key in crate::certs::CUSTOM_CA_ENV_KEYS {
|
||||
assert_eq!(
|
||||
env.get(key),
|
||||
Some(&mitm_ca_trust_bundle_path.display().to_string())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_proxy_env_overrides_preserves_command_scoped_mitm_ca_override() {
|
||||
let command_ca_bundle_path = "/tmp/command-ca.pem".to_string();
|
||||
let mut env = HashMap::from([(
|
||||
"REQUESTS_CA_BUNDLE".to_string(),
|
||||
command_ca_bundle_path.clone(),
|
||||
)]);
|
||||
let mitm_ca_trust_bundle_path = Path::new("/tmp/codex-proxy/ca-bundle.pem");
|
||||
let mitm_ca_trust_bundle = crate::certs::ManagedMitmCaTrustBundle {
|
||||
path: mitm_ca_trust_bundle_path.to_path_buf(),
|
||||
startup_env_values: HashMap::new(),
|
||||
};
|
||||
|
||||
apply_proxy_env_overrides(
|
||||
&mut env,
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 3128),
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8081),
|
||||
/*socks_enabled*/ true,
|
||||
/*allow_local_binding*/ false,
|
||||
Some(&mitm_ca_trust_bundle),
|
||||
);
|
||||
|
||||
assert_eq!(env.get("REQUESTS_CA_BUNDLE"), Some(&command_ca_bundle_path));
|
||||
assert_eq!(
|
||||
env.get("SSL_CERT_FILE"),
|
||||
Some(&mitm_ca_trust_bundle_path.display().to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_proxy_env_overrides_uses_http_for_all_proxy_without_socks() {
|
||||
let mut env = HashMap::new();
|
||||
@@ -1063,6 +1166,7 @@ mod tests {
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8081),
|
||||
/*socks_enabled*/ false,
|
||||
/*allow_local_binding*/ true,
|
||||
/*mitm_ca_trust_bundle*/ None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -1081,6 +1185,7 @@ mod tests {
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8081),
|
||||
/*socks_enabled*/ true,
|
||||
/*allow_local_binding*/ false,
|
||||
/*mitm_ca_trust_bundle*/ None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -1129,6 +1234,7 @@ mod tests {
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8081),
|
||||
/*socks_enabled*/ true,
|
||||
/*allow_local_binding*/ false,
|
||||
/*mitm_ca_trust_bundle*/ None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -1151,6 +1257,7 @@ mod tests {
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 48081),
|
||||
/*socks_enabled*/ true,
|
||||
/*allow_local_binding*/ false,
|
||||
/*mitm_ca_trust_bundle*/ None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -1174,6 +1281,7 @@ mod tests {
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 48081),
|
||||
/*socks_enabled*/ true,
|
||||
/*allow_local_binding*/ false,
|
||||
/*mitm_ca_trust_bundle*/ None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::connect_policy::TargetCheckedTcpConnector;
|
||||
use crate::state::NetworkProxyState;
|
||||
use codex_utils_rustls_provider::ensure_rustls_crypto_provider;
|
||||
use rama_core::Layer;
|
||||
use rama_core::Service;
|
||||
use rama_core::error::BoxError;
|
||||
@@ -225,6 +226,7 @@ fn build_http_connector(
|
||||
EstablishedClientConnection<HttpClientService<Body>, Request<Body>>,
|
||||
BoxError,
|
||||
> {
|
||||
ensure_rustls_crypto_provider();
|
||||
let proxy = HttpProxyConnectorLayer::optional().into_layer(transport);
|
||||
let tls_config = TlsConnectorDataBuilder::new()
|
||||
.with_alpn_protocols_http_auto()
|
||||
|
||||
@@ -19,6 +19,7 @@ pub use manager::SandboxType;
|
||||
pub use manager::SandboxablePreference;
|
||||
pub use manager::compatibility_sandbox_policy_for_permission_profile;
|
||||
pub use manager::get_platform_sandbox;
|
||||
pub use manager::with_managed_mitm_ca_readable_root;
|
||||
|
||||
use codex_protocol::error::CodexErr;
|
||||
|
||||
|
||||
@@ -61,6 +61,27 @@ pub fn get_platform_sandbox(windows_sandbox_enabled: bool) -> Option<SandboxType
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_managed_mitm_ca_readable_root(
|
||||
permission_profile: PermissionProfile,
|
||||
managed_mitm_ca_trust_bundle_path: Option<&AbsolutePathBuf>,
|
||||
sandbox_policy_cwd: &Path,
|
||||
) -> PermissionProfile {
|
||||
let Some(managed_mitm_ca_trust_bundle_path) = managed_mitm_ca_trust_bundle_path else {
|
||||
return permission_profile;
|
||||
};
|
||||
let (file_system_sandbox_policy, network_sandbox_policy) =
|
||||
permission_profile.to_runtime_permissions();
|
||||
let file_system_sandbox_policy = file_system_sandbox_policy.with_additional_readable_roots(
|
||||
sandbox_policy_cwd,
|
||||
std::slice::from_ref(managed_mitm_ca_trust_bundle_path),
|
||||
);
|
||||
PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
permission_profile.enforcement(),
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SandboxCommand {
|
||||
pub program: OsString,
|
||||
@@ -182,8 +203,15 @@ impl SandboxManager {
|
||||
windows_sandbox_private_desktop,
|
||||
} = request;
|
||||
let additional_permissions = command.additional_permissions.take();
|
||||
let managed_mitm_ca_trust_bundle_path =
|
||||
network.and_then(NetworkProxy::managed_mitm_ca_trust_bundle_path);
|
||||
let effective_permission_profile =
|
||||
effective_permission_profile(permissions, additional_permissions.as_ref());
|
||||
let effective_permission_profile = with_managed_mitm_ca_readable_root(
|
||||
effective_permission_profile,
|
||||
managed_mitm_ca_trust_bundle_path.as_ref(),
|
||||
sandbox_policy_cwd,
|
||||
);
|
||||
let (effective_file_system_policy, effective_network_policy) =
|
||||
effective_permission_profile.to_runtime_permissions();
|
||||
let mut argv = Vec::with_capacity(1 + command.args.len());
|
||||
|
||||
@@ -4,6 +4,7 @@ use super::SandboxTransformRequest;
|
||||
use super::SandboxType;
|
||||
use super::SandboxablePreference;
|
||||
use super::get_platform_sandbox;
|
||||
use super::with_managed_mitm_ca_readable_root;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::AdditionalPermissionProfile;
|
||||
use codex_protocol::models::FileSystemPermissions;
|
||||
@@ -242,6 +243,48 @@ fn transform_additional_permissions_preserves_denied_entries() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn managed_mitm_ca_bundle_becomes_readable_for_restricted_sandbox() {
|
||||
let cwd = TempDir::new().expect("create cwd");
|
||||
let cwd =
|
||||
AbsolutePathBuf::from_absolute_path(canonicalize(cwd.path()).expect("canonicalize cwd"))
|
||||
.expect("absolute cwd");
|
||||
let managed_bundle_dir = TempDir::new().expect("create managed bundle dir");
|
||||
let managed_bundle_path =
|
||||
AbsolutePathBuf::from_absolute_path(managed_bundle_dir.path().join("ca-bundle.pem"))
|
||||
.expect("absolute managed bundle path");
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
&FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: cwd.clone() },
|
||||
access: FileSystemAccessMode::Read,
|
||||
}]),
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
);
|
||||
|
||||
let permission_profile = with_managed_mitm_ca_readable_root(
|
||||
permission_profile,
|
||||
Some(&managed_bundle_path),
|
||||
cwd.as_path(),
|
||||
);
|
||||
let (file_system_sandbox_policy, _) = permission_profile.to_runtime_permissions();
|
||||
|
||||
assert_eq!(
|
||||
file_system_sandbox_policy,
|
||||
FileSystemSandboxPolicy::restricted(vec![
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: cwd },
|
||||
access: FileSystemAccessMode::Read,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: managed_bundle_path,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn transform_linux_seccomp_request(
|
||||
codex_linux_sandbox_exe: &std::path::Path,
|
||||
|
||||
Reference in New Issue
Block a user