Repository navigation
Add HTTP CONNECT proxy support for WebSocket connections - #148
Conversation
This enables WebSocket connections through HTTP CONNECT proxies, which is
essential for scenarios like:
- Corporate proxy environments
- MITM proxy debugging/inspection
- Network traffic analysis
Changes to Conn module:
- Add :proxy option to connection_option type (accepts URL string or map)
- New open_socket/1 clause for SSL connections with proxy:
1. Connect to proxy via plain TCP
2. Send HTTP CONNECT request to establish tunnel
3. Wait for 200 OK response
4. Upgrade TCP socket to SSL over the tunnel
- Add helper functions:
- parse_proxy_option/1 - handles URL strings and maps
- send_connect_request/2 - sends HTTP CONNECT to proxy
- receive_connect_response/3 - parses proxy response
- parse_connect_headers/2 - consumes response headers
Usage:
WebSockex.start_link(url, module, state,
proxy: "http://localhost:8080"
)
Or with a map:
WebSockex.start_link(url, module, state,
proxy: %{host: "proxy.example.com", port: 8080}
)
Tunnel plain ws:// connections, send proxy basic auth, honor socket options on the proxy connect, and set TLS SNI to the target host when upgrading the tunnel. Cover the proxy path with connection tests. Co-authored-by: Dominic Letz <dominicletz@users.noreply.github.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.
Bugbot Autofix prepared fixes for all 3 issues found in the latest run.
- ✅ Fixed: Proxy socket leak on failure
- After the proxy TCP connect succeeds, CONNECT, response, and TLS-upgrade failures now close that socket before returning the error.
- ✅ Fixed: IPv6 CONNECT target is malformed
- CONNECT request-target and Host now bracket IPv6 literals, so a target such as ::1 is sent as [::1]:port.
- ✅ Fixed: Invalid proxy config fails open
- Unparseable proxy URLs and configs missing host or port now return an error from new/2 and open_socket/1 instead of dialing the target directly.
You can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit 8621d6e. Configure here.
| {:ok, Map.put(conn, :socket, socket)} | ||
| else | ||
| {:error, error} -> {:error, %WebSockex.ConnError{original: error}} | ||
| end |
There was a problem hiding this comment.
Proxy socket leak on failure
High Severity
After the TCP connection to the proxy succeeds, failures during CONNECT, the response read, or the TLS upgrade return an error without closing that socket. Reconnects plus auth, timeout, or handshake failures can leak file descriptors until the VM cannot open new connections.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 8621d6e. Configure here.
|
|
||
| request = [ | ||
| "CONNECT #{host_port} HTTP/1.1\r\n", | ||
| "Host: #{host_port}\r\n", |
There was a problem hiding this comment.
IPv6 CONNECT target is malformed
Medium Severity
The CONNECT request formats the target as host colon port. URI.parse stores IPv6 hosts without brackets, so the request line is invalid and standards-compliant proxies reject tunnels to IPv6 destinations.
Reviewed by Cursor Bugbot for commit 8621d6e. Configure here.
| end | ||
| end | ||
|
|
||
| defp parse_proxy_option(_), do: nil |
There was a problem hiding this comment.
Invalid proxy config fails open
Medium Severity
Unparseable proxy URLs and maps missing host or port become nil. open_socket then dials the WebSocket target directly, so a misconfigured proxy option silently bypasses the intended proxy.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 8621d6e. Configure here.
Close the proxy TCP socket when CONNECT, the response read, or the TLS upgrade fails so reconnects cannot leak descriptors. Bracket IPv6 literals in the CONNECT request-target and Host header. Reject invalid proxy options instead of dialing the WebSocket target directly.


Summary
Adds an HTTP CONNECT proxy option for
ws://andwss://connections. This was split out of #145 so the half-close work can land on its own.proxy:accepts a URL ("http://localhost:8080"or"http://user:p%40ss@proxy.test:8080") or a map with:host,:port, and optional:username/:password.CONNECT, and on200uses that socket for the WebSocket.wss://upgrades the tunnel with TLS and setsserver_name_indicationto the target host, so the default peer verification from Verify TLS peers by default #144 still checks the destination certificate.:socket_optionsapply to the TCP connection to the proxy.Tests
Connection tests cover URL credential parsing,
ws://tunneling (includingProxy-Authorization), refusing to dial the target directly when a proxy is set,socket_optionson the proxy connect, and hostname verification through a tunnel.On Elixir 1.18.4 / OTP 27,
mix format --check-formattedandmix compile --warn 10000 ings-as-errorspass.mix testhas the same pre-existing failure asmaster:open_socket rejects a hostname mismatch with supplied cacertsexpects{:tls_alert, {:bad_certificate, _}}, and OTP 27 reports{:tls_alert, {:handshake_failure, _}}. The new proxy tests pass.If
CONNECTor the TLS upgrade fails after the TCP connection to the proxy succeeds, that proxy socket is not closed.Note
Medium Risk
Changes core connection and TLS upgrade paths and sends proxy credentials on the wire; failed CONNECT/TLS steps may leave the proxy TCP socket open per the PR note.
Reviewed by Cursor Bugbot for commit 8621d6e. Configure here.