From c5a9a95ab660d54869468ff3282d9d067e66a675 Mon Sep 17 00:00:00 2001 From: Winston Howes Date: Tue, 23 Jun 2026 12:20:51 -0700 Subject: [PATCH] Keep managed MITM CA private keys in proxy memory (#29013) ## Why The managed MITM trust bundle must be readable by sandboxed commands. Persisting its sibling CA private key under `$CODEX_HOME/proxy` therefore requires a deny-read sandbox rule, but the Windows unelevated backend rejects deny-read paths and WSL1's legacy Landlock path cannot enforce that rule. A persistent OS credential store also does not provide the same cross-platform boundary from other processes running as the same user. Keeping the signer inside the network proxy process avoids both problems: ordinary sandbox setup stays independent of CA-key state, and no private signing key is exposed through the filesystem or a persistent credential record. ## What - generate one managed CA per proxy process and retain its private signer only in proxy memory - emit only content-addressed public CA certificates and trust bundles under `$CODEX_HOME/proxy` - hold a cross-process lease for each active public certificate and prune artifacts from inactive proxy processes - keep all CA ownership in `codex-network-proxy`; no `codex-core` or sandbox-policy changes - validate generated trust-bundle paths by their content hash - keep the public bundle readable by sandboxed commands on Windows, WSL1, macOS, and Linux The independent startup custom-CA follow-up is #29014. ## Validation - `CODEX_HOME=/private/tmp/codex-test-home-network-proxy just test -p codex-network-proxy` (179 tests) - `just bazel-lock-check` - `just fix -p codex-network-proxy` - `just fmt` --------- Co-authored-by: viyatb-oai --- codex-rs/network-proxy/README.md | 6 +- codex-rs/network-proxy/src/certs.rs | 495 +++++++++++++++++++--------- codex-rs/network-proxy/src/mitm.rs | 4 +- 3 files changed, 346 insertions(+), 159 deletions(-) diff --git a/codex-rs/network-proxy/README.md b/codex-rs/network-proxy/README.md index d5d073022..484fa817a 100644 --- a/codex-rs/network-proxy/README.md +++ b/codex-rs/network-proxy/README.md @@ -34,9 +34,9 @@ allow_upstream_proxy = true 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. +# The CA private key remains in proxy memory. When MITM is active, spawned commands receive CA +# bundle env vars pointing at immutable public files 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. diff --git a/codex-rs/network-proxy/src/certs.rs b/codex-rs/network-proxy/src/certs.rs index 2ff7cc03b..442211f60 100644 --- a/codex-rs/network-proxy/src/certs.rs +++ b/codex-rs/network-proxy/src/certs.rs @@ -32,6 +32,8 @@ use std::net::IpAddr; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; +use std::sync::LazyLock; +use std::sync::Mutex; use std::time::SystemTime; use std::time::UNIX_EPOCH; use tracing::info; @@ -39,15 +41,60 @@ use tracing::warn; pub(super) struct ManagedMitmCa { issuer: Issuer<'static, KeyPair>, + certificate_path: PathBuf, + _artifact_lease: File, } +static MANAGED_MITM_CAS: LazyLock>>> = + LazyLock::new(|| Mutex::new(HashMap::new())); + impl ManagedMitmCa { - pub(super) fn load_or_create() -> Result { - let (ca_cert_pem, ca_key_pem) = load_or_create_ca()?; - let ca_key = KeyPair::from_pem(&ca_key_pem).context("failed to parse CA key")?; - let issuer: Issuer<'static, KeyPair> = - Issuer::from_ca_cert_pem(&ca_cert_pem, ca_key).context("failed to parse CA cert")?; - Ok(Self { issuer }) + pub(super) fn load_or_create() -> Result> { + let proxy_dir = managed_ca_dir()?; + let mut managed_cas = MANAGED_MITM_CAS + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + if let Some(ca) = managed_cas.get(&proxy_dir) { + return Ok(ca.clone()); + } + + let ca = Arc::new(Self::create(&proxy_dir)?); + managed_cas.insert(proxy_dir, ca.clone()); + Ok(ca) + } + + fn create(proxy_dir: &Path) -> Result { + fs::create_dir_all(proxy_dir) + .with_context(|| format!("failed to create {}", proxy_dir.display()))?; + + let (certificate_pem, private_key) = generate_ca()?; + let artifact_lock = match lock_managed_ca_artifacts(proxy_dir) { + Ok(lock) => Some(lock), + Err(err) => { + warn!("failed to lock managed MITM CA artifacts; skipping pruning: {err}"); + None + } + }; + let certificate_path = persist_managed_ca_certificate(proxy_dir, &certificate_pem)?; + let issuer = Issuer::from_ca_cert_pem(&certificate_pem, private_key) + .context("failed to parse managed MITM CA certificate")?; + let artifact_lease = lock_managed_ca_certificate(&certificate_path)?; + if artifact_lock.is_some() { + prune_managed_ca_artifacts(proxy_dir); + } + info!( + cert_path = %certificate_path.display(), + "generated process-local MITM CA" + ); + Ok(Self { + issuer, + certificate_path, + _artifact_lease: artifact_lease, + }) + } + + fn certificate_path(&self) -> &Path { + &self.certificate_path } pub(super) fn tls_acceptor_data_for_host(&self, host: &str) -> Result { @@ -100,8 +147,8 @@ 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_ARTIFACT_LOCK: &str = ".artifacts.lock"; +const MANAGED_MITM_CA_CERT_PREFIX: &str = "ca"; const MANAGED_MITM_CA_TRUST_BUNDLE_PREFIX: &str = "ca-bundle"; pub(crate) const SSL_CERT_DIR_ENV_KEY: &str = "SSL_CERT_DIR"; @@ -135,22 +182,17 @@ pub(crate) struct ManagedMitmCaTrustBundle { pub(crate) startup_env_values: HashMap<&'static str, String>, } -fn managed_ca_paths() -> Result<(PathBuf, PathBuf)> { +fn managed_ca_dir() -> Result { let codex_home = find_codex_home().context("failed to resolve CODEX_HOME for managed MITM CA")?; - let proxy_dir = codex_home.join(MANAGED_MITM_CA_DIR); - Ok(( - proxy_dir.join(MANAGED_MITM_CA_CERT).to_path_buf(), - proxy_dir.join(MANAGED_MITM_CA_KEY).to_path_buf(), - )) + Ok(codex_home.join(MANAGED_MITM_CA_DIR).to_path_buf()) } pub(crate) fn managed_ca_trust_bundle( env: &HashMap<&'static str, String>, ) -> Result { - load_or_create_ca()?; - let (cert_path, _) = managed_ca_paths()?; - managed_ca_trust_bundle_for_cert_path(&cert_path, env) + let ca = ManagedMitmCa::load_or_create()?; + managed_ca_trust_bundle_for_cert_path(ca.certificate_path(), env) } fn managed_ca_trust_bundle_for_cert_path( @@ -175,8 +217,8 @@ fn managed_ca_trust_bundle_for_cert_path( pub(crate) fn upstream_tls_root_store( env: &HashMap<&'static str, String>, ) -> Result> { - let (managed_ca_cert_path, _) = managed_ca_paths()?; - upstream_tls_root_store_for_cert_path(&managed_ca_cert_path, env) + let ca = ManagedMitmCa::load_or_create()?; + upstream_tls_root_store_for_cert_path(ca.certificate_path(), env) } pub(crate) fn upstream_tls_root_store_for_cert_path( @@ -360,6 +402,9 @@ fn is_current_generated_trust_bundle_path(path: &Path, managed_ca_cert_path: &Pa let Some(proxy_dir) = managed_ca_cert_path.parent() else { return false; }; + if is_generated_trust_bundle_path(path, proxy_dir) { + return true; + } let Some(file_name) = path.file_name().and_then(|file_name| file_name.to_str()) else { return false; }; @@ -381,12 +426,39 @@ fn is_current_generated_trust_bundle_path(path: &Path, managed_ca_cert_path: &Pa .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 { +fn is_generated_trust_bundle_path(path: &Path, proxy_dir: &Path) -> bool { + is_generated_managed_ca_artifact_path(path, proxy_dir, MANAGED_MITM_CA_TRUST_BUNDLE_PREFIX) +} + +fn is_generated_managed_ca_artifact_path(path: &Path, proxy_dir: &Path, prefix: &str) -> bool { + let Some(file_name) = path.file_name().and_then(|file_name| file_name.to_str()) else { return false; }; - is_current_generated_trust_bundle_path(Path::new(path), &managed_ca_cert_path) + let Some(expected_hash) = file_name + .strip_prefix(prefix) + .and_then(|suffix| suffix.strip_prefix('-')) + .and_then(|suffix| suffix.strip_suffix(".pem")) + else { + return false; + }; + if path.parent() != Some(proxy_dir) + || expected_hash.len() != 64 + || !expected_hash.bytes().all(|byte| byte.is_ascii_hexdigit()) + { + return false; + } + let Ok(trust_bundle) = fs::read(path) else { + return false; + }; + format!("{:x}", Sha256::digest(trust_bundle)) == expected_hash +} + +/// 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(proxy_dir) = managed_ca_dir() else { + return false; + }; + is_generated_trust_bundle_path(Path::new(path), &proxy_dir) } fn persist_managed_ca_trust_bundle( @@ -439,56 +511,160 @@ fn push_certificate_pem(bundle: &mut String, der: &[u8]) { bundle.push_str("-----END CERTIFICATE-----\n"); } -fn load_or_create_ca() -> Result<(String, String)> { - let (cert_path, key_path) = managed_ca_paths()?; - - if cert_path.exists() || key_path.exists() { - if !cert_path.exists() || !key_path.exists() { - return Err(anyhow!( - "both managed MITM CA files must exist (cert={}, key={})", - cert_path.display(), - key_path.display() - )); - } - validate_existing_ca_key_file(&key_path)?; - let cert_pem = fs::read_to_string(&cert_path) - .with_context(|| format!("failed to read CA cert {}", cert_path.display()))?; - let key_pem = fs::read_to_string(&key_path) - .with_context(|| format!("failed to read CA key {}", key_path.display()))?; - return Ok((cert_pem, key_pem)); - } - - if let Some(parent) = cert_path.parent() { - fs::create_dir_all(parent) - .with_context(|| format!("failed to create {}", parent.display()))?; - } - if let Some(parent) = key_path.parent() { - fs::create_dir_all(parent) - .with_context(|| format!("failed to create {}", parent.display()))?; - } - - let (cert_pem, key_pem) = generate_ca()?; - // The CA key is a high-value secret. Create it atomically with restrictive permissions. - // The cert can be world-readable, but we still write it atomically to avoid partial writes. - // - // We intentionally use create-new semantics: if a key already exists, we should not overwrite - // it silently (that would invalidate previously-trusted cert chains). - write_atomic_create_new(&key_path, key_pem.as_bytes(), /*mode*/ 0o600) - .with_context(|| format!("failed to persist CA key {}", key_path.display()))?; - if let Err(err) = write_atomic_create_new(&cert_path, cert_pem.as_bytes(), /*mode*/ 0o644) - .with_context(|| format!("failed to persist CA cert {}", cert_path.display())) - { - // Avoid leaving a partially-created CA around (cert missing) if the second write fails. - let _ = fs::remove_file(&key_path); - return Err(err); - } - let cert_path = cert_path.display(); - let key_path = key_path.display(); - info!("generated MITM CA (cert_path={cert_path}, key_path={key_path})"); - Ok((cert_pem, key_pem)) +fn persist_managed_ca_certificate(proxy_dir: &Path, cert_pem: &str) -> Result { + let hash = Sha256::digest(cert_pem.as_bytes()); + let cert_path = proxy_dir.join(format!("{MANAGED_MITM_CA_CERT_PREFIX}-{hash:x}.pem")); + write_atomic_create_new_or_reuse(&cert_path, cert_pem.as_bytes(), /*mode*/ 0o644) + .with_context(|| { + format!( + "failed to persist managed MITM CA certificate {}", + cert_path.display() + ) + })?; + Ok(cert_path) } -fn generate_ca() -> Result<(String, String)> { +fn lock_managed_ca_certificate(certificate_path: &Path) -> Result { + let lock_path = managed_ca_certificate_lock_path(certificate_path) + .ok_or_else(|| anyhow!("managed MITM CA certificate path is missing a file name"))?; + let file = open_managed_ca_lock(&lock_path)?; + file.lock_shared() + .with_context(|| format!("failed to lock {}", lock_path.display()))?; + Ok(file) +} + +fn lock_managed_ca_artifacts(proxy_dir: &Path) -> Result { + let lock_path = proxy_dir.join(MANAGED_MITM_CA_ARTIFACT_LOCK); + let file = open_managed_ca_lock(&lock_path)?; + file.lock() + .with_context(|| format!("failed to lock {}", lock_path.display()))?; + Ok(file) +} + +fn managed_ca_certificate_lock_path(certificate_path: &Path) -> Option { + let file_name = certificate_path.file_name()?.to_string_lossy(); + Some(certificate_path.with_file_name(format!(".{file_name}.lock"))) +} + +fn open_managed_ca_lock(path: &Path) -> Result { + if fs::symlink_metadata(path) + .ok() + .is_some_and(|metadata| metadata.file_type().is_symlink()) + { + return Err(anyhow!( + "refusing to use symlink lock file {}", + path.display() + )); + } + + #[cfg(unix)] + use std::os::unix::fs::OpenOptionsExt; + + let mut options = OpenOptions::new(); + options.read(true).write(true).create(true).truncate(false); + #[cfg(unix)] + options.mode(0o600); + options + .open(path) + .with_context(|| format!("failed to open {}", path.display())) +} + +fn prune_managed_ca_artifacts(proxy_dir: &Path) { + for certificate_path in + generated_managed_ca_artifact_paths(proxy_dir, MANAGED_MITM_CA_CERT_PREFIX) + { + remove_inactive_managed_ca_certificate(&certificate_path); + } + + let remaining_certificates = + generated_managed_ca_artifact_paths(proxy_dir, MANAGED_MITM_CA_CERT_PREFIX) + .into_iter() + .filter_map(|path| fs::read(path).ok()) + .filter(|certificate| !certificate.is_empty()) + .collect::>(); + let bundle_paths = + generated_managed_ca_artifact_paths(proxy_dir, MANAGED_MITM_CA_TRUST_BUNDLE_PREFIX); + for bundle_path in bundle_paths { + let Ok(contents) = fs::read(&bundle_path) else { + continue; + }; + if remaining_certificates.iter().any(|certificate| { + contents + .windows(certificate.len()) + .any(|window| window == certificate) + }) { + continue; + } + if let Err(err) = fs::remove_file(&bundle_path) + && err.kind() != std::io::ErrorKind::NotFound + { + warn!( + path = %bundle_path.display(), + "failed to prune stale managed MITM CA trust bundle: {err}" + ); + } + } +} + +fn generated_managed_ca_artifact_paths(proxy_dir: &Path, prefix: &str) -> Vec { + let Ok(entries) = fs::read_dir(proxy_dir) else { + return Vec::new(); + }; + entries + .filter_map(std::result::Result::ok) + .filter_map(|entry| { + let path = entry.path(); + if !is_generated_managed_ca_artifact_path(&path, proxy_dir, prefix) { + return None; + } + Some(path) + }) + .collect() +} + +fn remove_inactive_managed_ca_certificate(certificate_path: &Path) { + let Some(lock_path) = managed_ca_certificate_lock_path(certificate_path) else { + return; + }; + let Ok(lock_file) = open_managed_ca_lock(&lock_path) else { + return; + }; + match lock_file.try_lock() { + Ok(()) => {} + Err(std::fs::TryLockError::WouldBlock) => return, + Err(err) => { + warn!( + path = %lock_path.display(), + "failed to inspect managed MITM CA artifact lease: {err}" + ); + return; + } + } + + let removed = match fs::remove_file(certificate_path) { + Ok(()) => true, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => true, + Err(err) => { + warn!( + path = %certificate_path.display(), + "failed to prune stale managed MITM CA certificate: {err}" + ); + false + } + }; + drop(lock_file); + if removed + && let Err(err) = fs::remove_file(&lock_path) + && err.kind() != std::io::ErrorKind::NotFound + { + warn!( + path = %lock_path.display(), + "failed to prune stale managed MITM CA artifact lease: {err}" + ); + } +} + +fn generate_ca() -> Result<(String, KeyPair)> { let mut params = CertificateParams::default(); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); params.key_usages = vec![ @@ -505,7 +681,7 @@ fn generate_ca() -> Result<(String, String)> { let cert = params .self_signed(&key_pair) .map_err(|err| anyhow!("failed to generate CA cert: {err}"))?; - Ok((cert.pem(), key_pair.serialize_pem())) + Ok((cert.pem(), key_pair)) } fn write_atomic_create_new(path: &Path, contents: &[u8], mode: u32) -> Result<()> { @@ -604,41 +780,6 @@ fn write_atomic_create_new_or_reuse(path: &Path, contents: &[u8], mode: u32) -> } } -#[cfg(unix)] -fn validate_existing_ca_key_file(path: &Path) -> Result<()> { - use std::os::unix::fs::PermissionsExt; - - let metadata = fs::symlink_metadata(path) - .with_context(|| format!("failed to stat CA key {}", path.display()))?; - if metadata.file_type().is_symlink() { - return Err(anyhow!( - "refusing to use symlink for managed MITM CA key {}", - path.display() - )); - } - if !metadata.is_file() { - return Err(anyhow!( - "managed MITM CA key is not a regular file: {}", - path.display() - )); - } - - let mode = metadata.permissions().mode() & 0o777; - if mode & 0o077 != 0 { - return Err(anyhow!( - "managed MITM CA key {} must not be group/world accessible (mode={mode:o}; expected <= 600)", - path.display() - )); - } - - Ok(()) -} - -#[cfg(not(unix))] -fn validate_existing_ca_key_file(_path: &Path) -> Result<()> { - Ok(()) -} - #[cfg(unix)] fn open_create_new_with_mode(path: &Path, mode: u32) -> Result { use std::os::unix::fs::OpenOptionsExt; @@ -664,12 +805,83 @@ fn open_create_new_with_mode(path: &Path, _mode: u32) -> Result { mod tests { use super::*; - #[cfg(unix)] + use codex_utils_rustls_provider::ensure_rustls_crypto_provider; use pretty_assertions::assert_eq; - #[cfg(unix)] - use std::os::unix::fs::PermissionsExt; use tempfile::tempdir; + #[test] + fn managed_ca_private_key_is_not_persisted() { + ensure_rustls_crypto_provider(); + let dir = tempdir().unwrap(); + let ca = ManagedMitmCa::create(dir.path()).unwrap(); + ca.tls_acceptor_data_for_host("example.com").unwrap(); + let mut persisted_files = fs::read_dir(dir.path()) + .unwrap() + .map(|entry| entry.unwrap().path()) + .collect::>(); + persisted_files.sort(); + let mut expected_files = vec![ + ca.certificate_path().to_path_buf(), + managed_ca_certificate_lock_path(ca.certificate_path()).unwrap(), + dir.path().join(MANAGED_MITM_CA_ARTIFACT_LOCK), + ]; + expected_files.sort(); + + assert_eq!(persisted_files, expected_files); + assert_eq!( + fs::read(managed_ca_certificate_lock_path(ca.certificate_path()).unwrap()).unwrap(), + Vec::::new() + ); + } + + #[test] + fn managed_ca_artifact_pruning_preserves_only_active_certificates() { + let dir = tempdir().unwrap(); + let mut artifacts = Vec::new(); + let mut active_lease = None; + for index in 0..3 { + let certificate = format!("certificate {index}\n"); + let certificate_path = + persist_managed_ca_certificate(dir.path(), &certificate).unwrap(); + let lease = lock_managed_ca_certificate(&certificate_path).unwrap(); + if index == 0 { + active_lease = Some(lease); + } else { + drop(lease); + } + let bundle_path = persist_managed_ca_trust_bundle( + &certificate_path, + &format!("roots\n{certificate}"), + ) + .unwrap(); + artifacts.push((certificate_path, bundle_path)); + } + let unrelated_path = dir.path().join("ca-user.pem"); + fs::write(&unrelated_path, "user managed").unwrap(); + + prune_managed_ca_artifacts(dir.path()); + + let remaining_certificate_count = + generated_managed_ca_artifact_paths(dir.path(), MANAGED_MITM_CA_CERT_PREFIX).len(); + assert_eq!(remaining_certificate_count, 1); + assert!(artifacts[0].0.exists()); + assert!(artifacts[0].1.exists()); + assert!(!artifacts[1].0.exists()); + assert!(!artifacts[1].1.exists()); + assert!(!artifacts[2].0.exists()); + assert!(!artifacts[2].1.exists()); + assert!(unrelated_path.exists()); + + drop(active_lease.take()); + prune_managed_ca_artifacts(dir.path()); + + let remaining_certificates = + generated_managed_ca_artifact_paths(dir.path(), MANAGED_MITM_CA_CERT_PREFIX); + assert!(remaining_certificates.is_empty()); + assert!(!artifacts[0].0.exists()); + assert!(!artifacts[0].1.exists()); + } + #[test] fn current_generated_trust_bundle_path_rejects_stale_bundle() { let dir = tempdir().unwrap(); @@ -683,6 +895,24 @@ mod tests { )); } + #[test] + fn generated_trust_bundle_path_requires_matching_content_hash() { + let dir = tempdir().unwrap(); + let managed_ca_cert_path = dir.path().join("ca.pem"); + let trust_bundle_path = + persist_managed_ca_trust_bundle(&managed_ca_cert_path, "trusted roots").unwrap(); + + assert!(is_generated_trust_bundle_path( + &trust_bundle_path, + dir.path() + )); + fs::write(&trust_bundle_path, "tampered roots").unwrap(); + assert!(!is_generated_trust_bundle_path( + &trust_bundle_path, + dir.path() + )); + } + #[test] fn managed_ca_trust_bundle_appends_startup_file_and_directory_certificates() { let dir = tempdir().unwrap(); @@ -691,6 +921,7 @@ mod tests { let startup_ca_dir = dir.path().join("startup-certs"); let (managed_ca_cert, _) = generate_ca().unwrap(); let (startup_ca_cert, startup_ca_key) = generate_ca().unwrap(); + let startup_ca_key = startup_ca_key.serialize_pem(); let (directory_ca_cert, _) = generate_ca().unwrap(); let mut trusted_ca_der = CertificateDer::from_pem_slice(startup_ca_cert.as_bytes()) .unwrap() @@ -760,50 +991,6 @@ mod tests { assert_eq!(baseline_bundle.matches(&managed_ca_cert).count(), 1); } - #[cfg(unix)] - #[test] - fn validate_existing_ca_key_file_rejects_group_world_permissions() { - let dir = tempdir().unwrap(); - let key_path = dir.path().join("ca.key"); - fs::write(&key_path, "key").unwrap(); - fs::set_permissions(&key_path, fs::Permissions::from_mode(0o644)).unwrap(); - - let err = validate_existing_ca_key_file(&key_path).unwrap_err(); - assert!( - err.to_string().contains("group/world accessible"), - "unexpected error: {err:#}" - ); - } - - #[cfg(unix)] - #[test] - fn validate_existing_ca_key_file_rejects_symlink() { - use std::os::unix::fs::symlink; - - let dir = tempdir().unwrap(); - let target = dir.path().join("real.key"); - let link = dir.path().join("ca.key"); - fs::write(&target, "key").unwrap(); - symlink(&target, &link).unwrap(); - - let err = validate_existing_ca_key_file(&link).unwrap_err(); - assert!( - err.to_string().contains("symlink"), - "unexpected error: {err:#}" - ); - } - - #[cfg(unix)] - #[test] - fn validate_existing_ca_key_file_allows_private_permissions() { - let dir = tempdir().unwrap(); - let key_path = dir.path().join("ca.key"); - fs::write(&key_path, "key").unwrap(); - fs::set_permissions(&key_path, fs::Permissions::from_mode(0o600)).unwrap(); - - validate_existing_ca_key_file(&key_path).unwrap(); - } - #[cfg(unix)] #[test] fn write_atomic_create_new_or_reuse_rejects_matching_symlink_target() { diff --git a/codex-rs/network-proxy/src/mitm.rs b/codex-rs/network-proxy/src/mitm.rs index 70175ca70..6607d2ac0 100644 --- a/codex-rs/network-proxy/src/mitm.rs +++ b/codex-rs/network-proxy/src/mitm.rs @@ -53,7 +53,7 @@ use tracing::warn; /// State needed to terminate a CONNECT tunnel and enforce policy on inner HTTPS requests. pub struct MitmState { - ca: ManagedMitmCa, + ca: Arc, upstream: UpstreamClient, inspect: bool, max_body_bytes: usize, @@ -104,7 +104,7 @@ impl MitmState { // MITM exists when HTTPS policy depends on the inner request: limited-mode method clamps // and host-specific hooks both need visibility after CONNECT is established. We - // generate/load a local CA and issue per-host leaf certs so we can terminate TLS and + // generate a process-local CA and issue per-host leaf certs so we can terminate TLS and // apply policy. let ca = ManagedMitmCa::load_or_create()?; let upstream_tls_root_store =