mirror of
https://github.com/openai/codex.git
synced 2026-04-25 23:24:55 +00:00
feat: introducing a network sandbox proxy (#8442)
This add a new crate, `codex-network-proxy`, a local network proxy service used by Codex to enforce fine-grained network policy (domain allow/deny) and to surface blocked network events for interactive approvals. - New crate: `codex-rs/network-proxy/` (`codex-network-proxy` binary + library) - Core capabilities: - HTTP proxy support (including CONNECT tunneling) - SOCKS5 proxy support (in the later PR) - policy evaluation (allowed/denied domain lists; denylist wins; wildcard support) - small admin API for polling/reload/mode changes - optional MITM support for HTTPS CONNECT to enforce “limited mode” method restrictions (later PR) Will follow up integration with codex in subsequent PRs. ## Testing - `cd codex-rs && cargo build -p codex-network-proxy` - `cd codex-rs && cargo run -p codex-network-proxy -- proxy`
This commit is contained in:
176
codex-rs/network-proxy/src/proxy.rs
Normal file
176
codex-rs/network-proxy/src/proxy.rs
Normal file
@@ -0,0 +1,176 @@
|
||||
use crate::admin;
|
||||
use crate::config;
|
||||
use crate::http_proxy;
|
||||
use crate::network_policy::NetworkPolicyDecider;
|
||||
use crate::runtime::unix_socket_permissions_supported;
|
||||
use crate::state::NetworkProxyState;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use tokio::task::JoinHandle;
|
||||
use tracing::warn;
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[command(name = "codex-network-proxy", about = "Codex network sandbox proxy")]
|
||||
pub struct Args {}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct NetworkProxyBuilder {
|
||||
state: Option<Arc<NetworkProxyState>>,
|
||||
http_addr: Option<SocketAddr>,
|
||||
admin_addr: Option<SocketAddr>,
|
||||
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
|
||||
}
|
||||
|
||||
impl NetworkProxyBuilder {
|
||||
pub fn state(mut self, state: Arc<NetworkProxyState>) -> Self {
|
||||
self.state = Some(state);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn http_addr(mut self, addr: SocketAddr) -> Self {
|
||||
self.http_addr = Some(addr);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn admin_addr(mut self, addr: SocketAddr) -> Self {
|
||||
self.admin_addr = Some(addr);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn policy_decider<D>(mut self, decider: D) -> Self
|
||||
where
|
||||
D: NetworkPolicyDecider,
|
||||
{
|
||||
self.policy_decider = Some(Arc::new(decider));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn policy_decider_arc(mut self, decider: Arc<dyn NetworkPolicyDecider>) -> Self {
|
||||
self.policy_decider = Some(decider);
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn build(self) -> Result<NetworkProxy> {
|
||||
let state = match self.state {
|
||||
Some(state) => state,
|
||||
None => Arc::new(NetworkProxyState::new().await?),
|
||||
};
|
||||
let current_cfg = state.current_cfg().await?;
|
||||
let runtime = config::resolve_runtime(¤t_cfg)?;
|
||||
// Reapply bind clamping for caller overrides so unix-socket proxying stays loopback-only.
|
||||
let (http_addr, admin_addr) = config::clamp_bind_addrs(
|
||||
self.http_addr.unwrap_or(runtime.http_addr),
|
||||
self.admin_addr.unwrap_or(runtime.admin_addr),
|
||||
¤t_cfg.network_proxy,
|
||||
);
|
||||
|
||||
Ok(NetworkProxy {
|
||||
state,
|
||||
http_addr,
|
||||
admin_addr,
|
||||
policy_decider: self.policy_decider,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NetworkProxy {
|
||||
state: Arc<NetworkProxyState>,
|
||||
http_addr: SocketAddr,
|
||||
admin_addr: SocketAddr,
|
||||
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
|
||||
}
|
||||
|
||||
impl NetworkProxy {
|
||||
pub fn builder() -> NetworkProxyBuilder {
|
||||
NetworkProxyBuilder::default()
|
||||
}
|
||||
|
||||
pub async fn run(&self) -> Result<NetworkProxyHandle> {
|
||||
let current_cfg = self.state.current_cfg().await?;
|
||||
if !current_cfg.network_proxy.enabled {
|
||||
warn!("network_proxy.enabled is false; skipping proxy listeners");
|
||||
return Ok(NetworkProxyHandle::noop());
|
||||
}
|
||||
|
||||
if !unix_socket_permissions_supported() {
|
||||
warn!("allowUnixSockets is macOS-only; requests will be rejected on this platform");
|
||||
}
|
||||
|
||||
let http_task = tokio::spawn(http_proxy::run_http_proxy(
|
||||
self.state.clone(),
|
||||
self.http_addr,
|
||||
self.policy_decider.clone(),
|
||||
));
|
||||
let admin_task = tokio::spawn(admin::run_admin_api(self.state.clone(), self.admin_addr));
|
||||
|
||||
Ok(NetworkProxyHandle {
|
||||
http_task: Some(http_task),
|
||||
admin_task: Some(admin_task),
|
||||
completed: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NetworkProxyHandle {
|
||||
http_task: Option<JoinHandle<Result<()>>>,
|
||||
admin_task: Option<JoinHandle<Result<()>>>,
|
||||
completed: bool,
|
||||
}
|
||||
|
||||
impl NetworkProxyHandle {
|
||||
fn noop() -> Self {
|
||||
Self {
|
||||
http_task: Some(tokio::spawn(async { Ok(()) })),
|
||||
admin_task: Some(tokio::spawn(async { Ok(()) })),
|
||||
completed: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn wait(mut self) -> Result<()> {
|
||||
let http_task = self.http_task.take().context("missing http proxy task")?;
|
||||
let admin_task = self.admin_task.take().context("missing admin proxy task")?;
|
||||
let http_result = http_task.await;
|
||||
let admin_result = admin_task.await;
|
||||
self.completed = true;
|
||||
http_result??;
|
||||
admin_result??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn shutdown(mut self) -> Result<()> {
|
||||
abort_tasks(self.http_task.take(), self.admin_task.take()).await;
|
||||
self.completed = true;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn abort_tasks(
|
||||
http_task: Option<JoinHandle<Result<()>>>,
|
||||
admin_task: Option<JoinHandle<Result<()>>>,
|
||||
) {
|
||||
if let Some(http_task) = http_task {
|
||||
http_task.abort();
|
||||
let _ = http_task.await;
|
||||
}
|
||||
if let Some(admin_task) = admin_task {
|
||||
admin_task.abort();
|
||||
let _ = admin_task.await;
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for NetworkProxyHandle {
|
||||
fn drop(&mut self) {
|
||||
if self.completed {
|
||||
return;
|
||||
}
|
||||
let http_task = self.http_task.take();
|
||||
let admin_task = self.admin_task.take();
|
||||
tokio::spawn(async move {
|
||||
abort_tasks(http_task, admin_task).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user