HTTP/2 Is a Browser Handshake, Not Just Faster HTTP
Intro
HTTP/2 is usually sold as compression and multiplexing. For a collector sitting on the other side of TLS, those are side effects. The useful part is that the client has to introduce itself before it can ask for a page.
That introduction is not a User-Agent string. It is a SETTINGS frame, a list of pseudo-headers in a fixed order, and a first HEADERS frame whose size is basically “how much default junk this engine ships.” Browsers disagree on all three. Libraries that pretend to be browsers often agree on the TLS hello and then fail the HTTP/2 hello.
Fraudnode records this after the TCP and TLS blocks. Same session, next layer. If HTTP/2 never arrives, there is nothing to interpret. The check does not invent a Chrome SETTINGS map and let the client through.
The preface is a dialect
After ALPN picks `h2`, the client sends a connection preface and a SETTINGS frame. SETTINGS is a list of integer knobs: how many concurrent streams, how large a frame may be, how aggressive the window is, whether push is allowed. The RFC allows many combinations. Real browsers pick a small set and stick to it for years.
That is why HTTP/2 fingerprinting works at all. You are not hashing the HTML. You are looking at which knobs a specific engine thought were reasonable on a cold connection. A script using a generic HTTP/2 library tends to ship the library defaults. Those defaults are public, stable, and rarely match Gecko, WebKit, or Chromium.
Akamai documented this years ago as a fingerprint of SETTINGS plus the PRIORITY tree. The names of the fields matter less than the idea: the first control frames are a dialect. Dialects cluster by engine, not by the marketing name in `user-agent`.
Header order is not fashion
HTTP/1.1 already leaked the client in the order of `Host`, `Connection`, and `User-Agent`. HTTP/2 makes the leak stricter. Pseudo-headers must come first, and the RFC only says they have to appear before regular headers. It does not say in which order `:method`, `:scheme`, `:authority`, and `:path` must stand.
Chromium-family clients tend to put `:method`, then `:authority`, then `:scheme`, then `:path`. Firefox often places `:path` before `:authority`. Safari is the one that likes `:scheme` early. None of that is required for the request to succeed. It is just how each stack builds the block.
Regular headers follow the same habit. Firefox still likes a trailing `te`. Safari sends a short list. Chromium sends a longer one, including Client Hints whose order even moves between versions. HPACK compression does not erase the order of the first request. The collector sees the decompressed names in the sequence the client emitted.
A proxy that “fixes” HTTP/2 for you will usually rebuild this block. The TLS session may still look like Chrome. The HEADERS frame looks like the proxy.
Frame length is a side channel, not a hash
The first HEADERS frame has a length. That length is a crude sum of how many headers went out and how they compressed. Compact engines produce a smaller first block. Chromium-family browsers send more hints and more default headers, so the first frame is fatter.
Treat the number as a cluster, not as a serial. It moves when a user has extra extensions, when Accept-Language is long, when a Client Hint set grows. It is still expensive to fake correctly together with SETTINGS and pseudo-header order. Faking one of the three is easy. Faking all three, plus the TLS ClientHello that came before, is how people accidentally build a franken-browser.
What this is not
It is not a replacement for TLS fingerprints. HTTP/2 starts after the handshake. A client can look like Chrome in ClientHello and like a Python library in SETTINGS.
It is not a moral judgment about HTTP/2. The protocol did not set out to identify browsers. Identification is what happens when implementations freeze their defaults.
It is not a single threshold. Publishing “Firefox is 277 bytes” as a detector is how you get broken the week Firefox ships one more header. The honest statement is: this session’s HTTP/2 dialect does or does not match the rest of the claim.
Put it next to TCP and TLS
TCP MSS told you about the path. TLS ClientHello told you about the crypto library. HTTP/2 tells you about the application stack that speaks after ALPN.
A clean browser is boring on all three. A masking layer is rarely boring on all three at once. That is the whole product, and it only works if the frames actually arrived.