Remove metadata extraction from proxy

This commit is contained in:
viyatb-oai
2026-01-16 23:17:41 -08:00
parent 6c1df8b73e
commit cbb5f48ba3
10 changed files with 866 additions and 337 deletions

284
codex-rs/Cargo.lock generated
View File

@@ -193,6 +193,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
"getrandom 0.3.3",
"once_cell",
"version_check",
"zerocopy",
@@ -655,6 +656,16 @@ dependencies = [
"syn 2.0.104",
]
[[package]]
name = "asynk-strim"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52697735bdaac441a29391a9e97102c74c6ef0f9b60a40cf109b1b404e29d2f6"
dependencies = [
"futures-core",
"pin-project-lite",
]
[[package]]
name = "atomic-waker"
version = "1.1.2"
@@ -721,7 +732,7 @@ dependencies = [
"hyper",
"hyper-util",
"itoa",
"matchit",
"matchit 0.8.4",
"memchr",
"mime",
"percent-encoding",
@@ -1784,6 +1795,7 @@ name = "codex-network-proxy"
version = "0.0.0"
dependencies = [
"anyhow",
"async-trait",
"clap",
"codex-app-server-protocol",
"codex-core",
@@ -3037,6 +3049,12 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]]
name = "endian-type"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "869b0adbda23651a9c5c0c3d270aac9fcb52e8622a8f2b17e57802d7791962f2"
[[package]]
name = "enum-as-inner"
version = "0.6.1"
@@ -3195,6 +3213,9 @@ name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
dependencies = [
"getrandom 0.2.16",
]
[[package]]
name = "fax"
@@ -3304,13 +3325,13 @@ dependencies = [
[[package]]
name = "flume"
version = "0.11.1"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be"
dependencies = [
"fastrand",
"futures-core",
"futures-sink",
"nanorand",
"spin",
]
@@ -4727,6 +4748,12 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]]
name = "matchit"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3eede3bdf92f3b4f9dc04072a9ce5ab557d5ec9038773bf9ffcd5588b3cc05b"
[[package]]
name = "mcp-types"
version = "0.0.0"
@@ -4866,15 +4893,6 @@ dependencies = [
"serde",
]
[[package]]
name = "nanorand"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
dependencies = [
"getrandom 0.2.16",
]
[[package]]
name = "native-tls"
version = "0.2.14"
@@ -5824,7 +5842,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425"
dependencies = [
"anyhow",
"itertools 0.13.0",
"itertools 0.14.0",
"proc-macro2",
"quote",
"syn 2.0.104",
@@ -5973,16 +5991,29 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
dependencies = [
"endian-type",
"endian-type 0.1.2",
"nibble_vec",
]
[[package]]
name = "radix_trie"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b4431027dcd37fc2a73ef740b5f233aa805897935b8bce0195e41bbf9a3289a"
dependencies = [
"endian-type 0.2.0",
"nibble_vec",
]
[[package]]
name = "rama"
version = "0.3.0-alpha.3"
version = "0.3.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b9abba26d2a4a60eb94a17224755035ce03f06fd1f3448915cbd8bf02dd838d"
checksum = "66a8e02be6b50e4c35cbba44f15828fa716fa3048bb3b751b8dbda338983346f"
dependencies = [
"ahash",
"base64",
"opentelemetry-otlp",
"pin-project-lite",
"rama-core",
"rama-crypto",
@@ -5997,21 +6028,22 @@ dependencies = [
"rama-tcp",
"rama-tls-boring",
"rama-tls-rustls",
"rama-tower",
"rama-ua",
"rama-udp",
"rama-unix",
"rama-utils",
"rama-ws",
"rustversion",
"serde",
"tokio",
"tracing-subscriber",
]
[[package]]
name = "rama-boring"
version = "0.4.0"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6c52217ff947d630f3807cde9f8fe4cc69005b17a1e81fc99fa432cb3fb9c78"
checksum = "288926585d0b8ed1b1dd278a31ea1367007ad0bd4263ca84810e10939c2398a3"
dependencies = [
"bitflags 2.10.0",
"foreign-types 0.5.0",
@@ -6022,11 +6054,10 @@ dependencies = [
[[package]]
name = "rama-boring-sys"
version = "0.4.0"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c7fd7057828ca9fa148c704f1bf0fff12f6367891212c9c0caad63ae17902a7"
checksum = "421ebb40444a6d740f867a5055a710a73e7cd74b9b389583b45e9b29d1330465"
dependencies = [
"autocfg",
"bindgen",
"cmake",
"fs_extra",
@@ -6035,9 +6066,9 @@ dependencies = [
[[package]]
name = "rama-boring-tokio"
version = "0.4.0"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b07c71d66f9fa0b52b02055b94c779e98fc27d228e67105c6e528cfd1f19a7a4"
checksum = "913cf3d377b37ff903cd57c2f6133ba7da5b7e0fe94821dec83daa8a024bb6ce"
dependencies = [
"rama-boring",
"rama-boring-sys",
@@ -6046,11 +6077,12 @@ dependencies = [
[[package]]
name = "rama-core"
version = "0.3.0-alpha.3"
version = "0.3.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea03771324b3d27a4d5d450bdd288fdeac87638f7cdb5870f3d1da6503542483"
checksum = "0b93751ab27c9d151e84c1100057eab3f2a6a1378bc31b62abd416ecb1847658"
dependencies = [
"async-stream",
"ahash",
"asynk-strim",
"bytes",
"futures",
"parking_lot",
@@ -6058,16 +6090,19 @@ dependencies = [
"rama-error",
"rama-macros",
"rama-utils",
"serde",
"serde_json",
"tokio",
"tokio-graceful",
"tokio-util",
"tracing",
]
[[package]]
name = "rama-crypto"
version = "0.3.0-alpha.3"
version = "0.3.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91965fc4cb4bc45bd2980f6388f97dbc955bcf164fdbd81fc0e9155c4e9abe2f"
checksum = "d4ea5a793b2fe86a32e11a672d68378a18073701cce9b3f2b477b80b304711c5"
dependencies = [
"aws-lc-rs",
"base64",
@@ -6082,10 +6117,11 @@ dependencies = [
[[package]]
name = "rama-dns"
version = "0.3.0-alpha.3"
version = "0.3.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7556de219395b9b170f9fdbff10ee6a1c022749207a2d013c0c89e197bbaae3c"
checksum = "e340fef2799277e204260b17af01bc23604712092eacd6defe40167f304baed8"
dependencies = [
"ahash",
"hickory-resolver",
"rama-core",
"rama-net",
@@ -6096,15 +6132,15 @@ dependencies = [
[[package]]
name = "rama-error"
version = "0.3.0-alpha.3"
version = "0.3.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b3236ea868a50937223cbe56130b99725235c001ca8886cc01bf599736c9766"
checksum = "3c452aba1beb7e29b873ff32f304536164cffcc596e786921aea64e858ff8f40"
[[package]]
name = "rama-haproxy"
version = "0.3.0-alpha.3"
version = "0.3.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30cf7f5fe1af97475f2e4c7dc6320caca1db2c65336c8ce6513bf5790ca0f75f"
checksum = "3ad683d7f8858bc04c6e62525f598e2289c1cfee6193b9d4e033805552fe3992"
dependencies = [
"rama-core",
"rama-net",
@@ -6114,51 +6150,50 @@ dependencies = [
[[package]]
name = "rama-http"
version = "0.3.0-alpha.3"
version = "0.3.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52279e9b6c364ac8db44209707544eac45a3b79c1953cb3cd3985272565d9818"
checksum = "453d60af031e23af2d48995e41b17023f6150044738680508b63671f8d7417dd"
dependencies = [
"ahash",
"async-compression",
"base64",
"bitflags 2.10.0",
"chrono",
"compression-codecs",
"compression-core",
"const_format",
"csv",
"flate2",
"http 1.3.1",
"http-range-header",
"httpdate",
"iri-string",
"matchit",
"mime",
"mime_guess",
"matchit 0.9.1",
"parking_lot",
"percent-encoding",
"pin-project-lite",
"radix_trie 0.3.0",
"rama-core",
"rama-error",
"rama-http-headers",
"rama-http-types",
"rama-net",
"rama-ua",
"rama-utils",
"rand 0.9.2",
"rawzip",
"regex",
"serde",
"serde_html_form",
"serde_json",
"smol_str",
"tokio",
"tokio-util",
"uuid",
]
[[package]]
name = "rama-http-backend"
version = "0.3.0-alpha.3"
version = "0.3.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f53a4fab6c60854a5d53dccdfdec7b86be697269d523adfff3ff13cca7455a9"
checksum = "f3ff6a3c8ae690be8167e43777ba0bf6b0c8c2f6de165c538666affe2a32fd81"
dependencies = [
"const_format",
"futures",
"h2",
"pin-project-lite",
"rama-core",
@@ -6175,38 +6210,40 @@ dependencies = [
[[package]]
name = "rama-http-core"
version = "0.3.0-alpha.3"
version = "0.3.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42d2a4dc0b90d0ff7a2b7eda9993b51c7ad234b343c700f480ff83f7dea46966"
checksum = "3822be6703e010afec0bcfeb5dbb6e5a3b23ca5689d9b1215b66ce6446653b77"
dependencies = [
"ahash",
"atomic-waker",
"futures-channel",
"httparse",
"httpdate",
"indexmap 2.12.0",
"itoa",
"parking_lot",
"pin-project-lite",
"rama-core",
"rama-http",
"rama-http-types",
"rama-net",
"rama-utils",
"slab",
"smallvec",
"tokio",
"tokio-test",
"tokio-util",
"want",
]
[[package]]
name = "rama-http-headers"
version = "0.3.0-alpha.3"
version = "0.3.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c49d5a5f7c95eaffab07609767ed60d52f6c56e7c2115565311545e7141ecd23"
checksum = "9d74fe0cd9bd4440827dc6dc0f504cf66065396532e798891dee2c1b740b2285"
dependencies = [
"ahash",
"base64",
"chrono",
"const_format",
"httpdate",
"mime",
"rama-core",
"rama-error",
"rama-http-types",
@@ -6220,10 +6257,12 @@ dependencies = [
[[package]]
name = "rama-http-types"
version = "0.3.0-alpha.3"
version = "0.3.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "020137823b0d45de8eb78d261a45dc92a6d2cf763a948fc01ff922cd23eeabd3"
checksum = "b6dae655a72da5f2b97cfacb67960d8b28c5025e62707b4c8c5f0c5c9843a444"
dependencies = [
"ahash",
"bytes",
"const_format",
"fnv",
"http 1.3.1",
@@ -6242,30 +6281,29 @@ dependencies = [
"rand 0.9.2",
"serde",
"serde_json",
"smallvec",
"smol_str",
"sync_wrapper",
"tokio",
"tokio-util",
]
[[package]]
name = "rama-macros"
version = "0.3.0-alpha.3"
version = "0.3.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8531184c892978544f6799b132e2ff7c00ceaf8b7d68c2d693ad441d351cbf5"
checksum = "ea18a110bcf21e35c5f194168e6914ccea45ffdd0fea51bc4b169fbeafef6428"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 2.0.104",
]
[[package]]
name = "rama-net"
version = "0.3.0-alpha.3"
version = "0.3.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f2ca3d7c32de50880b81fc7d923fa3d9c9cf8d89c6557abdbd7e58758087926"
checksum = "b28ee9e1e5d39264414b71f5c33e7fbb66b382c3fac456fe0daad39cf5509933"
dependencies = [
"ahash",
"const_format",
"flume",
"hex",
@@ -6276,14 +6314,13 @@ dependencies = [
"parking_lot",
"pin-project-lite",
"psl",
"radix_trie",
"radix_trie 0.3.0",
"rama-core",
"rama-http-types",
"rama-macros",
"rama-utils",
"serde",
"sha2",
"smol_str",
"socket2 0.6.1",
"tokio",
"venndb",
@@ -6291,9 +6328,9 @@ dependencies = [
[[package]]
name = "rama-proxy"
version = "0.3.0-alpha.3"
version = "0.3.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ec0bb1a048cececb6d1a1c0e093ae7e3081d291511a6c2cbdcef8a5975a3c12"
checksum = "149eaf3134c30af80017182f3e659ad05d242e1ade8181ef8035b83859fafac7"
dependencies = [
"arc-swap",
"base64",
@@ -6308,9 +6345,9 @@ dependencies = [
[[package]]
name = "rama-socks5"
version = "0.3.0-alpha.3"
version = "0.3.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aeb01a3889de2d2a30e2a9143ffd26e1ed805f04fc477b02883dc0b907533b2"
checksum = "5468b263516daaf258de32542c1974b7cbe962363ad913dcb669f5d46db0ef3e"
dependencies = [
"byteorder",
"rama-core",
@@ -6320,16 +6357,16 @@ dependencies = [
"rama-udp",
"rama-utils",
"rand 0.9.2",
"smallvec",
"tokio",
]
[[package]]
name = "rama-tcp"
version = "0.3.0-alpha.3"
version = "0.3.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90f6b443f656ccf6d4d3cf5e288f73d5484f92c17dbe9836a1cdf13b4c9a4f67"
checksum = "fe60cd604f91196b3659a1b28945add2e8b10bd0b4e6373c93d024fb3197704b"
dependencies = [
"pin-project-lite",
"rama-core",
"rama-dns",
"rama-http-types",
@@ -6341,10 +6378,11 @@ dependencies = [
[[package]]
name = "rama-tls-boring"
version = "0.3.0-alpha.3"
version = "0.3.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed51b8ae893be304f578b94702f92d564e5d293928349e92d6151d9ab78bf90"
checksum = "def3d5d06d3ca3a2d2e4376cf93de0555cd9c7960f085bf77be9562f5c9ace8f"
dependencies = [
"ahash",
"brotli",
"flate2",
"flume",
@@ -6366,59 +6404,48 @@ dependencies = [
[[package]]
name = "rama-tls-rustls"
version = "0.3.0-alpha.3"
version = "0.3.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b544da527a10d09ff03d08a120adb0e7ea239fabcbf871863bb7f134d1d70b"
checksum = "536d47f6b269fb20dffd45e4c04aa8b340698b3509326e3c36e444b4f33ce0d6"
dependencies = [
"pin-project-lite",
"rama-core",
"rama-http-types",
"rama-net",
"rama-utils",
"rcgen",
"rustls",
"rustls-native-certs",
"rustls-pemfile",
"rustls-pki-types",
"tokio",
"tokio-rustls",
"webpki-roots",
]
[[package]]
name = "rama-tower"
version = "0.3.0-alpha.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2236eed00294ddc68befaaf565e6924d1918c97eaf63d71e2448d6ab961d52c"
dependencies = [
"rama-core",
"rama-http-types",
"tokio",
"tower-layer",
"tower-service",
"x509-parser",
]
[[package]]
name = "rama-ua"
version = "0.3.0-alpha.3"
version = "0.3.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e496c2926de78dd36d3190fe852b11e39260079b05590c531b187c997078818c"
checksum = "d7abde8e7b428c80c5948885c1ee0492852a21d91844c8414a0de4a8a1d62262"
dependencies = [
"ahash",
"itertools 0.14.0",
"rama-core",
"rama-http-headers",
"rama-http-types",
"rama-http",
"rama-net",
"rama-utils",
"rand 0.9.2",
"serde",
"serde_html_form",
"serde_json",
]
[[package]]
name = "rama-udp"
version = "0.3.0-alpha.3"
version = "0.3.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0460c8db3b802ffa1d1f91bd375ee431da3780d6625d2503891e881ab115339a"
checksum = "36ed05e0ecac73e084e92a3a8b1fbf16fdae8958c506f0f0eada180a2d99eef4"
dependencies = [
"rama-core",
"rama-net",
@@ -6428,35 +6455,39 @@ dependencies = [
[[package]]
name = "rama-unix"
version = "0.3.0-alpha.3"
version = "0.3.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa5e3efecbd7ba29667df34c9a949ed36da1d7b4f8f7b43b3b45e6234bbd932f"
checksum = "91acb16d571428ba4cece072dfab90d2667cdfa910a7b3cb4530c3f31542d708"
dependencies = [
"pin-project-lite",
"rama-core",
"rama-net",
"tokio",
"tokio-util",
]
[[package]]
name = "rama-utils"
version = "0.3.0-alpha.3"
version = "0.3.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02fd73071ddb078866fb7fca5def45bbfa12e7ea1cd819a0e3b701bf09ba2c72"
checksum = "bf28b18ba4a57f8334d7992d3f8020194ea359b246ae6f8f98b8df524c7a14ef"
dependencies = [
"const_format",
"parking_lot",
"pin-project-lite",
"rama-macros",
"regex",
"serde",
"smallvec",
"smol_str",
"tokio",
"wildcard",
]
[[package]]
name = "rama-ws"
version = "0.3.0-alpha.3"
version = "0.3.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6d2cc376163a79bdb2b49e8caad99b6b9c204424f8307c52b04c6627fd8e0b"
checksum = "300b2b6ba51381d1a6918d1142879a8314588f7cf24669f6ba8439d9d19ab486"
dependencies = [
"flate2",
"rama-core",
@@ -6898,15 +6929,6 @@ dependencies = [
"security-framework 3.5.1",
]
[[package]]
name = "rustls-pemfile"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "rustls-pki-types"
version = "1.12.0"
@@ -6950,7 +6972,7 @@ dependencies = [
"log",
"memchr",
"nix 0.28.0",
"radix_trie",
"radix_trie 0.2.1",
"unicode-segmentation",
"unicode-width 0.1.14",
"utf8parse",
@@ -7346,9 +7368,9 @@ dependencies = [
[[package]]
name = "serde_html_form"
version = "0.2.8"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2f2d7ff8a2140333718bb329f5c40fc5f0865b84c426183ce14c97d2ab8154f"
checksum = "2acf96b1d9364968fce46ebb548f1c0e1d7eceae27bdff73865d42e6c7369d94"
dependencies = [
"form_urlencoded",
"indexmap 2.12.0",
@@ -7618,6 +7640,9 @@ name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
dependencies = [
"serde",
]
[[package]]
name = "smawk"
@@ -8580,22 +8605,36 @@ dependencies = [
"web-time",
]
[[package]]
name = "tracing-serde"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1"
dependencies = [
"serde",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
dependencies = [
"chrono",
"matchers",
"nu-ansi-term",
"once_cell",
"regex-automata",
"serde",
"serde_json",
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
"tracing-serde",
]
[[package]]
@@ -9268,6 +9307,15 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471"
[[package]]
name = "wildcard"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9b0540e91e49de3817c314da0dd3bc518093ceacc6ea5327cb0e1eb073e5189"
dependencies = [
"thiserror 2.0.17",
]
[[package]]
name = "wildmatch"
version = "2.6.1"

View File

@@ -17,6 +17,7 @@ workspace = true
[dependencies]
anyhow = { workspace = true }
async-trait = { workspace = true }
clap = { workspace = true, features = ["derive"] }
codex-app-server-protocol = { workspace = true }
codex-core = { workspace = true }
@@ -28,7 +29,7 @@ time = { workspace = true }
tokio = { workspace = true, features = ["full"] }
tracing = { workspace = true }
tracing-subscriber = { workspace = true, features = ["fmt"] }
rama = { version = "=0.3.0-alpha.3", default-features = false, features = ["http-full", "proxy-full", "socks5", "rustls"] }
rama = { version = "=0.3.0-alpha.4", default-features = false, features = ["http-full", "proxy-full", "socks5", "rustls"] }
[dev-dependencies]
pretty_assertions = { workspace = true }

View File

@@ -67,6 +67,13 @@ cargo run -p codex-network-proxy -- init
cargo run -p codex-network-proxy --
```
Optional flags:
```bash
# Enable SOCKS5 UDP associate support (off by default).
cargo run -p codex-network-proxy -- --enable-socks5-udp
```
### 4) Point a client at it
For HTTP(S) traffic:
@@ -97,6 +104,45 @@ In "limited" mode, only `GET`, `HEAD`, and `OPTIONS` are allowed. In addition, H
requires MITM to be enabled to allow read-only HTTPS; otherwise the proxy blocks CONNECT with
reason `mitm_required`.
## Library API
`codex-network-proxy` can be embedded as a library with a thin API:
```rust
use codex_network_proxy::{NetworkProxy, NetworkDecision, NetworkPolicyRequest};
let proxy = NetworkProxy::builder()
.http_addr("127.0.0.1:8080".parse()?)
.socks_addr("127.0.0.1:1080".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() {
if command.starts_with("curl ") {
return NetworkDecision::Allow;
}
}
NetworkDecision::Deny {
reason: "policy_denied".to_string(),
}
})
.build()
.await?;
let handle = proxy.run().await?;
handle.shutdown().await?;
```
### Policy hook (exec-policy mapping)
The proxy exposes a policy hook (`NetworkPolicyDecider`) that can override allowlist-only blocks.
It receives `command` and `exec_policy_hint` fields when supplied by the embedding app. This lets
core map exec approvals to network access, e.g. if a user already approved `curl *` for a session,
the decider can auto-allow network requests originating from that command.
**Important:** Explicit deny rules still win. The decider only gets a chance to override
`not_allowed` (allowlist misses), not `denied` or `not_allowed_local`.
## Admin API
The admin API is a small HTTP server intended for debugging and runtime adjustments.

View File

@@ -1,14 +1,20 @@
use crate::config::NetworkMode;
use crate::mitm;
use crate::network_policy::NetworkDecision;
use crate::network_policy::NetworkPolicyDecider;
use crate::network_policy::NetworkPolicyRequest;
use crate::network_policy::NetworkProtocol;
use crate::network_policy::evaluate_host_policy;
use crate::policy::normalize_host;
use crate::responses::blocked_header_value;
use crate::state::AppState;
use crate::state::BlockedRequest;
use anyhow::Context as _;
use anyhow::Result;
use rama::Context;
use rama::Layer;
use rama::Service;
use rama::extensions::ExtensionsMut;
use rama::extensions::ExtensionsRef;
use rama::http::Body;
use rama::http::Request;
use rama::http::Response;
@@ -20,7 +26,7 @@ use rama::http::layer::upgrade::UpgradeLayer;
use rama::http::layer::upgrade::Upgraded;
use rama::http::matcher::MethodMatcher;
use rama::http::server::HttpServer;
use rama::layer::AddExtensionLayer;
use rama::layer::AddInputExtensionLayer;
use rama::net::http::RequestContext;
use rama::net::proxy::ProxyTarget;
use rama::net::stream::SocketInfo;
@@ -35,7 +41,11 @@ use tracing::error;
use tracing::info;
use tracing::warn;
pub async fn run_http_proxy(state: Arc<AppState>, addr: SocketAddr) -> Result<()> {
pub async fn run_http_proxy(
state: Arc<AppState>,
addr: SocketAddr,
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
) -> Result<()> {
let listener = TcpListener::build()
.bind(addr)
.await
@@ -51,39 +61,40 @@ pub async fn run_http_proxy(state: Arc<AppState>, addr: SocketAddr) -> Result<()
(
UpgradeLayer::new(
MethodMatcher::CONNECT,
service_fn(http_connect_accept),
service_fn({
let policy_decider = policy_decider.clone();
move |req| http_connect_accept(policy_decider.clone(), req)
}),
service_fn(http_connect_proxy),
),
RemoveResponseHeaderLayer::hop_by_hop(),
RemoveRequestHeaderLayer::hop_by_hop(),
)
.into_layer(service_fn(http_plain_proxy)),
.into_layer(service_fn({
let policy_decider = policy_decider.clone();
move |req| http_plain_proxy(policy_decider.clone(), req)
})),
);
info!("HTTP proxy listening on {addr}");
listener
.serve(AddExtensionLayer::new(state).into_layer(http_service))
.serve(AddInputExtensionLayer::new(state).into_layer(http_service))
.await;
Ok(())
}
async fn http_connect_accept<S>(
mut ctx: Context<S>,
req: Request,
) -> Result<(Response, Context<S>, Request), Response>
where
S: Clone + Send + Sync + 'static,
{
let app_state = ctx
async fn http_connect_accept(
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
mut req: Request,
) -> Result<(Response, Request), Response> {
let app_state = req
.extensions()
.get::<Arc<AppState>>()
.cloned()
.ok_or_else(|| text_response(StatusCode::INTERNAL_SERVER_ERROR, "missing state"))?;
let authority = match ctx
.get_or_try_insert_with_ctx::<RequestContext, _>(|ctx| (ctx, &req).try_into())
.map(|ctx| ctx.authority.clone())
{
let authority = match RequestContext::try_from(&req).map(|ctx| ctx.host_with_port()) {
Ok(authority) => authority,
Err(err) => {
warn!("CONNECT missing authority: {err}");
@@ -91,15 +102,25 @@ where
}
};
let host = normalize_host(&authority.host().to_string());
let host = normalize_host(&authority.host.to_string());
if host.is_empty() {
return Err(text_response(StatusCode::BAD_REQUEST, "invalid host"));
}
let client = client_addr(&ctx);
let client = client_addr(&req);
match app_state.host_blocked(&host, authority.port()).await {
Ok((true, reason)) => {
let request = NetworkPolicyRequest::new(
NetworkProtocol::HttpsConnect,
host.clone(),
authority.port,
client.clone(),
Some("CONNECT".to_string()),
None,
None,
);
match evaluate_host_policy(&app_state, policy_decider.as_ref(), &request).await {
Ok(NetworkDecision::Deny { reason }) => {
let _ = app_state
.record_blocked(BlockedRequest::new(
host.clone(),
@@ -114,7 +135,7 @@ where
warn!("CONNECT blocked (client={client}, host={host}, reason={reason})");
return Err(blocked_text(&reason));
}
Ok((false, _)) => {
Ok(NetworkDecision::Allow) => {
let client = client.as_deref().unwrap_or_default();
info!("CONNECT allowed (client={client}, host={host})");
}
@@ -160,10 +181,10 @@ where
return Err(blocked_text("mitm_required"));
}
ctx.insert(ProxyTarget(authority));
ctx.insert(mode);
req.extensions_mut().insert(ProxyTarget(authority));
req.extensions_mut().insert(mode);
if let Some(mitm_state) = mitm_state {
ctx.insert(mitm_state);
req.extensions_mut().insert(mitm_state);
}
Ok((
@@ -171,54 +192,59 @@ where
.status(StatusCode::OK)
.body(Body::empty())
.unwrap_or_else(|_| Response::new(Body::empty())),
ctx,
req,
))
}
async fn http_connect_proxy<S>(ctx: Context<S>, upgraded: Upgraded) -> Result<(), Infallible>
where
S: Clone + Send + Sync + 'static,
{
let mode = ctx
async fn http_connect_proxy(upgraded: Upgraded) -> Result<(), Infallible> {
let mode = upgraded
.extensions()
.get::<NetworkMode>()
.copied()
.unwrap_or(NetworkMode::Full);
let Some(target) = ctx.get::<ProxyTarget>().map(|t| t.0.clone()) else {
let Some(target) = upgraded
.extensions()
.get::<ProxyTarget>()
.map(|t| t.0.clone())
else {
warn!("CONNECT missing proxy target");
return Ok(());
};
let host = normalize_host(&target.host().to_string());
let host = normalize_host(&target.host.to_string());
if ctx.get::<Arc<mitm::MitmState>>().is_some() {
let port = target.port();
if upgraded
.extensions()
.get::<Arc<mitm::MitmState>>()
.is_some()
{
let port = target.port;
info!("CONNECT MITM enabled (host={host}, port={port}, mode={mode:?})");
if let Err(err) = mitm::mitm_tunnel(ctx, upgraded).await {
if let Err(err) = mitm::mitm_tunnel(upgraded).await {
warn!("MITM tunnel error: {err}");
}
return Ok(());
}
let forwarder = Forwarder::ctx();
if let Err(err) = forwarder.serve(ctx, upgraded).await {
if let Err(err) = forwarder.serve(upgraded).await {
warn!("tunnel error: {err}");
}
Ok(())
}
async fn http_plain_proxy<S>(mut ctx: Context<S>, req: Request) -> Result<Response, Infallible>
where
S: Clone + Send + Sync + 'static,
{
let app_state = match ctx.get::<Arc<AppState>>().cloned() {
async fn http_plain_proxy(
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
req: Request,
) -> Result<Response, Infallible> {
let app_state = match req.extensions().get::<Arc<AppState>>().cloned() {
Some(state) => state,
None => {
error!("missing app state");
return Ok(text_response(StatusCode::INTERNAL_SERVER_ERROR, "error"));
}
};
let client = client_addr(&ctx);
let client = client_addr(&req);
let method_allowed = match app_state.method_allowed(req.method().as_str()).await {
Ok(allowed) => allowed,
@@ -263,7 +289,7 @@ where
Ok(true) => {
let client = client.as_deref().unwrap_or_default();
info!("unix socket allowed (client={client}, path={socket_path})");
match proxy_via_unix_socket(ctx, req, &socket_path).await {
match proxy_via_unix_socket(req, &socket_path).await {
Ok(resp) => return Ok(resp),
Err(err) => {
warn!("unix socket proxy failed: {err}");
@@ -286,21 +312,28 @@ where
}
}
let authority = match ctx
.get_or_try_insert_with_ctx::<RequestContext, _>(|ctx| (ctx, &req).try_into())
.map(|ctx| ctx.authority.clone())
{
let authority = match RequestContext::try_from(&req).map(|ctx| ctx.host_with_port()) {
Ok(authority) => authority,
Err(err) => {
warn!("missing host: {err}");
return Ok(text_response(StatusCode::BAD_REQUEST, "missing host"));
}
};
let host = normalize_host(&authority.host().to_string());
let port = authority.port();
let host = normalize_host(&authority.host.to_string());
let port = authority.port;
match app_state.host_blocked(&host, port).await {
Ok((true, reason)) => {
let request = NetworkPolicyRequest::new(
NetworkProtocol::Http,
host.clone(),
port,
client.clone(),
Some(req.method().as_str().to_string()),
None,
None,
);
match evaluate_host_policy(&app_state, policy_decider.as_ref(), &request).await {
Ok(NetworkDecision::Deny { reason }) => {
let _ = app_state
.record_blocked(BlockedRequest::new(
host.clone(),
@@ -315,7 +348,7 @@ where
warn!("request blocked (client={client}, host={host}, reason={reason})");
return Ok(json_blocked(&host, &reason));
}
Ok((false, _)) => {}
Ok(NetworkDecision::Allow) => {}
Err(err) => {
error!("failed to evaluate host for {host}: {err}");
return Ok(text_response(StatusCode::INTERNAL_SERVER_ERROR, "error"));
@@ -346,7 +379,7 @@ where
info!("request allowed (client={client}, host={host}, method={method})");
let client = EasyHttpWebClient::default();
match client.serve(ctx, req).await {
match client.serve(req).await {
Ok(resp) => Ok(resp),
Err(err) => {
warn!("upstream request failed: {err}");
@@ -355,30 +388,24 @@ where
}
}
async fn proxy_via_unix_socket<S>(
ctx: Context<S>,
req: Request,
socket_path: &str,
) -> Result<Response>
where
S: Clone + Send + Sync + 'static,
{
async fn proxy_via_unix_socket(req: Request, socket_path: &str) -> Result<Response> {
#[cfg(target_os = "macos")]
{
use rama::unix::client::UnixConnector;
let client = EasyHttpWebClient::builder()
let client = EasyHttpWebClient::connector_builder()
.with_custom_transport_connector(UnixConnector::fixed(socket_path))
.without_tls_proxy_support()
.without_proxy_support()
.without_tls_support()
.build();
.with_default_http_connector()
.build_client();
let (mut parts, body) = req.into_parts();
let path = parts
.uri
.path_and_query()
.map(rama::http::dep::http::uri::PathAndQuery::as_str)
.map(rama::http::uri::PathAndQuery::as_str)
.unwrap_or("/");
parts.uri = path
.parse()
@@ -386,19 +413,20 @@ where
parts.headers.remove("x-unix-socket");
let req = Request::from_parts(parts, body);
Ok(client.serve(ctx, req).await?)
Ok(client.serve(req).await?)
}
#[cfg(not(target_os = "macos"))]
{
let _ = req;
let _ = ctx;
let _ = socket_path;
Err(anyhow::anyhow!("unix sockets not supported"))
}
}
fn client_addr<S>(ctx: &Context<S>) -> Option<String> {
ctx.get::<SocketInfo>()
fn client_addr<T: ExtensionsRef>(input: &T) -> Option<String> {
input
.extensions()
.get::<SocketInfo>()
.map(|info| info.peer_addr().to_string())
}

View File

@@ -3,55 +3,31 @@ mod config;
mod http_proxy;
mod init;
mod mitm;
mod network_policy;
mod policy;
mod proxy;
mod responses;
mod socks5;
mod state;
use crate::state::AppState;
use anyhow::Result;
use clap::Parser;
use clap::Subcommand;
use std::net::SocketAddr;
use std::sync::Arc;
use tracing::warn;
#[derive(Debug, Clone, Parser)]
#[command(name = "codex-network-proxy", about = "Codex network sandbox proxy")]
pub struct Args {
#[command(subcommand)]
pub command: Option<Command>,
}
#[derive(Debug, Clone, Subcommand)]
pub enum Command {
/// Initialize the Codex network proxy directories (e.g. MITM cert paths).
Init,
}
pub use network_policy::NetworkDecision;
pub use network_policy::NetworkPolicyDecider;
pub use network_policy::NetworkPolicyRequest;
pub use network_policy::NetworkProtocol;
pub use proxy::Args;
pub use proxy::Command;
pub use proxy::NetworkProxy;
pub use proxy::NetworkProxyBuilder;
pub use proxy::NetworkProxyHandle;
pub use proxy::run_init;
pub async fn run_main(args: Args) -> Result<()> {
tracing_subscriber::fmt::init();
if let Some(Command::Init) = args.command {
init::run_init()?;
run_init()?;
return Ok(());
}
if cfg!(not(target_os = "macos")) {
warn!("allowUnixSockets is macOS-only; requests will be rejected on this platform");
}
let state = Arc::new(AppState::new().await?);
let runtime = config::resolve_runtime(&state.current_cfg().await?);
let http_addr: SocketAddr = runtime.http_addr;
let socks_addr: SocketAddr = runtime.socks_addr;
let admin_addr: SocketAddr = runtime.admin_addr;
let http_task = http_proxy::run_http_proxy(state.clone(), http_addr);
let socks_task = socks5::run_socks5(state.clone(), socks_addr);
let admin_task = admin::run_admin_api(state.clone(), admin_addr);
tokio::try_join!(http_task, socks_task, admin_task)?;
Ok(())
let proxy = NetworkProxy::from_cli_args(args).await?;
proxy.run().await?.wait().await
}

View File

@@ -1,8 +1,20 @@
use anyhow::Result;
use clap::Parser;
use codex_network_proxy::Args;
use codex_network_proxy::Command;
use codex_network_proxy::NetworkProxy;
use codex_network_proxy::run_init;
#[tokio::main]
async fn main() -> Result<()> {
codex_network_proxy::run_main(Args::parse()).await
tracing_subscriber::fmt::init();
let args = Args::parse();
if let Some(Command::Init) = args.command {
run_init()?;
return Ok(());
}
let proxy = NetworkProxy::from_cli_args(args).await?;
proxy.run().await?.wait().await
}

View File

@@ -8,12 +8,12 @@ use crate::state::BlockedRequest;
use anyhow::Context as _;
use anyhow::Result;
use anyhow::anyhow;
use rama::Context;
use rama::Layer;
use rama::Service;
use rama::bytes::Bytes;
use rama::error::BoxError;
use rama::error::OpaqueError;
use rama::extensions::ExtensionsRef;
use rama::futures::stream::Stream;
use rama::http::Body;
use rama::http::HeaderValue;
@@ -29,6 +29,7 @@ use rama::http::layer::upgrade::Upgraded;
use rama::http::server::HttpServer;
use rama::net::proxy::ProxyTarget;
use rama::net::stream::SocketInfo;
use rama::rt::Executor;
use rama::service::service_fn;
use rama::tls::rustls::dep::pki_types::CertificateDer;
use rama::tls::rustls::dep::pki_types::PrivateKeyDer;
@@ -63,7 +64,7 @@ use rcgen_rama::SanType;
pub struct MitmState {
issuer: Issuer<'static, KeyPair>,
upstream: rama::service::BoxService<(), Request, Response, OpaqueError>,
upstream: rama::service::BoxService<Request, Response, OpaqueError>,
inspect: bool,
max_body_bytes: usize,
}
@@ -88,16 +89,17 @@ impl MitmState {
let issuer: Issuer<'static, KeyPair> =
Issuer::from_ca_cert_pem(&ca_cert_pem, ca_key).context("failed to parse CA cert")?;
let tls_config = rama::tls::rustls::client::TlsConnectorData::new_http_auto()
let tls_config = rama::tls::rustls::client::TlsConnectorData::try_new_http_auto()
.context("create upstream TLS config")?;
let upstream: rama::service::BoxService<(), Request, Response, OpaqueError> =
EasyHttpWebClient::builder()
let upstream: rama::service::BoxService<Request, Response, OpaqueError> =
EasyHttpWebClient::connector_builder()
// Use a direct transport connector (no upstream proxy) to avoid proxy loops.
.with_default_transport_connector()
.without_tls_proxy_support()
.without_proxy_support()
.with_tls_support_using_rustls(Some(tls_config))
.build()
.with_default_http_connector()
.build_client()
.boxed();
Ok(Self {
@@ -135,23 +137,26 @@ impl MitmState {
}
}
pub async fn mitm_tunnel<S>(ctx: Context<S>, upgraded: Upgraded) -> Result<()>
where
S: Clone + Send + Sync + 'static,
{
let state = ctx
pub async fn mitm_tunnel(upgraded: Upgraded) -> Result<()> {
let state = upgraded
.extensions()
.get::<Arc<MitmState>>()
.cloned()
.context("missing MITM state")?;
let target = ctx
let target = upgraded
.extensions()
.get::<ProxyTarget>()
.context("missing proxy target")?
.0
.clone();
let host = normalize_host(&target.host().to_string());
let host = normalize_host(&target.host.to_string());
let acceptor_data = state.tls_acceptor_data_for_host(&host)?;
let executor = ctx.executor().clone();
let executor = upgraded
.extensions()
.get::<Executor>()
.cloned()
.unwrap_or_default();
let http_service = HttpServer::auto(executor).service(
(
@@ -166,20 +171,14 @@ where
.into_layer(http_service);
https_service
.serve(ctx, upgraded)
.serve(upgraded)
.await
.map_err(|err| anyhow!("MITM serve error: {err}"))?;
Ok(())
}
async fn handle_mitm_request<S>(
ctx: Context<S>,
req: Request,
) -> Result<Response, std::convert::Infallible>
where
S: Clone + Send + Sync + 'static,
{
let response = match forward_request(ctx, req).await {
async fn handle_mitm_request(req: Request) -> Result<Response, std::convert::Infallible> {
let response = match forward_request(req).await {
Ok(resp) => resp,
Err(err) => {
warn!("MITM upstream request failed: {err}");
@@ -189,27 +188,28 @@ where
Ok(response)
}
async fn forward_request<S>(ctx: Context<S>, req: Request) -> Result<Response>
where
S: Clone + Send + Sync + 'static,
{
let target = ctx
async fn forward_request(req: Request) -> Result<Response> {
let target = req
.extensions()
.get::<ProxyTarget>()
.context("missing proxy target")?
.0
.clone();
let target_host = normalize_host(&target.host().to_string());
let target_port = target.port();
let mode = ctx
let target_host = normalize_host(&target.host.to_string());
let target_port = target.port;
let mode = req
.extensions()
.get::<NetworkMode>()
.copied()
.unwrap_or(NetworkMode::Full);
let mitm = ctx
let mitm = req
.extensions()
.get::<Arc<MitmState>>()
.cloned()
.context("missing MITM state")?;
let app_state = ctx
let app_state = req
.extensions()
.get::<Arc<AppState>>()
.cloned()
.context("missing app state")?;
@@ -223,7 +223,8 @@ where
let method = req.method().as_str().to_string();
let path = path_and_query(req.uri());
let client = ctx
let client = req
.extensions()
.get::<SocketInfo>()
.map(|info| info.peer_addr().to_string());
@@ -276,10 +277,7 @@ where
};
let upstream_req = Request::from_parts(parts, body);
let upstream_resp = mitm
.upstream
.serve(ctx.map_state(|_| ()), upstream_req)
.await?;
let upstream_resp = mitm.upstream.serve(upstream_req).await?;
respond_with_inspection(
upstream_resp,
inspect,
@@ -428,7 +426,7 @@ fn build_https_uri(authority: &str, path: &str) -> Result<Uri> {
fn path_and_query(uri: &Uri) -> String {
uri.path_and_query()
.map(rama::http::dep::http::uri::PathAndQuery::as_str)
.map(rama::http::uri::PathAndQuery::as_str)
.unwrap_or("/")
.to_string()
}

View File

@@ -0,0 +1,113 @@
use crate::state::AppState;
use anyhow::Result;
use async_trait::async_trait;
use std::future::Future;
use std::sync::Arc;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NetworkProtocol {
Http,
HttpsConnect,
Socks5Tcp,
Socks5Udp,
}
#[derive(Clone, Debug)]
pub struct NetworkPolicyRequest {
pub protocol: NetworkProtocol,
pub host: String,
pub port: u16,
pub client_addr: Option<String>,
pub method: Option<String>,
pub command: Option<String>,
pub exec_policy_hint: Option<String>,
}
impl NetworkPolicyRequest {
#[must_use]
pub fn new(
protocol: NetworkProtocol,
host: String,
port: u16,
client_addr: Option<String>,
method: Option<String>,
command: Option<String>,
exec_policy_hint: Option<String>,
) -> Self {
Self {
protocol,
host,
port,
client_addr,
method,
command,
exec_policy_hint,
}
}
}
#[derive(Clone, Debug)]
pub enum NetworkDecision {
Allow,
Deny { reason: String },
}
impl NetworkDecision {
#[must_use]
pub fn deny(reason: impl Into<String>) -> Self {
let reason = reason.into();
let reason = if reason.is_empty() {
"policy_denied".to_string()
} else {
reason
};
Self::Deny { reason }
}
}
/// Decide whether a network request should be allowed.
///
/// If `command` or `exec_policy_hint` is provided, callers can map exec-policy
/// approvals to network access (e.g., allow all requests for commands matching
/// approved prefixes like `curl *`).
#[async_trait]
pub trait NetworkPolicyDecider: Send + Sync + 'static {
async fn decide(&self, req: NetworkPolicyRequest) -> NetworkDecision;
}
#[async_trait]
impl<D: NetworkPolicyDecider + ?Sized> NetworkPolicyDecider for Arc<D> {
async fn decide(&self, req: NetworkPolicyRequest) -> NetworkDecision {
(**self).decide(req).await
}
}
#[async_trait]
impl<F, Fut> NetworkPolicyDecider for F
where
F: Fn(NetworkPolicyRequest) -> Fut + Send + Sync + 'static,
Fut: Future<Output = NetworkDecision> + Send,
{
async fn decide(&self, req: NetworkPolicyRequest) -> NetworkDecision {
(self)(req).await
}
}
pub(crate) async fn evaluate_host_policy(
state: &AppState,
decider: Option<&Arc<dyn NetworkPolicyDecider>>,
request: &NetworkPolicyRequest,
) -> Result<NetworkDecision> {
let (blocked, reason) = state.host_blocked(&request.host, request.port).await?;
if !blocked {
return Ok(NetworkDecision::Allow);
}
if reason == "not_allowed"
&& let Some(decider) = decider
{
return Ok(decider.decide(request.clone()).await);
}
Ok(NetworkDecision::deny(reason))
}

View File

@@ -0,0 +1,181 @@
use crate::admin;
use crate::config;
use crate::http_proxy;
use crate::init;
use crate::network_policy::NetworkPolicyDecider;
use crate::socks5;
use crate::state::AppState;
use anyhow::Result;
use clap::Parser;
use clap::Subcommand;
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 {
#[command(subcommand)]
pub command: Option<Command>,
/// Enable SOCKS5 UDP associate support (default: disabled).
#[arg(long, default_value_t = false)]
pub enable_socks5_udp: bool,
}
#[derive(Debug, Clone, Subcommand)]
pub enum Command {
/// Initialize the Codex network proxy directories (e.g. MITM cert paths).
Init,
}
#[derive(Clone, Default)]
pub struct NetworkProxyBuilder {
state: Option<Arc<AppState>>,
http_addr: Option<SocketAddr>,
socks_addr: Option<SocketAddr>,
admin_addr: Option<SocketAddr>,
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
enable_socks5_udp: bool,
}
impl NetworkProxyBuilder {
#[must_use]
pub fn state(mut self, state: Arc<AppState>) -> Self {
self.state = Some(state);
self
}
#[must_use]
pub fn http_addr(mut self, addr: SocketAddr) -> Self {
self.http_addr = Some(addr);
self
}
#[must_use]
pub fn socks_addr(mut self, addr: SocketAddr) -> Self {
self.socks_addr = Some(addr);
self
}
#[must_use]
pub fn admin_addr(mut self, addr: SocketAddr) -> Self {
self.admin_addr = Some(addr);
self
}
#[must_use]
pub fn policy_decider<D>(mut self, decider: D) -> Self
where
D: NetworkPolicyDecider,
{
self.policy_decider = Some(Arc::new(decider));
self
}
#[must_use]
pub fn policy_decider_arc(mut self, decider: Arc<dyn NetworkPolicyDecider>) -> Self {
self.policy_decider = Some(decider);
self
}
#[must_use]
pub fn enable_socks5_udp(mut self, enabled: bool) -> Self {
self.enable_socks5_udp = enabled;
self
}
pub async fn build(self) -> Result<NetworkProxy> {
let state = match self.state {
Some(state) => state,
None => Arc::new(AppState::new().await?),
};
let runtime = config::resolve_runtime(&state.current_cfg().await?);
Ok(NetworkProxy {
state,
http_addr: self.http_addr.unwrap_or(runtime.http_addr),
socks_addr: self.socks_addr.unwrap_or(runtime.socks_addr),
admin_addr: self.admin_addr.unwrap_or(runtime.admin_addr),
policy_decider: self.policy_decider,
enable_socks5_udp: self.enable_socks5_udp,
})
}
}
#[derive(Clone)]
pub struct NetworkProxy {
state: Arc<AppState>,
http_addr: SocketAddr,
socks_addr: SocketAddr,
admin_addr: SocketAddr,
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
enable_socks5_udp: bool,
}
impl NetworkProxy {
#[must_use]
pub fn builder() -> NetworkProxyBuilder {
NetworkProxyBuilder::default()
}
pub async fn from_cli_args(args: Args) -> Result<Self> {
let mut builder = Self::builder();
builder = builder.enable_socks5_udp(args.enable_socks5_udp);
builder.build().await
}
pub async fn run(&self) -> Result<NetworkProxyHandle> {
if cfg!(not(target_os = "macos")) {
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 socks_task = tokio::spawn(socks5::run_socks5(
self.state.clone(),
self.socks_addr,
self.policy_decider.clone(),
self.enable_socks5_udp,
));
let admin_task = tokio::spawn(admin::run_admin_api(self.state.clone(), self.admin_addr));
Ok(NetworkProxyHandle {
http_task,
socks_task,
admin_task,
})
}
}
pub struct NetworkProxyHandle {
http_task: JoinHandle<Result<()>>,
socks_task: JoinHandle<Result<()>>,
admin_task: JoinHandle<Result<()>>,
}
impl NetworkProxyHandle {
pub async fn wait(self) -> Result<()> {
self.http_task.await??;
self.socks_task.await??;
self.admin_task.await??;
Ok(())
}
pub async fn shutdown(self) -> Result<()> {
self.http_task.abort();
self.socks_task.abort();
self.admin_task.abort();
let _ = self.http_task.await;
let _ = self.socks_task.await;
let _ = self.admin_task.await;
Ok(())
}
}
pub fn run_init() -> Result<()> {
init::run_init()
}

View File

@@ -1,16 +1,24 @@
use crate::config::NetworkMode;
use crate::network_policy::NetworkDecision;
use crate::network_policy::NetworkPolicyDecider;
use crate::network_policy::NetworkPolicyRequest;
use crate::network_policy::NetworkProtocol;
use crate::network_policy::evaluate_host_policy;
use crate::policy::normalize_host;
use crate::state::AppState;
use crate::state::BlockedRequest;
use anyhow::Context as _;
use anyhow::Result;
use rama::Context;
use rama::Layer;
use rama::Service;
use rama::layer::AddExtensionLayer;
use rama::extensions::ExtensionsRef;
use rama::layer::AddInputExtensionLayer;
use rama::net::stream::SocketInfo;
use rama::proxy::socks5::Socks5Acceptor;
use rama::proxy::socks5::server::DefaultConnector;
use rama::proxy::socks5::server::DefaultUdpRelay;
use rama::proxy::socks5::server::udp::RelayRequest;
use rama::proxy::socks5::server::udp::RelayResponse;
use rama::service::service_fn;
use rama::tcp::client::Request as TcpRequest;
use rama::tcp::client::service::TcpConnector;
@@ -22,7 +30,12 @@ use tracing::error;
use tracing::info;
use tracing::warn;
pub async fn run_socks5(state: Arc<AppState>, addr: SocketAddr) -> Result<()> {
pub async fn run_socks5(
state: Arc<AppState>,
addr: SocketAddr,
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
enable_socks5_udp: bool,
) -> Result<()> {
let listener = TcpListener::build()
.bind(addr)
.await
@@ -44,80 +57,193 @@ pub async fn run_socks5(state: Arc<AppState>, addr: SocketAddr) -> Result<()> {
}
let tcp_connector = TcpConnector::default();
let policy_tcp_connector = service_fn(move |ctx: Context<()>, req: TcpRequest| {
let tcp_connector = tcp_connector.clone();
async move {
let app_state = ctx
.get::<Arc<AppState>>()
.cloned()
.ok_or_else(|| io::Error::other("missing state"))?;
let policy_tcp_connector = service_fn({
let policy_decider = policy_decider.clone();
move |req: TcpRequest| {
let tcp_connector = tcp_connector.clone();
let policy_decider = policy_decider.clone();
async move {
let app_state = req
.extensions()
.get::<Arc<AppState>>()
.cloned()
.ok_or_else(|| io::Error::other("missing state"))?;
let host = normalize_host(&req.authority().host().to_string());
let port = req.authority().port();
let client = ctx
.get::<SocketInfo>()
.map(|info| info.peer_addr().to_string());
let host = normalize_host(&req.authority.host.to_string());
let port = req.authority.port;
let client = req
.extensions()
.get::<SocketInfo>()
.map(|info| info.peer_addr().to_string());
match app_state.network_mode().await {
Ok(NetworkMode::Limited) => {
let _ = app_state
.record_blocked(BlockedRequest::new(
host.clone(),
"method_not_allowed".to_string(),
client.clone(),
None,
Some(NetworkMode::Limited),
"socks5".to_string(),
))
.await;
let client = client.as_deref().unwrap_or_default();
warn!(
"SOCKS blocked by method policy (client={client}, host={host}, mode=limited, allowed_methods=GET, HEAD, OPTIONS)"
);
return Err(
io::Error::new(io::ErrorKind::PermissionDenied, "blocked").into()
);
}
Ok(NetworkMode::Full) => {}
Err(err) => {
error!("failed to evaluate method policy: {err}");
return Err(io::Error::other("proxy error").into());
}
}
match app_state.network_mode().await {
Ok(NetworkMode::Limited) => {
let _ = app_state
.record_blocked(BlockedRequest::new(
host.clone(),
"method_not_allowed".to_string(),
client.clone(),
None,
Some(NetworkMode::Limited),
"socks5".to_string(),
))
.await;
let client = client.as_deref().unwrap_or_default();
warn!(
"SOCKS blocked by method policy (client={client}, host={host}, mode=limited, allowed_methods=GET, HEAD, OPTIONS)"
);
return Err(io::Error::new(io::ErrorKind::PermissionDenied, "blocked").into());
}
Ok(NetworkMode::Full) => {}
Err(err) => {
error!("failed to evaluate method policy: {err}");
return Err(io::Error::other("proxy error").into());
let request = NetworkPolicyRequest::new(
NetworkProtocol::Socks5Tcp,
host.clone(),
port,
client.clone(),
None,
None,
None,
);
match evaluate_host_policy(&app_state, policy_decider.as_ref(), &request).await {
Ok(NetworkDecision::Deny { reason }) => {
let _ = app_state
.record_blocked(BlockedRequest::new(
host.clone(),
reason.clone(),
client.clone(),
None,
None,
"socks5".to_string(),
))
.await;
let client = client.as_deref().unwrap_or_default();
warn!("SOCKS blocked (client={client}, host={host}, reason={reason})");
return Err(
io::Error::new(io::ErrorKind::PermissionDenied, "blocked").into()
);
}
Ok(NetworkDecision::Allow) => {
let client = client.as_deref().unwrap_or_default();
info!("SOCKS allowed (client={client}, host={host}, port={port})");
}
Err(err) => {
error!("failed to evaluate host: {err}");
return Err(io::Error::other("proxy error").into());
}
}
tcp_connector.serve(req).await
}
match app_state.host_blocked(&host, port).await {
Ok((true, reason)) => {
let _ = app_state
.record_blocked(BlockedRequest::new(
host.clone(),
reason.clone(),
client.clone(),
None,
None,
"socks5".to_string(),
))
.await;
let client = client.as_deref().unwrap_or_default();
warn!("SOCKS blocked (client={client}, host={host}, reason={reason})");
return Err(io::Error::new(io::ErrorKind::PermissionDenied, "blocked").into());
}
Ok((false, _)) => {
let client = client.as_deref().unwrap_or_default();
info!("SOCKS allowed (client={client}, host={host}, port={port})");
}
Err(err) => {
error!("failed to evaluate host: {err}");
return Err(io::Error::other("proxy error").into());
}
}
tcp_connector.serve(ctx, req).await
}
});
let socks_connector = DefaultConnector::default().with_connector(policy_tcp_connector);
let socks_acceptor = Socks5Acceptor::new().with_connector(socks_connector);
let base = Socks5Acceptor::new().with_connector(socks_connector);
listener
.serve(AddExtensionLayer::new(state).into_layer(socks_acceptor))
.await;
if enable_socks5_udp {
let udp_state = state.clone();
let udp_decider = policy_decider.clone();
let udp_relay = DefaultUdpRelay::default().with_async_inspector(service_fn(
move |request: RelayRequest| {
let udp_state = udp_state.clone();
let udp_decider = udp_decider.clone();
async move {
let RelayRequest {
server_address,
payload,
extensions,
..
} = request;
let host = normalize_host(&server_address.ip_addr.to_string());
let port = server_address.port;
let client = extensions
.get::<SocketInfo>()
.map(|info| info.peer_addr().to_string());
match udp_state.network_mode().await {
Ok(NetworkMode::Limited) => {
let _ = udp_state
.record_blocked(BlockedRequest::new(
host.clone(),
"method_not_allowed".to_string(),
client.clone(),
None,
Some(NetworkMode::Limited),
"socks5-udp".to_string(),
))
.await;
return Ok(RelayResponse {
maybe_payload: None,
extensions,
});
}
Ok(NetworkMode::Full) => {}
Err(err) => {
error!("failed to evaluate method policy: {err}");
return Err(io::Error::other("proxy error"));
}
}
let request = NetworkPolicyRequest::new(
NetworkProtocol::Socks5Udp,
host.clone(),
port,
client.clone(),
None,
None,
None,
);
match evaluate_host_policy(&udp_state, udp_decider.as_ref(), &request).await {
Ok(NetworkDecision::Deny { reason }) => {
let _ = udp_state
.record_blocked(BlockedRequest::new(
host.clone(),
reason.clone(),
client.clone(),
None,
None,
"socks5-udp".to_string(),
))
.await;
let client = client.as_deref().unwrap_or_default();
warn!(
"SOCKS UDP blocked (client={client}, host={host}, reason={reason})"
);
Ok(RelayResponse {
maybe_payload: None,
extensions,
})
}
Ok(NetworkDecision::Allow) => Ok(RelayResponse {
maybe_payload: Some(payload),
extensions,
}),
Err(err) => {
error!("failed to evaluate UDP host: {err}");
Err(io::Error::other("proxy error"))
}
}
}
},
));
let socks_acceptor = base.with_udp_associator(udp_relay);
listener
.serve(AddInputExtensionLayer::new(state).into_layer(socks_acceptor))
.await;
} else {
listener
.serve(AddInputExtensionLayer::new(state).into_layer(base))
.await;
}
Ok(())
}