xai-org/grok-build · error

TLS handshake through proxy failed: {e}

Error message

TLS handshake through proxy failed: {e}

What it means

`tls_wrap` throws this when the rustls TLS handshake over the established CONNECT tunnel fails (`connector.connect(dns_name, stream)` returns an error). The TCP tunnel to the target through the proxy succeeded, but the TLS layer could not complete: the server presented an untrusted/self-signed certificate, the certificate does not match `server_name`, a TLS version/cipher mismatch occurred, or the peer closed the connection during the handshake. The certificate trust chain comes from `xai_grok_extra_ca::rustls_client_config()` (native roots plus extra CAs).

Source

Thrown at crates/codegen/xai-grok-shell/src/agent/proxy.rs:211

    }

    // 6. Reunite the split halves back into a TcpStream.
    let stream = reader.into_inner().reunite(writer_half)?;
    Ok(stream)
}

async fn tls_wrap(
    stream: TcpStream,
    server_name: &str,
) -> anyhow::Result<tokio_rustls::client::TlsStream<TcpStream>> {
    let connector = tokio_rustls::TlsConnector::from(xai_grok_extra_ca::rustls_client_config());
    let dns_name = rustls::pki_types::ServerName::try_from(server_name.to_string())
        .map_err(|e| anyhow::anyhow!("Invalid TLS server name '{server_name}': {e}"))?;

    let tls_stream = connector
        .connect(dns_name, stream)
        .await
        .map_err(|e| anyhow::anyhow!("TLS handshake through proxy failed: {e}"))?;

    Ok(tls_stream)
}

/// Parse a proxy URL into (host, port).
///
/// Accepted formats:
/// - `http://host:port`
/// - `http://host` (defaults to port 80)
/// - `host:port`
fn parse_proxy_url(url: &str) -> anyhow::Result<(String, u16)> {
    // Strip scheme if present.
    let without_scheme = url
        .strip_prefix("http://")
        .or_else(|| url.strip_prefix("https://"))
        .unwrap_or(url);

    // Strip trailing path/slash.

View on GitHub (pinned to bc7f02eddd)

Solutions

  1. Install the corporate/intercepting proxy's root CA into the system trust store so `xai_grok_extra_ca`'s native-root config picks it up (or add it to the extra-CA bundle)
  2. Verify the certificate with `openssl s_client -connect <host>:443 -servername <host>` — check chain, expiry, and SAN match against `target_host`
  3. Confirm the proxy is tunneling (CONNECT 200) rather than MITM-ing/rejecting; check proxy logs, or add the target to NO_PROXY and test a direct connection
  4. Ensure `target_host` matches the certificate's DNS name (an IP or wrong hostname causes verification failure)
  5. Check for TLS-version/cipher mismatch (very old/new servers) and network devices resetting the connection mid-handshake
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight: check the target's certificate is acceptable before opening the tunnel
// openssl s_client -connect host:443 -servername host </dev/null 2>/dev/null | \
//   openssl x509 -noout -subject -issuer -dates
// Ensure the issuer is your corporate root (if TLS-inspected) and SANs cover the host.

Try / catch

match connect_via_proxy(&proxy_url, host, 443).await {
    Err(e) if e.to_string().starts_with("TLS handshake through proxy failed") => {
        eprintln!("TLS to '{host}' failed via proxy: is a TLS-inspecting proxy re-signing traffic? Install its root CA.");
        // optionally retry once after CA refresh, or bypass proxy via NO_PROXY
    }
    r => r?,
}

Prevention

When it happens

Trigger: After a successful CONNECT through the proxy, performing the TLS handshake with the target host fails: target presents a certificate not signed by a trusted root (corporate MITM proxy re-signing TLS whose CA is not installed), hostname/certificate mismatch, proxy intercepting and resetting the connection, or the target speaking plain HTTP where wss/TLS was expected.

Common situations: Corporate TLS-inspection proxy (Zscaler, Netskope, Palo Alto) re-signing traffic with an internal CA not present in the native trust store; connecting to a host whose cert expired or covers a different name; a proxy that terminates TLS itself and rejects CONNECT tunneling; firewalls resetting long handshakes; pointing at a plain-HTTP endpoint on port 443.

Understand the failure class

Related errors


AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31). Data as JSON: /api/errors/0201dc6ae0a523a4. Report an issue: GitHub.