Repository: go
Issue: net/http: consider supporting HTTP 2/3 never-indexed literals
Labels: NeedsInvestigation

HTTP/2 and HTTP/3 header encoding is vulnerable to a category of attacks which permit an attacker sharing a connection with a target to determine the value of certain headers sent by the target. See [RFC 7541 section 7.1](https://www.rfc-editor.org/rfc/rfc7541.html#section-7.1) and [RFC 9204 section 7.1](https://www.rfc-editor.org/rfc/rfc9204.html#section-7.1).

An implementation may defend against these attacks by not compressing sensitive headers.

When choosing not to compress a sensitive header, an implementation may indicate that the header has been intentionally left uncompressed, sending it as a "never-indexed literal". The recipient of a header sent as a never-indexed literal is supposed to avoid compressing that header if it forwards it.

The net/http and golang.org/x/net/http2 HTTP/2 implementation does not preserve the never-indexed state of received headers, and is therefore in violation of RFC 7541's mandate that:

> An intermediary MUST NOT re-encode a value that uses the never-
   indexed literal representation with another representation that would
   index it.  If HPACK is used for re-encoding, the never-indexed
   literal representation MUST be used.

One reason for this (probably the primary reason) is that the `net/http.Headers` type doesn't have any obvious place to store the never-indexed status of a header.

However, I'm not certain how much support there is in the world for never-indexed literals. So far as I can tell, Google QUICHE (https://github.com/google/quiche) has no support for preserving the never-indexed status of header fields. Like net/http, it represents headers as string key/value pairs with no additional per-field data.

Perhaps other implementations do make use of never-indexed literals. I haven't done any research here.

If we *do* want to support never-indexed literals, however, I believe we have a way to do so without any backwards-incompatible API changes. I propose that, in the event that we do want to support never-indexed literals:

Given an `http.Header` value `h`, the field `field` is never-indexed if `h[field][len(field)] == "\x00never-indexed"`. That is, if the `[]string` in `h[field]` contains an element one past its end (in the unused capacity section) with the value "\x00never-indexed".

The field decoder would arrange to set this value as appropriate, and the encoder would avoid indexing never-indexed values. In naive usage, I believe this would just work--most users can ignore the presence of the marker, but an `http.Headers` copied from an inbound request to an outbound one will preserve it. We might need to arrange for `httputil.ReverseProxy` to explicitly copy the value.

In the event that a header somehow ends up incorrectly marked as never-indexed (probably by reusing a slice with a never-indexed marker in it), the negative impact is limited to transmitting that header slightly less efficiently.

The use of a NUL byte in the marker avoids us ever mistaking a real field value for the marker.

I'm not particularly convinced this is a good idea, given that I don't see much evidence of use of never-indexed literals in the wild. But I think it's technically feasible if we want to do it.