Add a hidden HTTP/3 TCP tunnel command (#45900)

## What changed

Add `codex tcp-tunnel` and the `codex-tcp-tunnel` crate to forward loopback TCP connections to an explicit target through a TLS-verified HTTP/3 CONNECT proxy.

- Require the proxy origin to match an approved HTTPS origin in a supplied policy file.
- Read bearer tokens and optional bounded, non-forwarding `x-` headers from stdin. Support token updates for new connections, `LISTENING` and `AUTH_UPDATED` notifications, and shutdown when the control pipe closes in token-update mode.
- Preserve the listener across proxy reconnects without replaying TCP streams, and let accepted streams continue while a proxy drains.

## Testing

Add tests for hidden CLI parsing, proxy and target validation, credential renewal, control-pipe closure, and invalid input without secret disclosure. A local HTTP/3 proxy test covers token replacement, transport recovery without stream replay, and graceful draining.

GitOrigin-RevId: c6af3025301c61e9fe90940cf5a6df0039465e69
This commit is contained in:
richardopenai
2026-09-16 08:54:12 +00:00
committed by copyberry
parent 50d77959bf
commit 2aff7208fe
11 changed files with 1073 additions and 0 deletions

View File

@@ -150,6 +150,9 @@ enum Subcommand {
/// Browse all agent sessions on the shared local app-server daemon.
Agents(AgentsCommand),
/// Internal: forward a local TCP socket through an HTTP/3 CONNECT proxy.
#[clap(hide = true)]
TcpTunnel(codex_tcp_tunnel::Args),
/// Run Codex non-interactively.
#[clap(visible_alias = "e")]
Exec(ExecCli),
@@ -1276,6 +1279,9 @@ async fn cli_main(
.await?;
handle_app_exit(exit_info, daemon_cli_executable.as_deref())?;
}
Some(Subcommand::TcpTunnel(args)) => {
return codex_tcp_tunnel::run(args).await;
}
Some(Subcommand::Exec(mut exec_cli)) => {
reject_remote_mode_for_subcommand(
root_remote.as_deref(),
@@ -2674,6 +2680,7 @@ fn unsupported_subcommand_name_for_strict_config(
Some(Subcommand::ResponsesApiProxy(_)) => Some("responses-api-proxy"),
Some(Subcommand::StdioToUds(_)) => Some("stdio-to-uds"),
Some(Subcommand::Features(_)) => Some("features"),
Some(Subcommand::TcpTunnel(_)) => Some("tcp-tunnel"),
}
}
@@ -3780,6 +3787,36 @@ mod tests {
err.to_string()
}
#[test]
fn tcp_tunnel_is_hidden_and_accepts_its_control_input_contract() {
let root_help = help_from_args(&["codex", "--help"]);
assert!(!root_help.contains("tcp-tunnel"), "{root_help}");
let help = help_from_args(&["codex", "tcp-tunnel", "--help"]);
for option in [
"--target",
"--auth-token-stdin",
"--auth-token-updates-stdin",
"--connect-headers-stdin",
] {
assert!(help.contains(option), "{help}");
}
let cli = MultitoolCli::try_parse_from([
"codex",
"tcp-tunnel",
"--proxy-url",
"https://proxy.example.org",
"--proxy-origins-file",
"/tmp/approved-origins",
"--target",
"[::1]:22",
"--auth-token-stdin",
"--auth-token-updates-stdin",
"--connect-headers-stdin",
])
.expect("generic tunnel should parse");
assert!(matches!(cli.subcommand, Some(Subcommand::TcpTunnel(_))));
}
#[test]
fn plugin_marketplace_help_uses_plugin_namespace() {
let help = help_from_args(&["codex", "plugin", "marketplace", "--help"]);