You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
馃悰 QUIC Hijack() skips the status-written check that HTTP/2 enforces聽#1748
Hijack() has different preconditions on the two transports.
HTTP/2, http2RespWriter.Hijack in connection/http2.go, refuses when no status was written yet:
if!rp.statusWritten {
returnnil, nil, fmt.Errorf("status not yet written before attempting to hijack connection")
}
QUIC, httpResponseAdapter.Hijack in connection/quic_connection.go, has no such check. It always returns a localProxyConnection, even when connectResponseSent is still false, so the caller can write raw bytes to the stream before any connect response went out.
Same caller, different outcome depending on the transport: on HTTP/2 it gets an error, on QUIC it gets a conn.
To Reproduce
Stock cloudflared writes the status before it hijacks, so the built-in ingress path does not hit this. I hit it in a fork that replaces the OriginProxy, with code that hijacks first. net/http/httputil.ReverseProxy does exactly that on a 101: handleUpgradeResponse calls Hijack() and then writes the status line onto the conn itself.
ProxyHTTP serves the request through httputil.ReverseProxy to a backend that answers 101.
With --protocol http2, Hijack fails and the client gets a 502.
With --protocol quic, Hijack succeeds and the caller can write to the stream before any connect response was sent.
Expected behavior
Both transports enforce the same precondition. The HTTP/2 check looks like the right one, so QUIC could refuse the same way while connectResponseSent is false.
Environment and versions
OS: Linux
Architecture: AMD64
Version: 2026.9.1, and the Hijack bodies are unchanged on master as of 2026.9.3
Logs and errors
HTTP/2: status not yet written before attempting to hijack connection. QUIC: no error.
Additional context
I worked around it on my side, so it does not block me. Filing it because the two transports disagree on the ResponseWriter contract, and that is easy to miss.
Activity