#545·wstunnel

HTTP proxy answers an unauthenticated CONNECT with 401 instead of 407, so clients that wait for a challenge never authenticate

Author: zoroyihan7Created Sep 8, 2026Updated Sep 9, 2026

Describe the bug

When the client is started with -L "http://…?login=…&password=…", a CONNECT carrying no Proxy-Authorization is answered with a bare 401 Unauthorized, with no Proxy-Authenticate header and an empty body.

RFC 7235 §3.2 reserves 407 Proxy Authentication Required for this, and requires Proxy-Authenticate on it. Because the response is a 401 with no challenge, a client cannot tell it apart from an origin-server error and has nothing telling it which scheme to offer.

Clients that send Proxy-Authorization preemptively never notice — curl with credentials in the proxy URL works, and so does Go net/http. Clients that wait for the challenge fail permanently, even when they hold the correct credentials.

Source, unchanged between v10.6.2 and v10.7.1 (wstunnel/src/protocols/http_proxy/server.rs):

rust
fn err_response() -> Result<Response<Empty<Bytes>>, &'static str> {
    info!("Un-authorized connection to http proxy");
    Ok(Response::builder().status(401).body(Empty::new()).unwrap())
}

A unit test in the same file pins the current status, so a fix would update it as well:

rust
assert_eq!(String::from_utf8_lossy(&buf)[..27], *"HTTP/1.0 401 Unauthorized\r\n");

To Reproduce

  1. Start a client exposing an authenticated http proxy:
wstunnel client -L "http://0.0.0.0:3128?login=u&password=p" wss://server.example.com
  1. Connect without credentials and let the client wait for a challenge:
$ curl -sv -x http://127.0.0.1:3128 https://api.github.com/
> CONNECT api.github.com:443 HTTP/1.1
< HTTP/1.1 401 Unauthorized
< connection: close
< content-length: 0
* CONNECT tunnel failed, response 401
  1. The same request with credentials sent preemptively succeeds:
$ curl -s -o /dev/null -w "%{http_code}\n" -x http://u:[email protected]:3128 https://api.github.com/
200
  1. A client that only attaches credentials after a 407 never recovers. A GitHub Actions runner (.NET HttpClient), whose proxy URL already carries the credentials, loops on:
System.Net.Http.HttpRequestException: The proxy tunnel request to proxy
'http://user:***@proxy.example.com:3128/' failed with status code '401'.
   at GitHub.Services.Common.VssHttpRetryMessageHandler.SendAsync(...)
Retriable exception: ... Sleeping for 30 seconds before retrying.

Expected behavior

A CONNECT with missing or invalid Proxy-Authorization is answered with 407 Proxy Authentication Required and Proxy-Authenticate: Basic realm="wstunnel", so a client can attach credentials on retry.

Keeping 401 for the non-CONNECT rejection just above it would preserve today’s behaviour for that case, if the distinction is intended.

Your wstunnel setup

Client command line (credentials redacted):

bash
wstunnel client -L=http://0.0.0.0:3128?login=***&password=*** \
  --http-upgrade-path-prefix=github-tunnel-*** \
  wss://server.example.com

Client log for each rejected attempt — this is the whole of it, the reject path logs nothing else:

2026-09-08T14:03:46.758003Z  INFO wstunnel::protocols::http_proxy::server: Un-authorized connection to http proxy
2026-09-08T14:03:46.758902Z  INFO wstunnel::protocols::http_proxy::server: Un-authorized connection to http proxy
2026-09-08T14:03:46.802854Z  INFO wstunnel::protocols::http_proxy::server: Un-authorized connection to http proxy

I do not control the server side here, so I cannot attach its --log-lvl=DEBUG output. The behaviour is entirely client-side and visible from the source above — the response is produced before any tunnel to the server is opened, so the server is not involved.

Desktop (please complete the following information):

  • OS: Linux (container, ghcr.io/erebe/wstunnel:v10.6.2), running on Kubernetes
  • Version: v10.6.2; source re-checked at v10.7.1 and identical

Additional context

Happy to send a PR if the direction looks right.