Summary

  • RFC 9931 closes a dangerous evidentiary gap in HTTP/1.1: finishing an Upgrade or CONNECT request does not confirm that the server accepted the transition. A recent success can justify a forecast, but only the current response establishes the parser or tunnel state for the current connection.
  • When a trusted client releases untrusted third-party bytes before that response, rejection can make the server read new-protocol data as another HTTP/1.1 request. Operators need to preserve the confirmation boundary — what was held, which response arrived, which rule released bytes and which parser became authoritative — without logging the payload itself.

The fast path spends certainty before it receives it

Latency optimizations are often described as if they borrow time. An optimistic client sends data it expects the next protocol to consume while the request that would create that protocol is still awaiting an answer. If the prediction is right, one wait disappears. If it is wrong, the loss is not merely a slower retry. The same octets can arrive in a state where the receiver assigns them a different grammar and a different principal.

RFC 9931, published in March 2026 on the IETF Standards Track, isolates this problem for transitions made on an HTTP/1.1 connection. The document updates HTTP/1.1 and the UDP-over-HTTP specification. Its central distinction is easy to miss because two adjacent events look almost identical in a trace: the client has finished asking to change the connection, and the server has agreed to change it.

They are not the same event. HTTP Upgrade lets a client propose another application protocol. A 101 Switching Protocols response says the server understands and is willing to comply, and names the protocol that will apply after the response. CONNECT asks a proxy to form a tunnel. A successful 2xx response means tunnel mode begins after the response header section. Any other response means the tunnel has not been formed.

HTTP semantics already says that a client cannot start the upgraded protocol in the middle of its request. RFC 9931 makes the other half explicit: completing the request is necessary, but it is not sufficient. The server may lack the token, require authentication, reject a destination, fail to resolve it, decline the resource, or simply decide that the transition is not useful. Until the answer arrives, the client has an expectation, not a connection-state fact.

A memory is not a response

The temptation to erase that distinction is strongest when the prediction is usually good. The same token worked moments ago. The same proxy accepts almost every destination in the class. A cached capability probe is green. A vendor says the deployment supports the transition. A dashboard shows a negligible rejection rate.

All of those observations can be useful. They can inform capacity planning, select a rollout cohort and estimate whether the latency saving is worth pursuing. None is the response to this request. Conditions can change between connections: credentials expire, routing fails, destination policy changes, an intermediary is inserted, a service restarts, a resource moves or a request reaches a different backend. More fundamentally, acceptance belongs to the server’s present evaluation. Frequency cannot manufacture an affirmative response that has not yet occurred.

This is a governance boundary because several custodians meet at the same byte. The third-party application chooses the payload. The HTTP client decides when to release it. The proxy or server decides whether to change interpretation. A platform team may own the optimization policy; a security team may own request-smuggling controls; a vendor may publish a compatibility statement. If the trace records only “upgrade attempted” and “bytes sent,” later reviewers cannot tell whose decision allowed the data to cross or which grammar was in force.

The useful correction is not to call prediction worthless. It is to label it honestly. Historical success is evidence about likelihood. A response is evidence about present protocol state. The two may be highly correlated while remaining non-substitutable.

When the same bytes acquire two possible authors

In a two-party connection where each endpoint already expects arbitrary input from the other, an incorrect prediction can be a protocol error without introducing this particular security issue. RFC 9931 focuses on the harder arrangement: the HTTP client is trusted by the server, but the data source behind the client is not.

Suppose a local application asks an authenticated proxy client for a TCP tunnel. The application immediately supplies bytes intended for the remote TCP service. The client forwards them before the proxy has returned a successful CONNECT response. If the destination fails, the proxy rejects CONNECT and remains in HTTP/1.1 mode. The bytes that the application composed for a different protocol now sit where the proxy expects the next HTTP request.

An attacker can shape those bytes to resemble a valid request to the proxy itself. Because the connection may carry a client certificate or another connection-level identity, the proxy can attribute the injected request to the trusted client. The application has effectively spoken with authority it was never given. RFC 9112 describes the wider request-smuggling class as exploiting differences in parsing among recipients to conceal a request that policy might otherwise block.

The failure therefore has three layers. There is a prediction error: the transition did not happen. There is a framing error: sender and receiver disagree about which protocol parses the bytes. And there is an authority error: data chosen by one party is treated as a request from another. Counting only failed upgrades sees the first layer and misses the consequence.

Parser exploitation can arise even when the byte sequence is not a complete smuggled request. Software written on the assumption that request material came from a trusted HTTP client can encounter attacker-controlled input. The point is not that every rejected transition becomes an exploit. It is that early release removes the boundary that allowed the receiver to know whose grammar and whose authority applied.

The protocol family does not have one rule for optimism

RFC 9931 does not prohibit every attempt to save a round trip. It distinguishes mechanisms by the ambiguity they create.

WebSocket already takes the conservative route. After sending its opening handshake, the client must wait for the server’s response before sending more data. A fixed wait rule prevents a WebSocket frame from being offered to an HTTP/1.1 parser after rejection. WebSocket also masks client-to-server frames, a separate design property that makes them harder to repurpose as HTTP requests.

The earlier UDP-over-HTTP text allowed a client to send proxied UDP packets before receiving the proxying response. RFC 9931 narrows that permission: optimism is allowed with HTTP/2 or later, but not HTTP/1.x. The IP-over-HTTP specification follows the same boundary. It permits early packets on HTTP/2 or HTTP/3 and forbids them on HTTP/1.x; an intermediary that converts a newer HTTP version to HTTP/1.1 must hold capsules until it has parsed a successful proxying response.

The distinction is structural. HTTP/1.1 requests on a connection are sequential and their separation is implicit. HTTP/2 and HTTP/3 identify requests with explicit streams. That avoids this particular case in which bytes intended for a rejected transition fall through into the next HTTP/1.1 request. It does not prove that every higher-version deployment is secure, that every tunnel destination is authorized, or that tunneled payloads are safe. It closes one ambiguity, not every risk.

For future Upgrade tokens, RFC 9931 points to several design options: forbid optimistic use, begin the new protocol with a fixed preamble that deliberately terminates HTTP/1.1 processing, or use high-entropy masking. Where the upgraded protocol does not preserve the request method’s semantics, using GET without content also reduces the material exposed to competing interpretations.

IANA’s registry contributes a different fact. It records token names and their references. Registration helps participants interpret a name consistently. It does not announce that a particular endpoint supports the token now, or that a particular request will be accepted. Registry existence is specification provenance, not live transition confirmation.

CONNECT now has a two-sided containment rule

For an HTTP/1.1 proxy client acting for an untrusted TCP client, RFC 9931 requires at least one of two behaviors. It can wait for a successful 2xx response before forwarding TCP payload. Or it can include Connection: close, ensuring that rejection cannot leave the same connection available to parse the premature material as another request. A client can do both.

The update also gives the rejecting proxy server a containment duty: close the underlying connection without processing further requests. This protects against clients that release data too early. It can cost performance, especially when a 407 authentication challenge could otherwise be followed by another request on the same connection.

That cost creates an exception pressure. The RFC allows a server to disable the close-on-rejection mitigation when the client is known to wait for 2xx, and notes that User-Agent plus vendor documentation can identify compliant clients. This is a pragmatic operational allowance, not an authenticated attestation protocol. RFC 9110 defines User-Agent as information about the originating software and discourages masquerading. A durable exception decision should therefore retain more than a bare product token: the product and version scope, the vendor claim reviewed, the local validation, the owner, the expiry condition and the rollback rule.

The point is not to make the exception impossible. It is to stop a performance choice from becoming permanent folklore. “This client is safe” must remain a dated proposition whose evidence can expire when versions, intermediaries or configurations change.

A transition-confirmation receipt

Wire protocols already carry the response that resolves the transition. The missing object is often an operator record that connects that response to the release of third-party bytes. I propose a compact transition-confirmation receipt for paths where the distinction has security consequences.

The receipt begins with the connection epoch and HTTP version. It identifies whether the mechanism was Upgrade or CONNECT, the requested token or tunnel type, and the target in a protected or hashed form where necessary. It classifies the source of post-transition data — first-party control, bounded subsystem or untrusted third party — without copying the data.

Next it records the gate. Which implementation and policy version decided when bytes could move? Did it wait for 101 or 2xx, send Connection: close, rely on a fixed preamble, use masking, or operate on an explicitly separated HTTP/2 or HTTP/3 stream? Were any bytes forwarded before the current response, and if so, under which allowed design rather than which optimistic hope?

Then it binds the result: response class, selected protocol where applicable, final parser or tunnel state, connection disposition after rejection and any containment action. An exception block names the evidence, scope, approver, expiry and invalidation triggers. A software update, policy change, new intermediary, identity change or failed canary should open a new decision rather than silently inheriting the old one.

The record should not contain tunnel payloads, credentials, client certificates, raw application data or secrets. A small digest and structured state are enough to answer the governance question: which current event permitted these bytes to cross from one interpreter to another?

This receipt is not part of RFC 9931. It is an operational control that consumes the RFC’s boundary without asking the protocol to become a corporate approval system. It also stops the server response from being overstated. A 2xx CONNECT response confirms tunnel formation; it does not authorize every application action inside the tunnel. The next layer still owns its own identity and policy decisions.

What this analysis does not establish

The frozen sources describe protocol semantics and conditional risks. They do not show that a named product is vulnerable, that operators commonly forward payload early, that a particular exploit occurred or that one mitigation fits every architecture. A two-party transition may not expose the third-party authority problem at all. A newer HTTP version avoids this framing ambiguity but can have unrelated failures.

Nor should the article turn a standards update into a claim that latency no longer matters. A forced close can slow authentication and reconnection. Buffering can add delay or memory pressure. The legitimate engineering task is to choose an optimization whose failure grammar remains safe, then preserve the evidence that the chosen boundary is actually in force.

The strongest conclusion is narrower. A previous success may improve a forecast. Only the current response, interpreted under the current HTTP version and connection state, confirms the transition that decides how the next bytes will be read.

Sources

  1. RFC 9931: Security Considerations for Optimistic Protocol Transitions in HTTP/1.1
  2. RFC Editor information page for RFC 9931
  3. RFC 9110: HTTP Semantics
  4. RFC 9112: HTTP/1.1
  5. RFC 9298: Proxying UDP in HTTP
  6. RFC 6455: The WebSocket Protocol
  7. RFC 9484: Proxying IP in HTTP
  8. RFC 9113: HTTP/2
  9. RFC 9114: HTTP/3
  10. IANA HTTP Upgrade Token Registry
  11. Heng Lu, “The Policy Mirror”
  12. Heng Lu, “Minimum Initial Specification, Localized Future Decision, Voluntary Adoption”
  13. Heng Lu, “On Why BTW Media Exists, and Why Reality, Not Advocacy, Is the Product”