1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//! External egress proxy management (Phase 3a of #934).
//!
//! ## What this module does
//!
//! Phase 3a delivered the *enforcement layer* for network egress:
//!
//! 1. **Env-var bouquet** ([`mod@env`]) — the standard list of variables
//! (`HTTPS_PROXY`, `SSL_CERT_FILE`, etc.) that a sandboxed subprocess
//! needs so well-behaved HTTP clients (curl, gh, npm, pip, cargo, go,
//! node, python) route their traffic through a single hop.
//!
//! 2. **External proxy lifecycle** ([`external`]) — spawn a user-provided
//! proxy command (mitmproxy, Squid, Zscaler agent, anything that speaks
//! HTTP CONNECT), wait for it to bind, kill it cleanly on drop.
//!
//! 3. **`ProxyHandle`** ([`handle`]) — the polymorphic lifecycle wrapper
//! every spawn path returns, so callers can store any proxy variant
//! behind one type without trait objects or enums on the hot path.
//!
//! Phase 3b adds the built-in proxy ([`builtin`]) that implements
//! the *policy layer* (domain allowlist filtering). All variants plug
//! into the same env-var bouquet and the same [`ProxyHandle`] type —
//! applications can't tell whether they're talking to our proxy or
//! the user's, and the slot manager doesn't care which spawn path was
//! used to create the handle.
//!
//! ## Why support an external proxy at all?
//!
//! Three concrete user populations:
//!
//! - **Corporate MITM environments** (Zscaler, Bluecoat, Palo Alto) already
//! have a proxy doing TLS interception with a corporate CA. Stacking our
//! proxy on top would create fragile double-MITM chains.
//! - **`mitmproxy` debuggers** want to inspect their agent's traffic without
//! a second proxy in the way.
//! - **Air-gapped / homelab users** with Squid or Artifactory pull-through
//! already have egress infrastructure.
//!
//! Mirrors what Codex does (chain to upstream via `HTTPS_PROXY`) and what
//! Gemini CLI does (`GEMINI_SANDBOX_PROXY_COMMAND` external-only).
//!
//! ## Fail-open semantics
//!
//! [`ExternalProxy::spawn`] returns `Err` when the proxy can't be started.
//! Callers are expected to **warn and continue without restrictions** rather
//! than fail the session — same pattern as Claude Code's `upstreamproxy`. A
//! broken proxy must never break an otherwise-working session.
//!
//! ## Module layout
//!
//! Split per concern in commit-1 of Phase 3b so future additions don't push
//! any single file past 600 lines:
//!
//! ```text
//! proxy/
//! ├── mod.rs — this docstring + shared internal helpers
//! ├── env.rs — env-var bouquet + DEFAULT_NO_PROXY (3a) + socks5 (3d)
//! ├── external.rs — ExternalProxy::spawn (3a)
//! ├── handle.rs — ProxyHandle (External + BuiltIn variants)
//! ├── filter.rs — hostname allowlist (3b)
//! ├── server.rs — HTTP CONNECT proxy (3b)
//! ├── builtin.rs — BuiltInProxy::spawn (3b)
//! ├── socks5.rs — SOCKS5 server (3d)
//! ├── builtin_socks5.rs — BuiltInSocks5Proxy::spawn (3d)
//! └── upstream.rs — corp HTTPS_PROXY chaining (3d.3)
//! ```
pub use BuiltInProxy;
pub use BuiltInSocks5Proxy;
pub use ;
pub use ExternalProxy;
pub use ;
pub use ProxyHandle;
pub use Server;
pub use Socks5Server;
pub use UpstreamConfig;
// ── Shared internal helpers ──────────────────────────────────────────────────
//
// These two helpers are needed by every spawn path (today: `ExternalProxy`;
// 3b: `BuiltInProxy`). Living in `mod.rs` as `pub(super)` keeps them close
// to the docstring that explains why they exist while still being reachable
// from sibling modules.
use ;
use Duration;
use TcpStream;
use ;
/// Pick an unused port by binding `0` and immediately dropping.
///
/// There's a classic TOCTOU hole here (another process could grab the port
/// before the proxy does), but it's the same trick every test suite uses
/// and the failure mode (proxy bind error) is caught downstream by
/// [`wait_for_bind`].
pub
/// Poll `127.0.0.1:port` with TCP connects until success or timeout.
///
/// Used to confirm a freshly-spawned proxy has actually bound its socket
/// before we start handing its env vars to subprocesses. Exponential
/// backoff capped at 200 ms so we don't hammer the loopback stack.
pub async