Wait for HTTP/1.1 protocol transitions before sending untrusted data
HTTP/1.1 clients must treat a requested protocol transition as unconfirmed until the server accepts it, especially when forwarding untrusted data.
- standards updates
- HTTP/1.1
- protocol transitions
- connect
- request smuggling
- proxies
A transition request can be rejected
HTTP/1.1 can change how the rest of a connection is interpreted in two common ways:
- An
Upgraderequest is accepted with101 Switching Protocols. - A
CONNECTrequest is accepted with a successful2xxresponse, after which the connection becomes a tunnel.
Sending either request does not confirm the transition. A server can ignore an upgrade, require authentication, redirect, reject a destination, or fail to establish a tunnel. After rejection, subsequent bytes can still be parsed as HTTP/1.1.
This boundary is easy to miss when an implementation tries to reduce latency by sending bytes from the hoped-for next protocol before it receives the server’s decision.
Optimistic bytes can look like another request
Consider a proxy client that sends CONNECT on behalf of an untrusted local
application. If it forwards the application’s TCP bytes immediately, those bytes
reach the proxy server before the tunnel is accepted:
CONNECT target.example.invalid:443 HTTP/1.1
Host: target.example.invalid:443
POST /internal-action HTTP/1.1
Host: proxy.example.invalid
Content-Length: 0
Expected result: if the proxy rejects CONNECT, the second block can be
interpreted as a new HTTP/1.1 request on the authenticated proxy connection
instead of tunnel payload. This is a defensive illustration of the risk created
when a client forwards untrusted payload before handling the transition result.
The risk is strongest when the HTTP client is trusted by the proxy but the post-transition data source is not. Connection-level credentials, including a client certificate, can cause the smuggled request to receive the trusted client’s authority.
Parser vulnerabilities are another possibility. Bytes designed for the new protocol can unexpectedly become attacker-controlled input to an HTTP/1.1 request parser that was not designed for that context.
Apply the 2026 CONNECT requirements
RFC 9931 was published in March 2026 and updates HTTP/1.1. Its CONNECT rules are specifically scoped to proxy clients that forward data for untrusted TCP clients. Such a proxy client must do at least one of these:
- Wait for a successful
2xxresponse before forwarding any TCP payload data. - Send
Connection: closeon the CONNECT request.
Waiting for 2xx is the clearest normal design. It preserves the boundary:
CONNECT target.example.invalid:443 HTTP/1.1
Host: target.example.invalid:443
HTTP/1.1 200 Connection Established
<tunnel bytes begin here>
Expected result: the client starts forwarding tunnel bytes only after the successful response establishes that both sides have left HTTP/1.1 request processing for this connection.
As a mitigation for clients that might not comply, an HTTP/1.1 proxy server that rejects CONNECT must close the underlying connection without processing further requests on it. The server can omit that mitigation only when it knows the client waits for a successful response before forwarding untrusted payload.
That rejection rule is deliberately stronger than ordinary HTTP/1.1 persistence. It prevents already-sent payload bytes from becoming a second request.
Do not generalize the close rule to every rejected upgrade
RFC 9931 does not say that every rejected protocol transition must close the connection. In general, a rejected Upgrade can leave the connection available for normal HTTP/1.1 traffic, and closing it can impose unnecessary latency.
The mandatory close mitigation applies to an HTTP/1.1 proxy server rejecting CONNECT because vulnerable clients may already have forwarded attacker-controlled payload. Review the exact transition mechanism, token, data source, and requirements instead of applying one connection rule everywhere.
Existing protocols take different approaches
WebSocket already requires the client to wait for the server’s opening-handshake response before sending further data. Client-to-server WebSocket frames also use masking, which makes them less likely to be interpreted as a chosen HTTP request.
For connect-udp, RFC 9931 updates the earlier rule: a client must not send UDP
packets optimistically over HTTP/1.x. Optimistic sending remains permitted by
that specification over HTTP/2 or later.
HTTP/2 and HTTP/3 identify exchanges with explicit stream IDs. A rejected extended CONNECT or other stream-level operation does not turn following bytes into another HTTP request on the same sequential byte stream in the HTTP/1.1 way.
The TLS upgrade token has a different property: the first TLS handshake byte cannot begin a valid HTTP/1.1 method, so a compliant server encounters a parsing error rather than a valid attacker-chosen next request. That does not make optimistic transitions a generally safe implementation pattern.
Audit every source of early data
The vulnerable component might be called a proxy client, tunnel library, agent, VPN, gateway, sidecar, or protocol adapter. Look for the behavior, not the product label:
- It sends an HTTP/1.1 Upgrade or CONNECT request.
- It receives bytes from a less-trusted process, tenant, origin, or user.
- It forwards those bytes before the server confirms the transition.
- The HTTP connection carries stronger identity or network access than the untrusted source has directly.
Packet captures should show a clean response boundary before untrusted payload.
Test rejection paths, not only successful tunnels: invalid ports, denied
destinations, DNS failure, upstream refusal, 401, 407, redirects, and timeout
responses.
On the server side, verify that a rejected HTTP/1.1 CONNECT closes the connection and that no pipelined-looking bytes are processed. On the client side, verify that payload buffering is bounded while awaiting the decision and is discarded safely after rejection.
Design future transition protocols defensively
RFC 9931 identifies several protective patterns for future Upgrade tokens:
- Forbid optimistic data until the transition is confirmed.
- Use a fixed preamble that cannot be interpreted as continuing HTTP/1.1.
- Mask client-to-server data with sufficient entropy.
- Restrict an Upgrade to
GETwithout content when the method has no role in the new protocol.
The durable rule is simple: the old protocol remains authoritative until both peers have reached the specified acceptance point. Latency optimization must not let untrusted data cross that boundary early.
