refactor: remove proxy admin endpoint (#13687)

## Summary
- delete the network proxy admin server and its runtime listener/task
plumbing
- remove the admin endpoint config, runtime, requirement, protocol,
schema, and debug-surface fields
- update proxy docs to reflect the remaining HTTP and SOCKS listeners
only
This commit is contained in:
viyatb-oai
2026-03-05 22:03:16 -08:00
committed by GitHub
Unverified
parent f9ce403b5a
commit 6a79ed5920
24 changed files with 30 additions and 476 deletions
@@ -5609,9 +5609,6 @@
},
"SessionNetworkProxyRuntime": {
"properties": {
"admin_addr": {
"type": "string"
},
"http_addr": {
"type": "string"
},
@@ -5620,7 +5617,6 @@
}
},
"required": [
"admin_addr",
"http_addr",
"socks_addr"
],
@@ -7261,9 +7261,6 @@
},
"SessionNetworkProxyRuntime": {
"properties": {
"admin_addr": {
"type": "string"
},
"http_addr": {
"type": "string"
},
@@ -7272,7 +7269,6 @@
}
},
"required": [
"admin_addr",
"http_addr",
"socks_addr"
],
@@ -11023,12 +11019,6 @@
"null"
]
},
"dangerouslyAllowNonLoopbackAdmin": {
"type": [
"boolean",
"null"
]
},
"dangerouslyAllowNonLoopbackProxy": {
"type": [
"boolean",
@@ -8058,12 +8058,6 @@
"null"
]
},
"dangerouslyAllowNonLoopbackAdmin": {
"type": [
"boolean",
"null"
]
},
"dangerouslyAllowNonLoopbackProxy": {
"type": [
"boolean",
@@ -11094,9 +11088,6 @@
},
"SessionNetworkProxyRuntime": {
"properties": {
"admin_addr": {
"type": "string"
},
"http_addr": {
"type": "string"
},
@@ -11105,7 +11096,6 @@
}
},
"required": [
"admin_addr",
"http_addr",
"socks_addr"
],
@@ -132,12 +132,6 @@
"null"
]
},
"dangerouslyAllowNonLoopbackAdmin": {
"type": [
"boolean",
"null"
]
},
"dangerouslyAllowNonLoopbackProxy": {
"type": [
"boolean",
@@ -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 SessionNetworkProxyRuntime = { http_addr: string, socks_addr: string, admin_addr: string, };
export type SessionNetworkProxyRuntime = { http_addr: string, socks_addr: string, };
@@ -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, dangerouslyAllowNonLoopbackAdmin: 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, allowedDomains: Array<string> | null, deniedDomains: Array<string> | null, allowUnixSockets: Array<string> | null, allowLocalBinding: boolean | null, };
@@ -629,7 +629,6 @@ pub struct NetworkRequirements {
pub socks_port: Option<u16>,
pub allow_upstream_proxy: Option<bool>,
pub dangerously_allow_non_loopback_proxy: Option<bool>,
pub dangerously_allow_non_loopback_admin: Option<bool>,
pub dangerously_allow_all_unix_sockets: Option<bool>,
pub allowed_domains: Option<Vec<String>>,
pub denied_domains: Option<Vec<String>>,
-3
View File
@@ -163,7 +163,6 @@ fn map_network_requirements_to_api(
socks_port: network.socks_port,
allow_upstream_proxy: network.allow_upstream_proxy,
dangerously_allow_non_loopback_proxy: network.dangerously_allow_non_loopback_proxy,
dangerously_allow_non_loopback_admin: network.dangerously_allow_non_loopback_admin,
dangerously_allow_all_unix_sockets: network.dangerously_allow_all_unix_sockets,
allowed_domains: network.allowed_domains,
denied_domains: network.denied_domains,
@@ -230,7 +229,6 @@ mod tests {
socks_port: Some(1080),
allow_upstream_proxy: Some(false),
dangerously_allow_non_loopback_proxy: Some(false),
dangerously_allow_non_loopback_admin: Some(false),
dangerously_allow_all_unix_sockets: Some(true),
allowed_domains: Some(vec!["api.openai.com".to_string()]),
denied_domains: Some(vec!["example.com".to_string()]),
@@ -275,7 +273,6 @@ mod tests {
socks_port: Some(1080),
allow_upstream_proxy: Some(false),
dangerously_allow_non_loopback_proxy: Some(false),
dangerously_allow_non_loopback_admin: Some(false),
dangerously_allow_all_unix_sockets: Some(true),
allowed_domains: Some(vec!["api.openai.com".to_string()]),
denied_domains: Some(vec!["example.com".to_string()]),
@@ -136,7 +136,6 @@ pub struct NetworkRequirementsToml {
pub socks_port: Option<u16>,
pub allow_upstream_proxy: Option<bool>,
pub dangerously_allow_non_loopback_proxy: Option<bool>,
pub dangerously_allow_non_loopback_admin: Option<bool>,
pub dangerously_allow_all_unix_sockets: Option<bool>,
pub allowed_domains: Option<Vec<String>>,
pub denied_domains: Option<Vec<String>>,
@@ -152,7 +151,6 @@ pub struct NetworkConstraints {
pub socks_port: Option<u16>,
pub allow_upstream_proxy: Option<bool>,
pub dangerously_allow_non_loopback_proxy: Option<bool>,
pub dangerously_allow_non_loopback_admin: Option<bool>,
pub dangerously_allow_all_unix_sockets: Option<bool>,
pub allowed_domains: Option<Vec<String>>,
pub denied_domains: Option<Vec<String>>,
@@ -168,7 +166,6 @@ impl From<NetworkRequirementsToml> for NetworkConstraints {
socks_port,
allow_upstream_proxy,
dangerously_allow_non_loopback_proxy,
dangerously_allow_non_loopback_admin,
dangerously_allow_all_unix_sockets,
allowed_domains,
denied_domains,
@@ -181,7 +178,6 @@ impl From<NetworkRequirementsToml> for NetworkConstraints {
socks_port,
allow_upstream_proxy,
dangerously_allow_non_loopback_proxy,
dangerously_allow_non_loopback_admin,
dangerously_allow_all_unix_sockets,
allowed_domains,
denied_domains,
-6
View File
@@ -794,9 +794,6 @@
"NetworkToml": {
"additionalProperties": false,
"properties": {
"admin_url": {
"type": "string"
},
"allow_local_binding": {
"type": "boolean"
},
@@ -818,9 +815,6 @@
"dangerously_allow_all_unix_sockets": {
"type": "boolean"
},
"dangerously_allow_non_loopback_admin": {
"type": "boolean"
},
"dangerously_allow_non_loopback_proxy": {
"type": "boolean"
},
-1
View File
@@ -1017,7 +1017,6 @@ impl Session {
SessionNetworkProxyRuntime {
http_addr: proxy.http_addr().to_string(),
socks_addr: proxy.socks_addr().to_string(),
admin_addr: proxy.admin_addr().to_string(),
}
};
Ok((network_proxy, session_network_proxy))
-2
View File
@@ -2727,13 +2727,11 @@ allowed_domains = ["openai.com"]
NetworkToml {
enabled: Some(true),
proxy_url: Some("http://127.0.0.1:43128".to_string()),
admin_url: None,
enable_socks5: Some(false),
socks_url: None,
enable_socks5_udp: None,
allow_upstream_proxy: Some(false),
dangerously_allow_non_loopback_proxy: None,
dangerously_allow_non_loopback_admin: None,
dangerously_allow_all_unix_sockets: None,
mode: None,
allowed_domains: Some(vec!["openai.com".to_string()]),
@@ -183,14 +183,6 @@ impl NetworkProxySpec {
constraints.dangerously_allow_non_loopback_proxy =
Some(dangerously_allow_non_loopback_proxy);
}
if let Some(dangerously_allow_non_loopback_admin) =
requirements.dangerously_allow_non_loopback_admin
{
config.network.dangerously_allow_non_loopback_admin =
dangerously_allow_non_loopback_admin;
constraints.dangerously_allow_non_loopback_admin =
Some(dangerously_allow_non_loopback_admin);
}
if let Some(dangerously_allow_all_unix_sockets) =
requirements.dangerously_allow_all_unix_sockets
{
-11
View File
@@ -17,13 +17,11 @@ pub struct PermissionsToml {
pub struct NetworkToml {
pub enabled: Option<bool>,
pub proxy_url: Option<String>,
pub admin_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_non_loopback_admin: Option<bool>,
pub dangerously_allow_all_unix_sockets: Option<bool>,
#[schemars(with = "Option<NetworkModeSchema>")]
pub mode: Option<NetworkMode>,
@@ -48,9 +46,6 @@ impl NetworkToml {
if let Some(proxy_url) = self.proxy_url.as_ref() {
config.network.proxy_url = proxy_url.clone();
}
if let Some(admin_url) = self.admin_url.as_ref() {
config.network.admin_url = admin_url.clone();
}
if let Some(enable_socks5) = self.enable_socks5 {
config.network.enable_socks5 = enable_socks5;
}
@@ -69,12 +64,6 @@ impl NetworkToml {
config.network.dangerously_allow_non_loopback_proxy =
dangerously_allow_non_loopback_proxy;
}
if let Some(dangerously_allow_non_loopback_admin) =
self.dangerously_allow_non_loopback_admin
{
config.network.dangerously_allow_non_loopback_admin =
dangerously_allow_non_loopback_admin;
}
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;
}
@@ -146,11 +146,6 @@ fn apply_network_constraints(network: NetworkToml, constraints: &mut NetworkProx
constraints.dangerously_allow_non_loopback_proxy =
Some(dangerously_allow_non_loopback_proxy);
}
if let Some(dangerously_allow_non_loopback_admin) = network.dangerously_allow_non_loopback_admin
{
constraints.dangerously_allow_non_loopback_admin =
Some(dangerously_allow_non_loopback_admin);
}
if let Some(dangerously_allow_all_unix_sockets) = network.dangerously_allow_all_unix_sockets {
constraints.dangerously_allow_all_unix_sockets = Some(dangerously_allow_all_unix_sockets);
}
+3 -28
View File
@@ -4,7 +4,6 @@
- an HTTP proxy (default `127.0.0.1:3128`)
- a SOCKS5 proxy (default `127.0.0.1:8081`, enabled by default)
- an admin HTTP API (default `127.0.0.1:8080`)
It enforces an allow/deny policy and a "limited" mode intended for read-only network access.
@@ -20,7 +19,6 @@ Example config:
[network]
enabled = true
proxy_url = "http://127.0.0.1:3128"
admin_url = "http://127.0.0.1:8080"
# SOCKS5 listener (enabled by default).
enable_socks5 = true
socks_url = "http://127.0.0.1:8081"
@@ -32,7 +30,6 @@ allow_upstream_proxy = true
# By default, non-loopback binds are clamped to loopback for safety.
# If you want to expose these listeners beyond localhost, you must opt in explicitly.
dangerously_allow_non_loopback_proxy = false
dangerously_allow_non_loopback_admin = false
mode = "full" # default when unset; use "limited" for read-only mode
# When true, HTTPS CONNECT can be terminated so limited-mode method policy still applies.
mitm = false
@@ -104,7 +101,6 @@ use codex_network_proxy::{NetworkProxy, NetworkDecision, NetworkPolicyRequest};
let proxy = NetworkProxy::builder()
.http_addr("127.0.0.1:8080".parse()?)
.admin_addr("127.0.0.1:9000".parse()?)
.policy_decider(|request: NetworkPolicyRequest| async move {
// Example: auto-allow when exec policy already approved a command prefix.
if let Some(command) = request.command.as_deref() {
@@ -124,7 +120,7 @@ handle.shutdown().await?;
```
When unix socket proxying is enabled (`allow_unix_sockets` or
`dangerously_allow_all_unix_sockets`), HTTP/admin bind overrides are still clamped to loopback to
`dangerously_allow_all_unix_sockets`), proxy bind overrides are still clamped to loopback to
avoid turning the proxy into a remote bridge to local daemons.
### Policy hook (exec-policy mapping)
@@ -176,25 +172,6 @@ Unix-socket block-path audits use sentinel endpoint values:
Audit events intentionally avoid logging full URL/path/query data.
## Admin API
The admin API is a small HTTP server intended for debugging and runtime adjustments.
Endpoints:
```bash
curl -sS http://127.0.0.1:8080/health
curl -sS http://127.0.0.1:8080/config
curl -sS http://127.0.0.1:8080/patterns
curl -sS http://127.0.0.1:8080/blocked
# Switch modes without restarting:
curl -sS -X POST http://127.0.0.1:8080/mode -d '{"mode":"full"}'
# Force a config reload:
curl -sS -X POST http://127.0.0.1:8080/reload
```
## Platform notes
- Unix socket proxying via the `x-unix-socket` header is **macOS-only**; other platforms will
@@ -217,11 +194,9 @@ what it can reasonably guarantee.
- only `GET`, `HEAD`, and `OPTIONS` are allowed
- HTTPS `CONNECT` remains a tunnel; limited-mode method enforcement does not apply to HTTPS
- Listener safety defaults:
- the admin API is unauthenticated; non-loopback binds are clamped unless explicitly enabled via
`dangerously_allow_non_loopback_admin`
- the HTTP proxy listener similarly clamps non-loopback binds unless explicitly enabled via
- the HTTP proxy listener clamps non-loopback binds unless explicitly enabled via
`dangerously_allow_non_loopback_proxy`
- when unix socket proxying is enabled, both listeners are forced to loopback to avoid turning the
- when unix socket proxying is enabled, all proxy listeners are forced to loopback to avoid turning the
proxy into a remote bridge into local daemons.
- `dangerously_allow_all_unix_sockets = true` bypasses the unix socket allowlist entirely (still
macOS-only and absolute-path-only). Use only in tightly controlled environments.
-181
View File
@@ -1,181 +0,0 @@
use crate::config::NetworkMode;
use crate::responses::json_response;
use crate::responses::text_response;
use crate::state::NetworkProxyState;
use anyhow::Context;
use anyhow::Result;
use rama_core::rt::Executor;
use rama_core::service::service_fn;
use rama_http::Body;
use rama_http::Request;
use rama_http::Response;
use rama_http::StatusCode;
use rama_http_backend::server::HttpServer;
use rama_tcp::server::TcpListener;
use serde::Deserialize;
use serde::Serialize;
use std::convert::Infallible;
use std::net::SocketAddr;
use std::net::TcpListener as StdTcpListener;
use std::sync::Arc;
use tracing::error;
use tracing::info;
pub async fn run_admin_api(state: Arc<NetworkProxyState>, addr: SocketAddr) -> Result<()> {
// Debug-only admin API (health/config/patterns/blocked + mode/reload). Policy is config-driven
// and constraint-enforced; this endpoint should not become a second policy/approval plane.
let listener = TcpListener::build()
.bind(addr)
.await
// See `http_proxy.rs` for details on why we wrap `BoxError` before converting to anyhow.
.map_err(rama_core::error::OpaqueError::from)
.map_err(anyhow::Error::from)
.with_context(|| format!("bind admin API: {addr}"))?;
run_admin_api_with_listener(state, listener).await
}
pub async fn run_admin_api_with_std_listener(
state: Arc<NetworkProxyState>,
listener: StdTcpListener,
) -> Result<()> {
let listener =
TcpListener::try_from(listener).context("convert std listener to admin API listener")?;
run_admin_api_with_listener(state, listener).await
}
async fn run_admin_api_with_listener(
state: Arc<NetworkProxyState>,
listener: TcpListener,
) -> Result<()> {
let addr = listener
.local_addr()
.context("read admin API listener local addr")?;
let server_state = state.clone();
let server = HttpServer::auto(Executor::new()).service(service_fn(move |req| {
let state = server_state.clone();
async move { handle_admin_request(state, req).await }
}));
info!("admin API listening on {addr}");
listener.serve(server).await;
Ok(())
}
async fn handle_admin_request(
state: Arc<NetworkProxyState>,
req: Request,
) -> Result<Response, Infallible> {
const MODE_BODY_LIMIT: usize = 8 * 1024;
let method = req.method().clone();
let path = req.uri().path().to_string();
let response = match (method.as_str(), path.as_str()) {
("GET", "/health") => Response::new(Body::from("ok")),
("GET", "/config") => match state.current_cfg().await {
Ok(cfg) => json_response(&cfg),
Err(err) => {
error!("failed to load config: {err}");
text_response(StatusCode::INTERNAL_SERVER_ERROR, "error")
}
},
("GET", "/patterns") => match state.current_patterns().await {
Ok((allow, deny)) => json_response(&PatternsResponse {
allowed: allow,
denied: deny,
}),
Err(err) => {
error!("failed to load patterns: {err}");
text_response(StatusCode::INTERNAL_SERVER_ERROR, "error")
}
},
("GET", "/blocked") => match state.blocked_snapshot().await {
Ok(blocked) => json_response(&BlockedResponse { blocked }),
Err(err) => {
error!("failed to read blocked queue: {err}");
text_response(StatusCode::INTERNAL_SERVER_ERROR, "error")
}
},
("POST", "/mode") => {
let mut body = req.into_body();
let mut buf: Vec<u8> = Vec::new();
loop {
let chunk = match body.chunk().await {
Ok(chunk) => chunk,
Err(err) => {
error!("failed to read mode body: {err}");
return Ok(text_response(StatusCode::BAD_REQUEST, "invalid body"));
}
};
let Some(chunk) = chunk else {
break;
};
if buf.len().saturating_add(chunk.len()) > MODE_BODY_LIMIT {
return Ok(text_response(
StatusCode::PAYLOAD_TOO_LARGE,
"body too large",
));
}
buf.extend_from_slice(&chunk);
}
if buf.is_empty() {
return Ok(text_response(StatusCode::BAD_REQUEST, "missing body"));
}
let update: ModeUpdate = match serde_json::from_slice(&buf) {
Ok(update) => update,
Err(err) => {
error!("failed to parse mode update: {err}");
return Ok(text_response(StatusCode::BAD_REQUEST, "invalid json"));
}
};
match state.set_network_mode(update.mode).await {
Ok(()) => json_response(&ModeUpdateResponse {
status: "ok",
mode: update.mode,
}),
Err(err) => {
error!("mode update failed: {err}");
text_response(StatusCode::INTERNAL_SERVER_ERROR, "mode update failed")
}
}
}
("POST", "/reload") => match state.force_reload().await {
Ok(()) => json_response(&ReloadResponse { status: "reloaded" }),
Err(err) => {
error!("reload failed: {err}");
text_response(StatusCode::INTERNAL_SERVER_ERROR, "reload failed")
}
},
_ => text_response(StatusCode::NOT_FOUND, "not found"),
};
Ok(response)
}
#[derive(Deserialize)]
struct ModeUpdate {
mode: NetworkMode,
}
#[derive(Debug, Serialize)]
struct PatternsResponse {
allowed: Vec<String>,
denied: Vec<String>,
}
#[derive(Debug, Serialize)]
struct BlockedResponse<T> {
blocked: T,
}
#[derive(Debug, Serialize)]
struct ModeUpdateResponse {
status: &'static str,
mode: NetworkMode,
}
#[derive(Debug, Serialize)]
struct ReloadResponse {
status: &'static str,
}
+17 -51
View File
@@ -23,8 +23,6 @@ pub struct NetworkProxySettings {
pub enabled: bool,
#[serde(default = "default_proxy_url")]
pub proxy_url: String,
#[serde(default = "default_admin_url")]
pub admin_url: String,
pub enable_socks5: bool,
#[serde(default = "default_socks_url")]
pub socks_url: String,
@@ -33,8 +31,6 @@ pub struct NetworkProxySettings {
#[serde(default)]
pub dangerously_allow_non_loopback_proxy: bool,
#[serde(default)]
pub dangerously_allow_non_loopback_admin: bool,
#[serde(default)]
pub dangerously_allow_all_unix_sockets: bool,
#[serde(default)]
pub mode: NetworkMode,
@@ -54,13 +50,11 @@ impl Default for NetworkProxySettings {
Self {
enabled: false,
proxy_url: default_proxy_url(),
admin_url: default_admin_url(),
enable_socks5: true,
socks_url: default_socks_url(),
enable_socks5_udp: true,
allow_upstream_proxy: true,
dangerously_allow_non_loopback_proxy: false,
dangerously_allow_non_loopback_admin: false,
dangerously_allow_all_unix_sockets: false,
mode: NetworkMode::default(),
allowed_domains: Vec::new(),
@@ -98,16 +92,17 @@ fn default_proxy_url() -> String {
"http://127.0.0.1:3128".to_string()
}
fn default_admin_url() -> String {
"http://127.0.0.1:8080".to_string()
}
fn default_socks_url() -> String {
"http://127.0.0.1:8081".to_string()
}
/// Clamp non-loopback bind addresses to loopback unless explicitly allowed.
fn clamp_non_loopback(addr: SocketAddr, allow_non_loopback: bool, name: &str) -> SocketAddr {
fn clamp_non_loopback(
addr: SocketAddr,
allow_non_loopback: bool,
name: &str,
override_setting_name: &str,
) -> SocketAddr {
if addr.ip().is_loopback() {
return addr;
}
@@ -118,7 +113,7 @@ fn clamp_non_loopback(addr: SocketAddr, allow_non_loopback: bool, name: &str) ->
}
warn!(
"{name} requested non-loopback bind ({addr}); clamping to 127.0.0.1:{port} (set dangerously_allow_non_loopback_proxy or dangerously_allow_non_loopback_admin to override)",
"{name} requested non-loopback bind ({addr}); clamping to 127.0.0.1:{port} (set {override_setting_name} to override)",
port = addr.port()
);
SocketAddr::from(([127, 0, 0, 1], addr.port()))
@@ -127,30 +122,26 @@ fn clamp_non_loopback(addr: SocketAddr, allow_non_loopback: bool, name: &str) ->
pub(crate) fn clamp_bind_addrs(
http_addr: SocketAddr,
socks_addr: SocketAddr,
admin_addr: SocketAddr,
cfg: &NetworkProxySettings,
) -> (SocketAddr, SocketAddr, SocketAddr) {
) -> (SocketAddr, SocketAddr) {
let http_addr = clamp_non_loopback(
http_addr,
cfg.dangerously_allow_non_loopback_proxy,
"HTTP proxy",
"dangerously_allow_non_loopback_proxy",
);
let socks_addr = clamp_non_loopback(
socks_addr,
cfg.dangerously_allow_non_loopback_proxy,
"SOCKS5 proxy",
);
let admin_addr = clamp_non_loopback(
admin_addr,
cfg.dangerously_allow_non_loopback_admin,
"admin API",
"dangerously_allow_non_loopback_proxy",
);
if cfg.allow_unix_sockets.is_empty() && !cfg.dangerously_allow_all_unix_sockets {
return (http_addr, socks_addr, admin_addr);
return (http_addr, socks_addr);
}
// `x-unix-socket` is intentionally a local escape hatch. If the proxy (or admin API) is
// reachable from outside the machine, it can become a remote bridge into local daemons
// `x-unix-socket` is intentionally a local escape hatch. If the proxy is reachable from
// outside the machine, it can become a remote bridge into local daemons
// (e.g. docker.sock). To avoid footguns, enforce loopback binding whenever unix sockets
// are enabled.
if cfg.dangerously_allow_non_loopback_proxy && !http_addr.ip().is_loopback() {
@@ -163,22 +154,15 @@ pub(crate) fn clamp_bind_addrs(
"unix socket proxying is enabled; ignoring dangerously_allow_non_loopback_proxy and clamping SOCKS5 proxy to loopback"
);
}
if cfg.dangerously_allow_non_loopback_admin && !admin_addr.ip().is_loopback() {
warn!(
"unix socket proxying is enabled; ignoring dangerously_allow_non_loopback_admin and clamping admin API to loopback"
);
}
(
SocketAddr::from(([127, 0, 0, 1], http_addr.port())),
SocketAddr::from(([127, 0, 0, 1], socks_addr.port())),
SocketAddr::from(([127, 0, 0, 1], admin_addr.port())),
)
}
pub struct RuntimeConfig {
pub http_addr: SocketAddr,
pub socks_addr: SocketAddr,
pub admin_addr: SocketAddr,
}
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -228,15 +212,11 @@ pub fn resolve_runtime(cfg: &NetworkProxyConfig) -> Result<RuntimeConfig> {
.with_context(|| format!("invalid network.proxy_url: {}", cfg.network.proxy_url))?;
let socks_addr = resolve_addr(&cfg.network.socks_url, 8081)
.with_context(|| format!("invalid network.socks_url: {}", cfg.network.socks_url))?;
let admin_addr = resolve_addr(&cfg.network.admin_url, 8080)
.with_context(|| format!("invalid network.admin_url: {}", cfg.network.admin_url))?;
let (http_addr, socks_addr, admin_addr) =
clamp_bind_addrs(http_addr, socks_addr, admin_addr, &cfg.network);
let (http_addr, socks_addr) = clamp_bind_addrs(http_addr, socks_addr, &cfg.network);
Ok(RuntimeConfig {
http_addr,
socks_addr,
admin_addr,
})
}
@@ -384,13 +364,11 @@ mod tests {
NetworkProxySettings {
enabled: false,
proxy_url: "http://127.0.0.1:3128".to_string(),
admin_url: "http://127.0.0.1:8080".to_string(),
enable_socks5: true,
socks_url: "http://127.0.0.1:8081".to_string(),
enable_socks5_udp: true,
allow_upstream_proxy: true,
dangerously_allow_non_loopback_proxy: false,
dangerously_allow_non_loopback_admin: false,
dangerously_allow_all_unix_sockets: false,
mode: NetworkMode::Full,
allowed_domains: Vec::new(),
@@ -545,59 +523,47 @@ mod tests {
fn clamp_bind_addrs_allows_non_loopback_when_enabled() {
let cfg = NetworkProxySettings {
dangerously_allow_non_loopback_proxy: true,
dangerously_allow_non_loopback_admin: true,
..Default::default()
};
let http_addr = "0.0.0.0:3128".parse::<SocketAddr>().unwrap();
let socks_addr = "0.0.0.0:8081".parse::<SocketAddr>().unwrap();
let admin_addr = "0.0.0.0:8080".parse::<SocketAddr>().unwrap();
let (http_addr, socks_addr, admin_addr) =
clamp_bind_addrs(http_addr, socks_addr, admin_addr, &cfg);
let (http_addr, socks_addr) = clamp_bind_addrs(http_addr, socks_addr, &cfg);
assert_eq!(http_addr, "0.0.0.0:3128".parse::<SocketAddr>().unwrap());
assert_eq!(socks_addr, "0.0.0.0:8081".parse::<SocketAddr>().unwrap());
assert_eq!(admin_addr, "0.0.0.0:8080".parse::<SocketAddr>().unwrap());
}
#[test]
fn clamp_bind_addrs_forces_loopback_when_unix_sockets_enabled() {
let cfg = NetworkProxySettings {
dangerously_allow_non_loopback_proxy: true,
dangerously_allow_non_loopback_admin: true,
allow_unix_sockets: vec!["/tmp/docker.sock".to_string()],
..Default::default()
};
let http_addr = "0.0.0.0:3128".parse::<SocketAddr>().unwrap();
let socks_addr = "0.0.0.0:8081".parse::<SocketAddr>().unwrap();
let admin_addr = "0.0.0.0:8080".parse::<SocketAddr>().unwrap();
let (http_addr, socks_addr, admin_addr) =
clamp_bind_addrs(http_addr, socks_addr, admin_addr, &cfg);
let (http_addr, socks_addr) = clamp_bind_addrs(http_addr, socks_addr, &cfg);
assert_eq!(http_addr, "127.0.0.1:3128".parse::<SocketAddr>().unwrap());
assert_eq!(socks_addr, "127.0.0.1:8081".parse::<SocketAddr>().unwrap());
assert_eq!(admin_addr, "127.0.0.1:8080".parse::<SocketAddr>().unwrap());
}
#[test]
fn clamp_bind_addrs_forces_loopback_when_all_unix_sockets_enabled() {
let cfg = NetworkProxySettings {
dangerously_allow_non_loopback_proxy: true,
dangerously_allow_non_loopback_admin: true,
dangerously_allow_all_unix_sockets: true,
..Default::default()
};
let http_addr = "0.0.0.0:3128".parse::<SocketAddr>().unwrap();
let socks_addr = "0.0.0.0:8081".parse::<SocketAddr>().unwrap();
let admin_addr = "0.0.0.0:8080".parse::<SocketAddr>().unwrap();
let (http_addr, socks_addr, admin_addr) =
clamp_bind_addrs(http_addr, socks_addr, admin_addr, &cfg);
let (http_addr, socks_addr) = clamp_bind_addrs(http_addr, socks_addr, &cfg);
assert_eq!(http_addr, "127.0.0.1:3128".parse::<SocketAddr>().unwrap());
assert_eq!(socks_addr, "127.0.0.1:8081".parse::<SocketAddr>().unwrap());
assert_eq!(admin_addr, "127.0.0.1:8080".parse::<SocketAddr>().unwrap());
}
#[test]
-1
View File
@@ -1,6 +1,5 @@
#![deny(clippy::print_stdout, clippy::print_stderr)]
mod admin;
mod certs;
mod config;
mod http_proxy;
+8 -76
View File
@@ -1,4 +1,3 @@
use crate::admin;
use crate::config;
use crate::http_proxy;
use crate::network_policy::NetworkPolicyDecider;
@@ -26,15 +25,13 @@ pub struct Args {}
struct ReservedListeners {
http: Mutex<Option<StdTcpListener>>,
socks: Mutex<Option<StdTcpListener>>,
admin: Mutex<Option<StdTcpListener>>,
}
impl ReservedListeners {
fn new(http: StdTcpListener, socks: Option<StdTcpListener>, admin: StdTcpListener) -> Self {
fn new(http: StdTcpListener, socks: Option<StdTcpListener>) -> Self {
Self {
http: Mutex::new(Some(http)),
socks: Mutex::new(socks),
admin: Mutex::new(Some(admin)),
}
}
@@ -53,14 +50,6 @@ impl ReservedListeners {
.unwrap_or_else(std::sync::PoisonError::into_inner);
guard.take()
}
fn take_admin(&self) -> Option<StdTcpListener> {
let mut guard = self
.admin
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
guard.take()
}
}
#[derive(Clone)]
@@ -68,7 +57,6 @@ pub struct NetworkProxyBuilder {
state: Option<Arc<NetworkProxyState>>,
http_addr: Option<SocketAddr>,
socks_addr: Option<SocketAddr>,
admin_addr: Option<SocketAddr>,
managed_by_codex: bool,
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
blocked_request_observer: Option<Arc<dyn BlockedRequestObserver>>,
@@ -80,7 +68,6 @@ impl Default for NetworkProxyBuilder {
state: None,
http_addr: None,
socks_addr: None,
admin_addr: None,
managed_by_codex: true,
policy_decider: None,
blocked_request_observer: None,
@@ -104,11 +91,6 @@ impl NetworkProxyBuilder {
self
}
pub fn admin_addr(mut self, addr: SocketAddr) -> Self {
self.admin_addr = Some(addr);
self
}
pub fn managed_by_codex(mut self, managed_by_codex: bool) -> Self {
self.managed_by_codex = managed_by_codex;
self
@@ -153,10 +135,10 @@ impl NetworkProxyBuilder {
.set_blocked_request_observer(self.blocked_request_observer.clone())
.await;
let current_cfg = state.current_cfg().await?;
let (requested_http_addr, requested_socks_addr, requested_admin_addr, reserved_listeners) =
let (requested_http_addr, requested_socks_addr, reserved_listeners) =
if self.managed_by_codex {
let runtime = config::resolve_runtime(&current_cfg)?;
let (http_listener, socks_listener, admin_listener) =
let (http_listener, socks_listener) =
reserve_loopback_ephemeral_listeners(current_cfg.network.enable_socks5)
.context("reserve managed loopback proxy listeners")?;
let http_addr = http_listener
@@ -169,17 +151,12 @@ impl NetworkProxyBuilder {
} else {
runtime.socks_addr
};
let admin_addr = admin_listener
.local_addr()
.context("failed to read reserved admin API address")?;
(
http_addr,
socks_addr,
admin_addr,
Some(Arc::new(ReservedListeners::new(
http_listener,
socks_listener,
admin_listener,
))),
)
} else {
@@ -187,16 +164,14 @@ impl NetworkProxyBuilder {
(
self.http_addr.unwrap_or(runtime.http_addr),
self.socks_addr.unwrap_or(runtime.socks_addr),
self.admin_addr.unwrap_or(runtime.admin_addr),
None,
)
};
// Reapply bind clamping for caller overrides so unix-socket proxying stays loopback-only.
let (http_addr, socks_addr, admin_addr) = config::clamp_bind_addrs(
let (http_addr, socks_addr) = config::clamp_bind_addrs(
requested_http_addr,
requested_socks_addr,
requested_admin_addr,
&current_cfg.network,
);
@@ -210,7 +185,6 @@ impl NetworkProxyBuilder {
dangerously_allow_all_unix_sockets: current_cfg
.network
.dangerously_allow_all_unix_sockets,
admin_addr,
reserved_listeners,
policy_decider: self.policy_decider,
})
@@ -219,7 +193,7 @@ impl NetworkProxyBuilder {
fn reserve_loopback_ephemeral_listeners(
reserve_socks_listener: bool,
) -> Result<(StdTcpListener, Option<StdTcpListener>, StdTcpListener)> {
) -> Result<(StdTcpListener, Option<StdTcpListener>)> {
let http_listener =
reserve_loopback_ephemeral_listener().context("reserve HTTP proxy listener")?;
let socks_listener = if reserve_socks_listener {
@@ -227,9 +201,7 @@ fn reserve_loopback_ephemeral_listeners(
} else {
None
};
let admin_listener =
reserve_loopback_ephemeral_listener().context("reserve admin API listener")?;
Ok((http_listener, socks_listener, admin_listener))
Ok((http_listener, socks_listener))
}
fn reserve_loopback_ephemeral_listener() -> Result<StdTcpListener> {
@@ -246,7 +218,6 @@ pub struct NetworkProxy {
allow_local_binding: bool,
allow_unix_sockets: Vec<String>,
dangerously_allow_all_unix_sockets: bool,
admin_addr: SocketAddr,
reserved_listeners: Option<Arc<ReservedListeners>>,
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
}
@@ -258,7 +229,6 @@ impl std::fmt::Debug for NetworkProxy {
f.debug_struct("NetworkProxy")
.field("http_addr", &self.http_addr)
.field("socks_addr", &self.socks_addr)
.field("admin_addr", &self.admin_addr)
.finish_non_exhaustive()
}
}
@@ -268,7 +238,6 @@ impl PartialEq for NetworkProxy {
self.http_addr == other.http_addr
&& self.socks_addr == other.socks_addr
&& self.allow_local_binding == other.allow_local_binding
&& self.admin_addr == other.admin_addr
}
}
@@ -421,10 +390,6 @@ impl NetworkProxy {
self.socks_addr
}
pub fn admin_addr(&self) -> SocketAddr {
self.admin_addr
}
pub async fn add_allowed_domain(&self, host: &str) -> Result<()> {
self.state.add_allowed_domain(host).await
}
@@ -475,7 +440,6 @@ impl NetworkProxy {
let reserved_listeners = self.reserved_listeners.as_ref();
let http_listener = reserved_listeners.and_then(|listeners| listeners.take_http());
let socks_listener = reserved_listeners.and_then(|listeners| listeners.take_socks());
let admin_listener = reserved_listeners.and_then(|listeners| listeners.take_admin());
let http_state = self.state.clone();
let http_decider = self.policy_decider.clone();
@@ -520,21 +484,10 @@ impl NetworkProxy {
} else {
None
};
let admin_state = self.state.clone();
let admin_addr = self.admin_addr;
let admin_task = tokio::spawn(async move {
match admin_listener {
Some(listener) => {
admin::run_admin_api_with_std_listener(admin_state, listener).await
}
None => admin::run_admin_api(admin_state, admin_addr).await,
}
});
Ok(NetworkProxyHandle {
http_task: Some(http_task),
socks_task,
admin_task: Some(admin_task),
completed: false,
})
}
@@ -543,7 +496,6 @@ impl NetworkProxy {
pub struct NetworkProxyHandle {
http_task: Option<JoinHandle<Result<()>>>,
socks_task: Option<JoinHandle<Result<()>>>,
admin_task: Option<JoinHandle<Result<()>>>,
completed: bool,
}
@@ -552,24 +504,20 @@ impl NetworkProxyHandle {
Self {
http_task: Some(tokio::spawn(async { Ok(()) })),
socks_task: None,
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 socks_task = self.socks_task.take();
let http_result = http_task.await;
let admin_result = admin_task.await;
let socks_result = match socks_task {
Some(task) => Some(task.await),
None => None,
};
self.completed = true;
http_result??;
admin_result??;
if let Some(socks_result) = socks_result {
socks_result??;
}
@@ -577,12 +525,7 @@ impl NetworkProxyHandle {
}
pub async fn shutdown(mut self) -> Result<()> {
abort_tasks(
self.http_task.take(),
self.socks_task.take(),
self.admin_task.take(),
)
.await;
abort_tasks(self.http_task.take(), self.socks_task.take()).await;
self.completed = true;
Ok(())
}
@@ -598,11 +541,9 @@ async fn abort_task(task: Option<JoinHandle<Result<()>>>) {
async fn abort_tasks(
http_task: Option<JoinHandle<Result<()>>>,
socks_task: Option<JoinHandle<Result<()>>>,
admin_task: Option<JoinHandle<Result<()>>>,
) {
abort_task(http_task).await;
abort_task(socks_task).await;
abort_task(admin_task).await;
}
impl Drop for NetworkProxyHandle {
@@ -612,9 +553,8 @@ impl Drop for NetworkProxyHandle {
}
let http_task = self.http_task.take();
let socks_task = self.socks_task.take();
let admin_task = self.admin_task.take();
tokio::spawn(async move {
abort_tasks(http_task, socks_task, admin_task).await;
abort_tasks(http_task, socks_task).await;
});
}
}
@@ -648,10 +588,8 @@ mod tests {
assert!(proxy.http_addr.ip().is_loopback());
assert!(proxy.socks_addr.ip().is_loopback());
assert!(proxy.admin_addr.ip().is_loopback());
assert_ne!(proxy.http_addr.port(), 0);
assert_ne!(proxy.socks_addr.port(), 0);
assert_ne!(proxy.admin_addr.port(), 0);
}
#[tokio::test]
@@ -659,7 +597,6 @@ mod tests {
let settings = NetworkProxySettings {
proxy_url: "http://127.0.0.1:43128".to_string(),
socks_url: "http://127.0.0.1:48081".to_string(),
admin_url: "http://127.0.0.1:48080".to_string(),
..NetworkProxySettings::default()
};
let state = Arc::new(network_proxy_state_for_policy(settings));
@@ -678,10 +615,6 @@ mod tests {
proxy.socks_addr,
"127.0.0.1:48081".parse::<SocketAddr>().unwrap()
);
assert_eq!(
proxy.admin_addr,
"127.0.0.1:48080".parse::<SocketAddr>().unwrap()
);
}
#[tokio::test]
@@ -706,7 +639,6 @@ mod tests {
};
assert!(proxy.http_addr.ip().is_loopback());
assert!(proxy.admin_addr.ip().is_loopback());
assert_eq!(
proxy.socks_addr,
"127.0.0.1:43129".parse::<SocketAddr>().unwrap()
-36
View File
@@ -1251,42 +1251,6 @@ mod tests {
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
}
#[test]
fn validate_policy_against_constraints_disallows_non_loopback_admin_without_managed_opt_in() {
let constraints = NetworkProxyConstraints {
dangerously_allow_non_loopback_admin: Some(false),
..NetworkProxyConstraints::default()
};
let config = NetworkProxyConfig {
network: NetworkProxySettings {
enabled: true,
dangerously_allow_non_loopback_admin: true,
..NetworkProxySettings::default()
},
};
assert!(validate_policy_against_constraints(&config, &constraints).is_err());
}
#[test]
fn validate_policy_against_constraints_allows_non_loopback_admin_with_managed_opt_in() {
let constraints = NetworkProxyConstraints {
dangerously_allow_non_loopback_admin: Some(true),
..NetworkProxyConstraints::default()
};
let config = NetworkProxyConfig {
network: NetworkProxySettings {
enabled: true,
dangerously_allow_non_loopback_admin: true,
..NetworkProxySettings::default()
},
};
assert!(validate_policy_against_constraints(&config, &constraints).is_ok());
}
#[test]
fn validate_policy_against_constraints_disallows_allow_all_unix_sockets_without_managed_opt_in()
{
-21
View File
@@ -21,7 +21,6 @@ pub struct NetworkProxyConstraints {
pub mode: Option<NetworkMode>,
pub allow_upstream_proxy: Option<bool>,
pub dangerously_allow_non_loopback_proxy: Option<bool>,
pub dangerously_allow_non_loopback_admin: Option<bool>,
pub dangerously_allow_all_unix_sockets: Option<bool>,
pub allowed_domains: Option<Vec<String>>,
pub denied_domains: Option<Vec<String>>,
@@ -41,7 +40,6 @@ pub struct PartialNetworkConfig {
pub mode: Option<NetworkMode>,
pub allow_upstream_proxy: Option<bool>,
pub dangerously_allow_non_loopback_proxy: Option<bool>,
pub dangerously_allow_non_loopback_admin: Option<bool>,
pub dangerously_allow_all_unix_sockets: Option<bool>,
#[serde(default)]
pub allowed_domains: Option<Vec<String>>,
@@ -149,25 +147,6 @@ pub fn validate_policy_against_constraints(
},
)?;
let allow_non_loopback_admin = constraints.dangerously_allow_non_loopback_admin;
validate(
config.network.dangerously_allow_non_loopback_admin,
move |candidate| match allow_non_loopback_admin {
Some(true) | None => Ok(()),
Some(false) => {
if *candidate {
Err(invalid_value(
"network.dangerously_allow_non_loopback_admin",
"true",
"false (disabled by managed config)",
))
} else {
Ok(())
}
}
},
)?;
let allow_non_loopback_proxy = constraints.dangerously_allow_non_loopback_proxy;
validate(
config.network.dangerously_allow_non_loopback_proxy,
-1
View File
@@ -2802,7 +2802,6 @@ pub struct SkillsListEntry {
pub struct SessionNetworkProxyRuntime {
pub http_addr: String,
pub socks_addr: String,
pub admin_addr: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
-8
View File
@@ -27,7 +27,6 @@ pub(crate) fn new_debug_config_output(
let SessionNetworkProxyRuntime {
http_addr,
socks_addr,
admin_addr,
} = proxy;
let all_proxy = session_all_proxy_url(
http_addr,
@@ -40,7 +39,6 @@ pub(crate) fn new_debug_config_output(
);
lines.push(format!(" - HTTP_PROXY = http://{http_addr}").into());
lines.push(format!(" - ALL_PROXY = {all_proxy}").into());
lines.push(format!(" - ADMIN_PROXY = http://{admin_addr}").into());
}
PlainHistoryCell::new(lines)
@@ -331,7 +329,6 @@ fn format_network_constraints(network: &NetworkConstraints) -> String {
socks_port,
allow_upstream_proxy,
dangerously_allow_non_loopback_proxy,
dangerously_allow_non_loopback_admin,
dangerously_allow_all_unix_sockets,
allowed_domains,
denied_domains,
@@ -356,11 +353,6 @@ fn format_network_constraints(network: &NetworkConstraints) -> String {
"dangerously_allow_non_loopback_proxy={dangerously_allow_non_loopback_proxy}"
));
}
if let Some(dangerously_allow_non_loopback_admin) = dangerously_allow_non_loopback_admin {
parts.push(format!(
"dangerously_allow_non_loopback_admin={dangerously_allow_non_loopback_admin}"
));
}
if let Some(dangerously_allow_all_unix_sockets) = dangerously_allow_all_unix_sockets {
parts.push(format!(
"dangerously_allow_all_unix_sockets={dangerously_allow_all_unix_sockets}"