windmill-labs/windmill · error

HTTP proxy sent unexpected bytes after CONNECT response

Error message

HTTP proxy sent unexpected bytes after CONNECT response

What it means

After reading the CONNECT response, `http_connect_tunnel` checks `BufReader`'s internal buffer. A conforming HTTP proxy sends nothing after the CONNECT response until the client speaks. If buffered bytes remain, the proxy already sent extra data (pipelined response, injected banner, HTTP redirect, or a non-CONNECT response body); handing the raw socket to the TLS layer would silently discard those bytes and corrupt the WebSocket/TLS handshake, so the function fails fast.

Source

Thrown at backend/windmill-trigger-websocket/src/proxy.rs:308

    }

    if !status_ok {
        return Err(io::Error::new(
            io::ErrorKind::Other,
            format!(
                "HTTP proxy CONNECT to {} rejected: {}",
                host_header,
                status_line.trim_end()
            ),
        ));
    }

    // A conforming proxy stays silent after the CONNECT response until the
    // client speaks. If our read buffer is non-empty, the proxy spoke
    // first — handing the raw socket to TLS would silently drop those
    // bytes and break the handshake.
    if !reader.buffer().is_empty() {
        return Err(io::Error::new(
            io::ErrorKind::Other,
            "HTTP proxy sent unexpected bytes after CONNECT response",
        ));
    }

    Ok(reader.into_inner())
}

#[cfg(test)]
mod tests {
    //! The single live test (`http_connect_tunnel_…_unwraps_stream`) drives
    //! a real `TcpListener` masquerading as a proxy and verifies both the
    //! on-the-wire CONNECT request and that the returned `TcpStream`
    //! actually carries tunneled bytes. The other proxy-URL and NO_PROXY
    //! checks are kept under `#[ignore]` for manual debugging — they cover
    //! logic that's mostly delegated to `url::Url::parse` and trivial
    //! string matching, so re-running them on every CI build is low ROI.
    use super::*;

View on GitHub (pinned to e474e8803c)

Solutions

  1. Verify the proxy URL scheme matches the proxy listener (plain http:// CONNECT port vs https:// TLS port) and fix the scheme
  2. Confirm the endpoint is an HTTP CONNECT proxy, not a SOCKS proxy (use a SOCKS-aware config or change the proxy)
  3. Capture traffic (tcpdump/Wireshark on the client↔proxy link) to see what bytes the proxy sends after the response
  4. Disable or reconfigure any TLS-interception appliance (e.g. MITM AV/firewall module) for this destination

Example fix

// before
let proxy = "http://proxy.corp:443"; // 443 is the TLS listener -> banner bytes after CONNECT
// after
let proxy = "https://proxy.corp:443"; // or "http://proxy.corp:3128" for the plain listener
Defensive patterns

Strategy: validation

Validate before calling

// fail fast if the endpoint is not a plain HTTP CONNECT proxy
let parsed = url::Url::parse(&proxy_url)?;
let is_plain_http = parsed.scheme() == "http";
let is_socks = matches!(parsed.scheme(), "socks4" | "socks4a" | "socks5");
if !is_plain_http && !is_socks {
    return Err(anyhow!("unsupported/ambiguous proxy scheme: {}", parsed.scheme()));
}

Try / catch

match connect_async_with_proxy(&url, &proxy).await {
    Err(WsError::Io(e)) if e.to_string().contains("unexpected bytes after CONNECT") => {
        eprintln!("{} is not a conforming CONNECT proxy: {}", proxy, e);
        // fix scheme/port or replace the intercepting proxy before retrying
    }
    Err(e) => return Err(e.into()),
    Ok(_) => {}
}

Prevention

When it happens

Trigger: Calling `connect_async_with_proxy` (via `test_connection` or `get_consumer`) when the proxy sends bytes immediately after its CONNECT response — e.g. it sent a redirect body, an error page after a non-standard status, TLS on the proxy link, or is not actually an HTTP CONNECT proxy (some intercepting/mTLS proxies greet the client first).

Common situations: Pointing the client at a TLS/HTTPS proxy port with a plaintext CONNECT; pointing at a SOCKS proxy or transparent interception proxy that isn't CONNECT-capable; an anti-virus / SSL-inspection appliance injecting a banner; a misbehaving proxy that pipelines responses.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/b4878a52f30bd00e. Report an issue: GitHub.