Exhibit · WebKit and Network.framework
Two servers on this host present the same advertised values, as far as they have been compared, and Safari on iOS 27.0 beta establishes a session against one while refusing the other. Chrome and Firefox accept both. This page is a live reproduction and the wire evidence, published so that anyone with better visibility into WebKit can identify what is missing from it.
Observed on iOS 27.0 beta only, six attempts for six, on two devices across two networks. Not confirmed against a release build, and not yet reproduced by anyone outside this project.
Run this on an iPhone or iPad to reproduce. On a desktop browser both go green, which is the control.
QUIC and TLS complete. SETTINGS are exchanged in both directions. The client opens the extended CONNECT stream, writes zero bytes, and abandons it.
// server qlog, refusal case transport:packet_received handshake complete http:frame_parsed SETTINGS (nine identifiers, both directions) http:stream_opened stream 0, extended CONNECT transport:frame_received stop_sending(stream 0, error 0x10C) // H3_REQUEST_CANCELLED transport:frame_received reset_stream(stream 0, final_size 0) transport:connection_closed 0x100 // H3_NO_ERROR, empty reason
final_size = 0 is the tell: the CONNECT stream was created and nothing was
ever written to it. The decision was taken on the SETTINGS exchange.
Client-side the same moment is a WebTransportError with an empty message,
roughly 50 ms after construction.
| :4440 | :4436 | |
|---|---|---|
| h3 SETTINGS | Nine identifiers, matching on every value compared | |
| QUIC transport parameters | Matching on every value compared | |
| Certificate | The same Let's Encrypt chain | |
| Echo handler | The same code | |
| Server build | webtransport-go v0.12.0 · quic-go v0.61.0 | webtransport-go v0.11.0 · quic-go v0.60.0 |
| iOS 27.0 beta | refused | binds |
| Chrome, Firefox | binds | binds |
If that comparison is complete, then what Safari reacts to is not an advertised value at all, which would put it in frame encoding, capsule handling or sequencing. That inference is worth exactly as much as the comparison behind it, and the comparison was made by reading decoded values rather than by diffing raw frames. The captures are published below so the inference can be checked rather than taken on trust.
Where this is weakest. Two dependencies move between those builds, webtransport-go and quic-go together, so this is not a one-variable comparison. Pinning webtransport-go v0.11.0 against quic-go v0.61.0 as a fifth endpoint would separate them. Until that endpoint exists, neither library can be singled out, and an interaction between the two cannot be ruled out.
iOS 27 also enforces the draft-13 conditional that WT_MAX_SESSIONS > 1
obliges a server to send the three WT_INITIAL_MAX_* settings. A server
advertising the former without the latter is refused before CONNECT.
This is defensible behavior and no other engine enforces it. Worth confirming that the enforcement is intended, since it makes several widely deployed server defaults unreachable from Safari with no diagnostic.
Invalid settings, TLS rejection and the wire-level refusal above all surface to web
developers as the same WebTransportError with an empty message at the same
latency. There is no client-side way to tell them apart, so the failures are only
diagnosable by whoever controls the server.
Related: the server-library side of the same pair · what this means if you are building on WebTransport