Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions codex-rs/network-proxy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,9 @@ When a request is blocked, the proxy responds with `403` and includes:
- `blocked-by-method-policy`
- `blocked-by-policy`

In "limited" mode, only `GET`, `HEAD`, and `OPTIONS` are allowed. HTTPS `CONNECT` requests require
MITM to enforce limited-mode method policy; otherwise they are blocked. SOCKS5 remains blocked in
limited mode.
In "limited" mode, only `GET`, `HEAD`, and `OPTIONS` are allowed. HTTPS `CONNECT` requests and
HTTPS SOCKS5 TCP targets on `:443` require MITM to enforce limited-mode method policy; otherwise
they are blocked. SOCKS5 UDP and non-HTTPS SOCKS5 TCP remain blocked in limited mode.

Websocket clients typically tunnel `wss://` through HTTPS `CONNECT`; those CONNECT targets still go
through the same host allowlist/denylist checks.
Expand Down Expand Up @@ -215,7 +215,8 @@ what it can reasonably guarantee.
allowlisted (best-effort DNS lookup).
- Limited mode enforcement:
- only `GET`, `HEAD`, and `OPTIONS` are allowed
- HTTPS `CONNECT` remains a tunnel; limited-mode method enforcement does not apply to HTTPS
- HTTPS `CONNECT` requests and HTTPS SOCKS5 TCP targets on `:443` require MITM so the proxy can
enforce limited-mode method policy; SOCKS5 UDP and non-HTTPS SOCKS5 TCP remain blocked
- Listener safety defaults:
- the HTTP proxy listener clamps non-loopback binds unless explicitly enabled via
`dangerously_allow_non_loopback_proxy`
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/network-proxy/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ impl NetworkProxySettings {
pub enum NetworkMode {
/// Limited (read-only) access: only GET/HEAD/OPTIONS are allowed for HTTP. HTTPS CONNECT is
/// blocked unless MITM is enabled so the proxy can enforce method policy on inner requests.
/// SOCKS5 remains blocked in limited mode.
/// SOCKS5 UDP and non-HTTPS SOCKS5 TCP remain blocked in limited mode.
Limited,
/// Full network access: all HTTP methods are allowed. HTTPS CONNECTs are tunneled directly.
/// MITM hooks do not currently make full mode enter MITM.
Expand Down
26 changes: 18 additions & 8 deletions codex-rs/network-proxy/src/mitm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ use rama_core::Layer;
use rama_core::Service;
use rama_core::bytes::Bytes;
use rama_core::error::BoxError;
use rama_core::extensions::ExtensionsMut;
use rama_core::extensions::ExtensionsRef;
use rama_core::futures::stream::Stream;
use rama_core::futures::stream::Stream as FuturesStream;
use rama_core::rt::Executor;
use rama_core::service::service_fn;
use rama_core::stream::Stream;
use rama_http::Body;
use rama_http::BodyDataStream;
use rama_http::HeaderMap;
Expand Down Expand Up @@ -135,17 +137,25 @@ impl MitmState {

/// Terminate the upgraded CONNECT stream with a generated leaf cert and proxy inner HTTPS traffic.
pub(crate) async fn mitm_tunnel(upgraded: Upgraded) -> Result<()> {
let mitm = upgraded
mitm_stream(upgraded).await
}

/// Terminate a raw client stream with a generated leaf cert and proxy inner HTTPS traffic.
pub(crate) async fn mitm_stream<S>(stream: S) -> Result<()>
where
S: Stream + Unpin + ExtensionsMut,
{
let mitm = stream
.extensions()
.get::<Arc<MitmState>>()
.cloned()
.context("missing MITM state")?;
let app_state = upgraded
let app_state = stream
.extensions()
.get::<Arc<NetworkProxyState>>()
.cloned()
.context("missing app state")?;
let target = upgraded
let target = stream
.extensions()
.get::<ProxyTarget>()
.context("missing proxy target")?
Expand All @@ -154,7 +164,7 @@ pub(crate) async fn mitm_tunnel(upgraded: Upgraded) -> Result<()> {
let target_host = normalize_host(&target.host.to_string());
let target_port = target.port;
let acceptor_data = mitm.tls_acceptor_data_for_host(&target_host)?;
let mode = upgraded
let mode = stream
.extensions()
.get::<NetworkMode>()
.copied()
Expand All @@ -169,7 +179,7 @@ pub(crate) async fn mitm_tunnel(upgraded: Upgraded) -> Result<()> {
mitm,
});

let executor = upgraded
let executor = stream
.extensions()
.get::<Executor>()
.cloned()
Expand All @@ -194,7 +204,7 @@ pub(crate) async fn mitm_tunnel(upgraded: Upgraded) -> Result<()> {
.into_layer(http_service);

https_service
.serve(upgraded)
.serve(stream)
.await
.map_err(|err| anyhow!("MITM serve error: {err}"))?;
Ok(())
Expand Down Expand Up @@ -456,7 +466,7 @@ struct InspectStream<T> {
max_body_bytes: usize,
}

impl<T: BodyLoggable> Stream for InspectStream<T> {
impl<T: BodyLoggable> FuturesStream for InspectStream<T> {
type Item = Result<Bytes, BoxError>;

fn poll_next(self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll<Option<Self::Item>> {
Expand Down
Loading
Loading