fix(cli): refuse --stealth + socks5:// proxy instead of silent breakage (#160)

The previous workaround in page.rs rewrote socks5:// to http:// before
handing the proxy to the stealth client. That works for Clash-style
mixed-mode proxies, but breaks plain SOCKS5 servers like `ssh -ND`,
producing a confusing `TunnelUnexpectedEof` deep in the network layer.

The stealth client is backed by wreq, which does not speak SOCKS5,
so silently substituting schemes can never be correct in general.

Refuse the combination at CLI startup with a clear message that tells
the user what to do (use --proxy http://... or drop --stealth) and
revert the rewrite in Page::new. SOCKS5 without --stealth still works
through the reqwest path.
This commit is contained in:
SGavrl committed 2026-05-15 21:23:53 +00:00
1 parent b2b41529c4
commit 8cb0cb429b
2 files changed
+68 -7

No files matched your search

+7 -5
View File
@@ -109,13 +109,15 @@ impl Page {
let frame_id = id.clone();
#[cfg(feature = "stealth")]
let stealth_client = if context.stealth {
// wreq doesn't support SOCKS5; Clash mixed port handles both.
let stealth_proxy = context.proxy_url.as_ref().map(|u| {
u.replacen("socks5://", "http://", 1)
});
// The wreq client backing StealthHttpClient does not speak SOCKS5.
// Callers must validate the proxy scheme up front and fail loudly
// (see obscura-cli) rather than silently rewriting socks5:// to
// http://, which only works when the upstream happens to be a
// Clash-style mixed-mode proxy and breaks plain SOCKS5 servers
// like `ssh -ND` (#160).
Some(Arc::new(StealthHttpClient::with_proxy(
context.cookie_jar.clone(),
stealth_proxy.as_deref(),
context.proxy_url.as_deref(),
)))
} else {
None