PQ Crypta Logo

Where HTTP/3 Clients Fail

85 failures across 22 tests, grouped by what was being tested rather than by who failed it. Counted against the implementations that reached a verdict, not against the whole fleet.

PQ CRYPTA PLATFORM

🏠 Main

📰 News

👤 Account

⟨ QUANTUM ERROR PORTAL ⟩

Navigate the Error Dimensions

Jump to an anomaly59

Every one of these is a page: what the server emits, the clause it is judged against, and how each client answered. The full list carries the verdict tallies too.

Grouped by test, not by client, because that is what makes the interesting case visible: a clause several independent implementations get wrong the same way is not several bugs. Four teams reading one sentence and arriving at one wrong answer is evidence about the sentence.

Every entry below is a MUST or a SHOULD the client did not meet, with the clause it was judged against. Discretionary tests cannot appear here: where the RFC permits either behaviour, the report says which was chosen and grades nothing. Nor can inconclusive runs — a test the client was never actually put through is not a failure of the client.

These are specification violations, not vulnerabilities. Some have security relevance and most are simply behaviour a document prohibits; calling every one a vulnerability is how a conformance suite loses the right to be believed about the ones that are.

PUSH_PROMISE for a push the client never allowed

h-push-promise-unsolicited · RFC 9114 §7.2.5, §4.6 · correctness · MUST · 9 of 12 implementations that reached a verdict

9 independent implementations fail this one. Worth reading as a question about the clause before reading it as a question about the clients.

Close the connection with H3_ID_ERROR. §7.2.7 leaves the maximum push ID unset until the client sends MAX_PUSH_ID, so a server "cannot push until it receives a MAX_PUSH_ID frame" and every push ID is larger than the client has advertised. WHERE the frame goes is the whole test. It was written to the control stream, where §7.2.5 gives a different and more specific answer -- a PUSH_PROMISE there is H3_FRAME_UNEXPECTED whatever its push ID -- so the port asked one question and this entry graded the other. Seven independent implementations answered 0x105 correctly and were failed for it, until 2026-09-20. It now arrives on the response stream, where the frame is legal and its push ID is the only thing left to object to. §7.2.5 is explicit about the answer: a client "MUST treat receipt of a PUSH_PROMISE frame that contains a larger push ID than the client has advertised as a connection error of H3_ID_ERROR".

ImplementationVersionWhat it did
curl libcurl/8.23.0 ngtcp2/1.25.0 Fail Objected at the QUIC layer (rejected at the QUIC layer with INTERNAL_ERROR) instead of signalling the HTTP/3 error. The violation was detected and rejected, which is the right instinct; §8.1 requires an HTTP/3 connection error to be carried in an application close, and this test names 0x108. A transport close carries no HTTP/3 code at all, so a peer has no way to learn what was wrong.
aioquic 1.3.0 (PyPI wheel) Fail Accepted a protocol violation and carried on. The anomaly was in the response the client read and delivered, so it was seen; this should have been rejected.
chromium build 1248 Fail Rejected, but with error 0x106 where the specification requires 0x108. The violation was detected; the code reported is wrong.
quic-go quic-go v0.61.0 (h1:9So2anK4Tp22) Fail Accepted a protocol violation and carried on. The anomaly was in the response the client read and delivered, so it was seen; this should have been rejected.
quiche 0.30.0 (1c8e976) Fail Accepted a protocol violation and carried on. The anomaly was in the response the client read and delivered, so it was seen; this should have been rejected.
picoquic 8162550 Fail Accepted a protocol violation and carried on. The anomaly was in the response the client read and delivered, so it was seen; this should have been rejected.
ngtcp2 3c23148 Fail Rejected, but with error 0x105 where the specification requires 0x108. The violation was detected; the code reported is wrong.
xquic 2060a44 Fail Accepted a protocol violation and carried on. The anomaly was in the response the client read and delivered, so it was seen; this should have been rejected.
msquic 2.7.0 (7e6525d) Fail Accepted a protocol violation and carried on. The anomaly was in the response the client read and delivered, so it was seen; this should have been rejected.

CANCEL_PUSH for a push ID that was never promised

h-cancel-push-unsolicited · RFC 9114 §7.2.3 · correctness · MUST · 8 of 12 implementations that reached a verdict

8 independent implementations fail this one. Worth reading as a question about the clause before reading it as a question about the clients.

Close the connection with H3_ID_ERROR. No MAX_PUSH_ID was granted, so every push ID is greater than currently allowed, and §7.2.3 requires a CANCEL_PUSH referencing one to be treated as a connection error of that type.

ImplementationVersionWhat it did
curl libcurl/8.23.0 ngtcp2/1.25.0 Fail Objected at the QUIC layer (rejected at the QUIC layer with INTERNAL_ERROR) instead of signalling the HTTP/3 error. The violation was detected and rejected, which is the right instinct; §8.1 requires an HTTP/3 connection error to be carried in an application close, and this test names 0x108. A transport close carries no HTTP/3 code at all, so a peer has no way to learn what was wrong.
aioquic 1.3.0 (PyPI wheel) Fail Rejected, but with error 0x105 where the specification requires 0x108. The violation was detected; the code reported is wrong.
chromium build 1248 Fail Rejected, but with error 0x106 where the specification requires 0x108. The violation was detected; the code reported is wrong.
quic-go quic-go v0.61.0 (h1:9So2anK4Tp22) Fail Rejected, but with error 0x105 where the specification requires 0x108. The violation was detected; the code reported is wrong.
quiche 0.30.0 (1c8e976) Fail Accepted a protocol violation and carried on. The anomaly was on the control stream, and this client extended flow-control credit on it -- a receiver extends credit on a stream only once its application has consumed what was already there, so the bytes were read rather than merely delivered. This should have been rejected.
picoquic 8162550 Fail Accepted a protocol violation and carried on. The anomaly was on the control stream, and this client extended flow-control credit on it -- a receiver extends credit on a stream only once its application has consumed what was already there, so the bytes were read rather than merely delivered. This should have been rejected.
ngtcp2 3c23148 Fail Rejected, but with error 0x105 where the specification requires 0x108. The violation was detected; the code reported is wrong.
msquic 2.7.0 (7e6525d) Fail Accepted a protocol violation and carried on. The anomaly was on the control stream, and this client extended flow-control credit on it -- a receiver extends credit on a stream only once its application has consumed what was already there, so the bytes were read rather than merely delivered. This should have been rejected.

Push stream opened for a push nobody allowed

h-push-stream-unpromised · RFC 9114 §6.2.2 · correctness · MUST · 7 of 11 implementations that reached a verdict

7 independent implementations fail this one. Worth reading as a question about the clause before reading it as a question about the clients.

1 of the fleet reached no verdict here, and which clients those are changes between runs: a client that rejects at the QUIC layer carries no HTTP/3 error code for this to read, and one whose close arrives without the code is indistinguishable from one that never objected. The failures are stable; the size of the set they are counted against is not.

Close the connection with H3_ID_ERROR. §6.2.2 does not require the push to have been promised first — the stream alone is enough: a client "MUST treat receipt of a push stream as a connection error of type H3_ID_ERROR when no MAX_PUSH_ID frame has been sent", and no client under test sends one. Distinct from the PUSH_PROMISE test, which puts the same violation in a frame on the control stream. This one opens the push stream itself, which a client has to recognise by its stream type before any frame inside it is read.

ImplementationVersionWhat it did
curl libcurl/8.23.0 ngtcp2/1.25.0 Fail Objected at the QUIC layer (rejected at the QUIC layer with INTERNAL_ERROR) instead of signalling the HTTP/3 error. The violation was detected and rejected, which is the right instinct; §8.1 requires an HTTP/3 connection error to be carried in an application close, and this test names 0x108. A transport close carries no HTTP/3 code at all, so a peer has no way to learn what was wrong.
aioquic 1.3.0 (PyPI wheel) Fail Accepted a protocol violation and carried on. The anomaly was on a server-initiated push stream, and this client extended flow-control credit on it -- a receiver extends credit on a stream only once its application has consumed what was already there, so the bytes were read rather than merely delivered. This should have been rejected.
chromium build 1248 Fail Rejected, but with error 0x101 where the specification requires 0x108. The violation was detected; the code reported is wrong.
quiche 0.30.0 (1c8e976) Fail Rejected, but with error 0x103 where the specification requires 0x108. The violation was detected; the code reported is wrong.
picoquic 8162550 Fail Accepted a protocol violation and carried on. The anomaly was on a server-initiated push stream, and this client extended flow-control credit on it -- a receiver extends credit on a stream only once its application has consumed what was already there, so the bytes were read rather than merely delivered. This should have been rejected.
ngtcp2 3c23148 Fail Rejected, but with error 0x103 where the specification requires 0x108. The violation was detected; the code reported is wrong.
msquic 2.7.0 (7e6525d) Fail Accepted a protocol violation and carried on. The anomaly was on a server-initiated push stream, and this client extended flow-control credit on it -- a receiver extends credit on a stream only once its application has consumed what was already there, so the bytes were read rather than merely delivered. This should have been rejected.

Transport parameter carrying a value the specification forbids

q-invalid-transport-param · RFC 9000 §7.4, §18.2 · correctness · MUST · 6 of 12 implementations that reached a verdict

6 independent implementations fail this one. Worth reading as a question about the clause before reading it as a question about the clients.

Close the connection with TRANSPORT_PARAMETER_ERROR. §18.2 makes an ack_delay_exponent above 20 invalid, and §7.4 is a MUST: "An endpoint MUST treat receipt of a transport parameter with an invalid value as a connection error of type TRANSPORT_PARAMETER_ERROR." Distinct from a *duplicate* parameter, which the same clause makes only a SHOULD — this port sends one parameter, once, with a value outside its permitted range. The parameter travels in the handshake, so every client that connects here reads it. Only a client that answers with a CONNECTION_CLOSE this endpoint can read is scored: one that simply abandons the handshake is recorded as inconclusive, because a close that was sent and lost looks exactly like one that was never sent.

ImplementationVersionWhat it did
curl libcurl/8.23.0 ngtcp2/1.25.0 Fail abandoned the handshake without a CONNECTION_CLOSE. It saw the parameter -- that travels in the handshake -- and §7.4 requires treating it as a connection error of type TRANSPORT_PARAMETER_ERROR, which RFC 9000 §10.2 signals in a CONNECTION_CLOSE. None was sent: this client runs on the endpoint's own host, where nothing is lost in transit.
chromium build 1248 Fail rejected the out-of-range parameter with PROTOCOL_VIOLATION, where RFC 9000 §7.4 requires TRANSPORT_PARAMETER_ERROR. The violation was detected; the code reported is wrong.
quic-go quic-go v0.61.0 (h1:9So2anK4Tp22) Fail rejected the out-of-range parameter with INTERNAL_ERROR, where RFC 9000 §7.4 requires TRANSPORT_PARAMETER_ERROR. The violation was detected; the code reported is wrong.
neqo 0.31.1 (cac2b74) Fail rejected the out-of-range parameter with Code::crypto(2f), where RFC 9000 §7.4 requires TRANSPORT_PARAMETER_ERROR. The violation was detected; the code reported is wrong.
lsquic 4.9.4 (2b30189) Fail abandoned the handshake without a CONNECTION_CLOSE. It saw the parameter -- that travels in the handshake -- and §7.4 requires treating it as a connection error of type TRANSPORT_PARAMETER_ERROR, which RFC 9000 §10.2 signals in a CONNECTION_CLOSE. None was sent: this client runs on the endpoint's own host, where nothing is lost in transit.
msquic 2.7.0 (7e6525d) Fail rejected the out-of-range parameter with Code::crypto(00), where RFC 9000 §7.4 requires TRANSPORT_PARAMETER_ERROR. The violation was detected; the code reported is wrong.

SETTINGS_H3_DATAGRAM with a value that is neither 0 nor 1

h-datagram-setting-invalid · RFC 9297 §2.1.1 · correctness · MUST · 5 of 11 implementations that reached a verdict

5 independent implementations fail this one. Worth reading as a question about the clause before reading it as a question about the clients.

1 of the fleet reached no verdict here, and which clients those are changes between runs: a client that rejects at the QUIC layer carries no HTTP/3 error code for this to read, and one whose close arrives without the code is indistinguishable from one that never objected. The failures are stable; the size of the set they are counted against is not.

Close the connection with H3_SETTINGS_ERROR. Unusually for a setting, RFC 9297 pins down the invalid-value case rather than leaving it open: the value "MUST be either 0 or 1", and if one "is received with a value that is neither 0 nor 1, the receiver MUST terminate the connection with error H3_SETTINGS_ERROR". This is the setting that gates HTTP Datagrams and so WebTransport, which means a client that implements either has real parsing behind it rather than an ignored identifier.

ImplementationVersionWhat it did
curl libcurl/8.23.0 ngtcp2/1.25.0 Fail Objected at the QUIC layer (rejected at the QUIC layer with INTERNAL_ERROR) instead of signalling the HTTP/3 error. The violation was detected and rejected, which is the right instinct; §8.1 requires an HTTP/3 connection error to be carried in an application close, and this test names 0x109. A transport close carries no HTTP/3 code at all, so a peer has no way to learn what was wrong.
quic-go quic-go v0.61.0 (h1:9So2anK4Tp22) Fail Rejected, but with error 0x106 where the specification requires 0x109. The violation was detected; the code reported is wrong.
picoquic 8162550 Fail Accepted a protocol violation and carried on. The anomaly was on the control stream, and this client extended flow-control credit on it -- a receiver extends credit on a stream only once its application has consumed what was already there, so the bytes were read rather than merely delivered. This should have been rejected.
lsquic 4.9.4 (2b30189) Fail Accepted a protocol violation and carried on. The anomaly was on the control stream, and this client extended flow-control credit on it -- a receiver extends credit on a stream only once its application has consumed what was already there, so the bytes were read rather than merely delivered. This should have been rejected.
msquic 2.7.0 (7e6525d) Fail Accepted a protocol violation and carried on. The anomaly was on the control stream, and this client extended flow-control credit on it -- a receiver extends credit on a stream only once its application has consumed what was already there, so the bytes were read rather than merely delivered. This should have been rejected.

Encoder stream setting a dynamic table capacity above the client's limit

h-qpack-encoder-overflow · RFC 9204 §4.3.1, §6 · correctness · MUST · 5 of 11 implementations that reached a verdict

5 independent implementations fail this one. Worth reading as a question about the clause before reading it as a question about the clients.

1 of the fleet reached no verdict here, and which clients those are changes between runs: a client that rejects at the QUIC layer carries no HTTP/3 error code for this to read, and one whose close arrives without the code is indistinguishable from one that never objected. The failures are stable; the size of the set they are counted against is not.

Close the connection with QPACK_ENCODER_STREAM_ERROR (0x201). §4.3.1 says the new capacity "MUST be lower than or equal to the limit" the decoder advertised, and that a decoder "MUST treat a new dynamic table capacity value that exceeds this limit as a connection error of type QPACK_ENCODER_STREAM_ERROR". Unlike the other two dynamic-table tests, this one runs against every client, because the capacity asked for is computed from the one that client advertised: one byte more, whatever it said. It was a fixed 4096 until 2026-09-20, which exceeded the zero every client then advertised and would have exceeded nothing at all for a client that granted a 4096-byte table.

ImplementationVersionWhat it did
curl libcurl/8.23.0 ngtcp2/1.25.0 Fail Objected at the QUIC layer (rejected at the QUIC layer with INTERNAL_ERROR) instead of signalling the HTTP/3 error. The violation was detected and rejected, which is the right instinct; §8.1 requires an HTTP/3 connection error to be carried in an application close, and this test names 0x201. A transport close carries no HTTP/3 code at all, so a peer has no way to learn what was wrong.
quiche 0.30.0 (1c8e976) Fail Accepted a protocol violation and carried on. The anomaly was on the QPACK encoder stream, and this client extended flow-control credit on it -- a receiver extends credit on a stream only once its application has consumed what was already there, so the bytes were read rather than merely delivered. This should have been rejected.
picoquic 8162550 Fail Accepted a protocol violation and carried on. The anomaly was on the QPACK encoder stream, and this client extended flow-control credit on it -- a receiver extends credit on a stream only once its application has consumed what was already there, so the bytes were read rather than merely delivered. This should have been rejected.
xquic 2060a44 Fail Rejected, but with error 0x106 where the specification requires 0x201. The violation was detected; the code reported is wrong.
msquic 2.7.0 (7e6525d) Fail Accepted a protocol violation and carried on. The anomaly was on the QPACK encoder stream, and this client extended flow-control credit on it -- a receiver extends credit on a stream only once its application has consumed what was already there, so the bytes were read rather than merely delivered. This should have been rejected.

A second GOAWAY naming a larger identifier than the first

h-goaway-increasing · RFC 9114 §5.2 · correctness · MUST · 5 of 12 implementations that reached a verdict

5 independent implementations fail this one. Worth reading as a question about the clause before reading it as a question about the clients.

Close the connection with H3_ID_ERROR. §5.2 permits multiple GOAWAY frames but requires the identifier in each to be no greater than any previously sent, because the identifier is a promise about what will still be processed and raising it takes that promise back. "Receiving a GOAWAY containing a larger identifier than previously received MUST be treated as a connection error of type H3_ID_ERROR."

ImplementationVersionWhat it did
curl libcurl/8.23.0 ngtcp2/1.25.0 Fail Objected at the QUIC layer (rejected at the QUIC layer with INTERNAL_ERROR) instead of signalling the HTTP/3 error. The violation was detected and rejected, which is the right instinct; §8.1 requires an HTTP/3 connection error to be carried in an application close, and this test names 0x108. A transport close carries no HTTP/3 code at all, so a peer has no way to learn what was wrong.
aioquic 1.3.0 (PyPI wheel) Fail Rejected, but with error 0x105 where the specification requires 0x108. The violation was detected; the code reported is wrong.
neqo 0.31.1 (cac2b74) Fail Rejected, but with error 0x101 where the specification requires 0x108. The violation was detected; the code reported is wrong.
picoquic 8162550 Fail Accepted a protocol violation and carried on. The anomaly was on the control stream, and this client extended flow-control credit on it -- a receiver extends credit on a stream only once its application has consumed what was already there, so the bytes were read rather than merely delivered. This should have been rejected.
msquic 2.7.0 (7e6525d) Fail Accepted a protocol violation and carried on. The anomaly was on the control stream, and this client extended flow-control credit on it -- a receiver extends credit on a stream only once its application has consumed what was already there, so the bytes were read rather than merely delivered. This should have been rejected.

Field line indexing a static table entry that does not exist

h-qpack-static-index-invalid · RFC 9204 §3.1, §4.5.2 · correctness · MUST · 5 of 12 implementations that reached a verdict

5 independent implementations fail this one. Worth reading as a question about the clause before reading it as a question about the clients.

Close the connection with QPACK_DECOMPRESSION_FAILED (0x200). The static table has 99 entries, so index 200 refers to nothing, and §3.1 is explicit: "When the decoder encounters an invalid static table index in a field line representation, it MUST treat this as a connection error of type QPACK_DECOMPRESSION_FAILED." The static table needs no permission and never changes size, so unlike the dynamic-table tests this one applies to every client.

ImplementationVersionWhat it did
curl libcurl/8.23.0 ngtcp2/1.25.0 Fail Objected at the QUIC layer (rejected at the QUIC layer with INTERNAL_ERROR) instead of signalling the HTTP/3 error. The violation was detected and rejected, which is the right instinct; §8.1 requires an HTTP/3 connection error to be carried in an application close, and this test names 0x200. A transport close carries no HTTP/3 code at all, so a peer has no way to learn what was wrong.
quic-go quic-go v0.61.0 (h1:9So2anK4Tp22) Fail rejected the field section with QPACK_DECOMPRESSION_FAILED, but as a stream error: it stopped the request stream and kept the connection open. The code is right and the scope is not -- RFC 9204 makes every decompression failure a connection error.
picoquic 8162550 Fail Rejected, but with error 0x106 where the specification requires 0x200. The violation was detected; the code reported is wrong.
xquic 2060a44 Fail Rejected, but with error 0x106 where the specification requires 0x200. The violation was detected; the code reported is wrong.
msquic 2.7.0 (7e6525d) Fail Rejected, but with error 0x101 where the specification requires 0x200. The violation was detected; the code reported is wrong.

Encoder instruction naming a static index that does not exist

h-qpack-encoder-bad-name-index · RFC 9204 §3.1, §4.3.2 · correctness · MUST · 5 of 11 implementations that reached a verdict

5 independent implementations fail this one. Worth reading as a question about the clause before reading it as a question about the clients.

1 of the fleet reached no verdict here, and which clients those are changes between runs: a client that rejects at the QUIC layer carries no HTTP/3 error code for this to read, and one whose close arrives without the code is indistinguishable from one that never objected. The failures are stable; the size of the set they are counted against is not.

Close the connection with QPACK_ENCODER_STREAM_ERROR (0x201). The same bad index means different things depending on where it arrives, and §3.1 says both: on a field line it is QPACK_DECOMPRESSION_FAILED, and "if this index is received on the encoder stream, this MUST be treated as a connection error of type QPACK_ENCODER_STREAM_ERROR". Paired deliberately with the field-line version on the neighbouring port: a client that answers both with the same code has collapsed a distinction the specification draws twice in one paragraph.

ImplementationVersionWhat it did
curl libcurl/8.23.0 ngtcp2/1.25.0 Fail Objected at the QUIC layer (rejected at the QUIC layer with INTERNAL_ERROR) instead of signalling the HTTP/3 error. The violation was detected and rejected, which is the right instinct; §8.1 requires an HTTP/3 connection error to be carried in an application close, and this test names 0x201. A transport close carries no HTTP/3 code at all, so a peer has no way to learn what was wrong.
quiche 0.30.0 (1c8e976) Fail Accepted a protocol violation and carried on. The anomaly was on the QPACK encoder stream, and this client extended flow-control credit on it -- a receiver extends credit on a stream only once its application has consumed what was already there, so the bytes were read rather than merely delivered. This should have been rejected.
picoquic 8162550 Fail Accepted a protocol violation and carried on. The anomaly was on the QPACK encoder stream, and this client extended flow-control credit on it -- a receiver extends credit on a stream only once its application has consumed what was already there, so the bytes were read rather than merely delivered. This should have been rejected.
xquic 2060a44 Fail Rejected, but with error 0x106 where the specification requires 0x201. The violation was detected; the code reported is wrong.
msquic 2.7.0 (7e6525d) Fail Accepted a protocol violation and carried on. The anomaly was on the QPACK encoder stream, and this client extended flow-control credit on it -- a receiver extends credit on a stream only once its application has consumed what was already there, so the bytes were read rather than merely delivered. This should have been rejected.

Unknown frame type in a 1-RTT packet

q-reserved-frame · RFC 9000 §12.4 · correctness · MUST · 3 of 12 implementations that reached a verdict

3 independent implementations fail this one. Worth reading as a question about the clause before reading it as a question about the clients.

Close the connection with FRAME_ENCODING_ERROR. Unlike HTTP/3, QUIC reserves no ignorable frame types — §12.4 makes an unknown frame a connection error, so ignoring it is the failure here.

ImplementationVersionWhat it did
neqo 0.31.1 (cac2b74) Fail rejected the unknown frame type with INTERNAL_ERROR, where RFC 9000 §12.4 requires FRAME_ENCODING_ERROR. The violation was detected; the code reported is wrong.
picoquic 8162550 Fail rejected the unknown frame type with PROTOCOL_VIOLATION, where RFC 9000 §12.4 requires FRAME_ENCODING_ERROR. The violation was detected; the code reported is wrong.
xquic 2060a44 Fail closed cleanly after receiving a frame of unknown type. RFC 9000 §12.4 requires a connection error of type FRAME_ENCODING_ERROR: QUIC reserves no ignorable frame types, and an unknown frame carries no length, so nothing after it in the packet can be parsed.

A DATA frame on the control stream

h-control-frame-unexpected · RFC 9114 §7.2.1 · correctness · MUST · 3 of 12 implementations that reached a verdict

3 independent implementations fail this one. Worth reading as a question about the clause before reading it as a question about the clients.

Close the connection with H3_FRAME_UNEXPECTED.

ImplementationVersionWhat it did
curl libcurl/8.23.0 ngtcp2/1.25.0 Fail Objected at the QUIC layer (rejected at the QUIC layer with INTERNAL_ERROR) instead of signalling the HTTP/3 error. The violation was detected and rejected, which is the right instinct; §8.1 requires an HTTP/3 connection error to be carried in an application close, and this test names 0x105. A transport close carries no HTTP/3 code at all, so a peer has no way to learn what was wrong.
picoquic 8162550 Fail Accepted a protocol violation and carried on. The anomaly was on the control stream, and this client extended flow-control credit on it -- a receiver extends credit on a stream only once its application has consumed what was already there, so the bytes were read rather than merely delivered. This should have been rejected.
msquic 2.7.0 (7e6525d) Fail Accepted a protocol violation and carried on. The anomaly was on the control stream, and this client extended flow-control credit on it -- a receiver extends credit on a stream only once its application has consumed what was already there, so the bytes were read rather than merely delivered. This should have been rejected.

Control stream whose first frame is not SETTINGS

h-missing-settings · RFC 9114 §6.2.1 · correctness · MUST · 3 of 12 implementations that reached a verdict

3 independent implementations fail this one. Worth reading as a question about the clause before reading it as a question about the clients.

Close the connection with H3_MISSING_SETTINGS.

ImplementationVersionWhat it did
curl libcurl/8.23.0 ngtcp2/1.25.0 Fail Objected at the QUIC layer (rejected at the QUIC layer with INTERNAL_ERROR) instead of signalling the HTTP/3 error. The violation was detected and rejected, which is the right instinct; §8.1 requires an HTTP/3 connection error to be carried in an application close, and this test names 0x10a. A transport close carries no HTTP/3 code at all, so a peer has no way to learn what was wrong.
picoquic 8162550 Fail Accepted a protocol violation and carried on. The anomaly was on the control stream, and this client extended flow-control credit on it -- a receiver extends credit on a stream only once its application has consumed what was already there, so the bytes were read rather than merely delivered. This should have been rejected.
msquic 2.7.0 (7e6525d) Fail Accepted a protocol violation and carried on. The anomaly was on the control stream, and this client extended flow-control credit on it -- a receiver extends credit on a stream only once its application has consumed what was already there, so the bytes were read rather than merely delivered. This should have been rejected.

A second control stream opened by the server

h-second-control-stream · RFC 9114 §6.2.1 · correctness · MUST · 3 of 11 implementations that reached a verdict

3 independent implementations fail this one. Worth reading as a question about the clause before reading it as a question about the clients.

1 of the fleet reached no verdict here, and which clients those are changes between runs: a client that rejects at the QUIC layer carries no HTTP/3 error code for this to read, and one whose close arrives without the code is indistinguishable from one that never objected. The failures are stable; the size of the set they are counted against is not.

Close the connection with H3_STREAM_CREATION_ERROR.

ImplementationVersionWhat it did
curl libcurl/8.23.0 ngtcp2/1.25.0 Fail Objected at the QUIC layer (rejected at the QUIC layer with INTERNAL_ERROR) instead of signalling the HTTP/3 error. The violation was detected and rejected, which is the right instinct; §8.1 requires an HTTP/3 connection error to be carried in an application close, and this test names 0x103. A transport close carries no HTTP/3 code at all, so a peer has no way to learn what was wrong.
picoquic 8162550 Fail Accepted a protocol violation and carried on. The anomaly was on the control stream, and this client extended flow-control credit on it -- a receiver extends credit on a stream only once its application has consumed what was already there, so the bytes were read rather than merely delivered. This should have been rejected.
msquic 2.7.0 (7e6525d) Fail Accepted a protocol violation and carried on. The anomaly was on the control stream, and this client extended flow-control credit on it -- a receiver extends credit on a stream only once its application has consumed what was already there, so the bytes were read rather than merely delivered. This should have been rejected.

MAX_PUSH_ID sent by the server

h-max-push-id · RFC 9114 §7.2.7 · correctness · MUST NOT · 3 of 12 implementations that reached a verdict

3 independent implementations fail this one. Worth reading as a question about the clause before reading it as a question about the clients.

Reject with H3_FRAME_UNEXPECTED. MAX_PUSH_ID travels client to server only, so a server sending one is using a frame in a direction the specification does not allow.

ImplementationVersionWhat it did
curl libcurl/8.23.0 ngtcp2/1.25.0 Fail Objected at the QUIC layer (rejected at the QUIC layer with INTERNAL_ERROR) instead of signalling the HTTP/3 error. The violation was detected and rejected, which is the right instinct; §8.1 requires an HTTP/3 connection error to be carried in an application close, and this test names 0x105. A transport close carries no HTTP/3 code at all, so a peer has no way to learn what was wrong.
picoquic 8162550 Fail Accepted a protocol violation and carried on. The anomaly was on the control stream, and this client extended flow-control credit on it -- a receiver extends credit on a stream only once its application has consumed what was already there, so the bytes were read rather than merely delivered. This should have been rejected.
msquic 2.7.0 (7e6525d) Fail Accepted a protocol violation and carried on. The anomaly was on the control stream, and this client extended flow-control credit on it -- a receiver extends credit on a stream only once its application has consumed what was already there, so the bytes were read rather than merely delivered. This should have been rejected.

DATA frame before any HEADERS on the response stream

h-data-before-headers · RFC 9114 §4.1 · correctness · MUST · 3 of 11 implementations that reached a verdict

3 independent implementations fail this one. Worth reading as a question about the clause before reading it as a question about the clients.

1 of the fleet reached no verdict here, and which clients those are changes between runs: a client that rejects at the QUIC layer carries no HTTP/3 error code for this to read, and one whose close arrives without the code is indistinguishable from one that never objected. The failures are stable; the size of the set they are counted against is not.

Close the connection with H3_FRAME_UNEXPECTED. A response begins with a field section, and §4.1 makes "receipt of an invalid sequence of frames" a connection error of that type — a body arriving before the headers that describe it is exactly that.

ImplementationVersionWhat it did
curl libcurl/8.23.0 ngtcp2/1.25.0 Fail Objected at the QUIC layer (rejected at the QUIC layer with INTERNAL_ERROR) instead of signalling the HTTP/3 error. The violation was detected and rejected, which is the right instinct; §8.1 requires an HTTP/3 connection error to be carried in an application close, and this test names 0x105. A transport close carries no HTTP/3 code at all, so a peer has no way to learn what was wrong.
quiche 0.30.0 (1c8e976) Fail Accepted a protocol violation and carried on. The anomaly was in the response the client read and delivered, so it was seen; this should have been rejected.
msquic 2.7.0 (7e6525d) Fail Rejected, but with error 0x102 where the specification requires 0x105. The violation was detected; the code reported is wrong.

Stateless reset

q-stateless-reset · RFC 9000 §10.3 · correctness · MUST · 2 of 11 implementations that reached a verdict

2 independent implementations fail this one. Worth reading as a question about the clause before reading it as a question about the clients.

1 of the fleet reached no verdict here, and which clients those are changes between runs: a client that rejects at the QUIC layer carries no HTTP/3 error code for this to read, and one whose close arrives without the code is indistinguishable from one that never objected. The failures are stable; the size of the set they are counted against is not.

Recognise the token in the last 16 bytes of the datagram, enter the draining period, and send no further packets on the connection. The packet cannot be authenticated, so there is nothing to reply with and nothing to report to the peer — continuing to send is the failure §10.3.1 names.

ImplementationVersionWhat it did
aioquic 1.3.0 (PyPI wheel) Fail kept sending after the Stateless Reset: 2 more datagram(s) arrived in the following 3 seconds, after a 500ms grace for packets already in flight (89 of those). RFC 9000 §10.3.1 requires a client that recognises the token to enter the draining period and send nothing further, so this connection is wedged against an endpoint that has forgotten it.
lsquic 4.9.4 (2b30189) Fail kept sending after the Stateless Reset: 9 more datagram(s) arrived in the following 3 seconds, after a 500ms grace for packets already in flight (0 of those). RFC 9000 §10.3.1 requires a client that recognises the token to enter the draining period and send nothing further, so this connection is wedged against an endpoint that has forgotten it.

0-RTT rejected after the client sends early data

q-zero-rtt-reject · RFC 9001 §4.6.2 · resilience · MUST · 2 of 10 implementations that reached a verdict

2 independent implementations fail this one. Worth reading as a question about the clause before reading it as a question about the clients.

2 of the fleet reached no verdict here, and which clients those are changes between runs: a client that rejects at the QUIC layer carries no HTTP/3 error code for this to read, and one whose close arrives without the code is indistinguishable from one that never objected. The failures are stable; the size of the set they are counted against is not.

Reset the state of every stream, including application state bound to them. Section 4.6.2 requires the reset because a rejected 0-RTT means every assumed connection characteristic may have been wrong. It does not require retransmission, which is the application concern, not QUIC's. This port issues tickets that advertise early data and then declines every offer, so a resuming client sends 0-RTT and always has it refused. The handshake itself completes normally.

ImplementationVersionWhat it did
quic-go quic-go v0.61.0 (h1:9So2anK4Tp22) Fail Stalled instead of recovering.
ngtcp2 3c23148 Fail Gave up (rejected at the QUIC layer with INTERNAL_ERROR) instead of recovering.

103 Early Hints before the final response

h-early-hints · RFC 9110 §15.2, RFC 8297 · resilience · MUST · 2 of 12 implementations that reached a verdict

2 independent implementations fail this one. Worth reading as a question about the clause before reading it as a question about the clients.

Treat 103 as informational and keep reading for the final response. RFC 9110 §15.2 makes this a MUST: a client "MUST be able to parse one or more 1xx responses received prior to a final response, even if the client does not expect one". Ignoring the hints is fine — a user agent MAY do that — but closing the connection is not.

ImplementationVersionWhat it did
aioquic 1.3.0 (PyPI wheel) Fail Gave up with error 0x10e instead of recovering.
picoquic 8162550 Fail Gave up with error 0x105 instead of recovering.

Server selects a key exchange group the client never offered

t-group-not-offered · RFC 8446 §4.1.3, §4.2.8, RFC 9001 §4.8 · correctness · MUST · 2 of 10 implementations that reached a verdict

2 independent implementations fail this one. Worth reading as a question about the clause before reading it as a question about the clients.

2 of the fleet reached no verdict here, and which clients those are changes between runs: a client that rejects at the QUIC layer carries no HTTP/3 error code for this to read, and one whose close arrives without the code is indistinguishable from one that never objected. The failures are stable; the size of the set they are counted against is not.

Abort with illegal_parameter. §4.1.3 is explicit that if the selected group was not offered, the client MUST abort — accepting it would let a server steer a client onto a group it deliberately excluded. The port names secp384r1 in its ServerHello against a client that offered only the hybrid, and leaves the payload exactly as generated: the only thing wrong is the name, so a client that aborts can only be aborting over §4.1.3 and not over a share it could not parse. What is judged is the abort, not the alert value. RFC 9001 §4.8 lets a QUIC endpoint replace any alert with a generic one — handshake_failure in place of illegal_parameter — expressly so that a client need not say what it objected to, so the code a client chooses is reported here and not scored. A handshake that ends without any CONNECTION_CLOSE is inconclusive rather than a failure: the group was certainly not accepted, but a close that was never sent cannot be told from one that was lost.

ImplementationVersionWhat it did
curl libcurl/8.23.0 ngtcp2/1.25.0 Fail abandoned the handshake without any alert. The group was not accepted, but §4.1.3 requires the abort to carry illegal_parameter, and RFC 9001 §4.8 delivers an alert -- a generic one is permitted -- in a CONNECTION_CLOSE. None was sent: this client runs on the endpoint's own host, where nothing is lost in transit.
lsquic 4.9.4 (2b30189) Fail abandoned the handshake without any alert. The group was not accepted, but §4.1.3 requires the abort to carry illegal_parameter, and RFC 9001 §4.8 delivers an alert -- a generic one is permitted -- in a CONNECTION_CLOSE. None was sent: this client runs on the endpoint's own host, where nothing is lost in transit.

Hybrid key share whose length does not match the group named with it

t-hybrid-share-length · draft-kwiatkowski-tls-ecdhe-mlkem §3.1.2, RFC 8446 §6.2, RFC 9001 §4.8 · correctness · MUST · 2 of 10 implementations that reached a verdict

2 independent implementations fail this one. Worth reading as a question about the clause before reading it as a question about the clients.

2 of the fleet reached no verdict here, and which clients those are changes between runs: a client that rejects at the QUIC layer carries no HTTP/3 error code for this to read, and one whose close arrives without the code is indistinguishable from one that never objected. The failures are stable; the size of the set they are counted against is not.

Abort rather than proceed. §3.1.2 is explicit: "For all groups, the client MUST check if the ciphertext length matches the selected group, and abort with an illegal_parameter alert if it fails." The port sends 1,088 bytes where X25519MLKEM768 fixes the server share at 1,120 — the ML-KEM ciphertext whole and the 32-byte X25519 tail removed. It parses cleanly as an opaque vector, so the only thing wrong with it is its length for the group it is named with. Neither outcome lets a client derive our keys, because the shared secret needs both halves. What separates them is where it notices: a client that checks the length rejects this at the ServerHello, while one that does not carries a truncated share into decapsulation and fails later and less clearly. What is judged is the abort, not the alert value. RFC 9001 §4.8 lets a QUIC endpoint replace any alert with a generic one, so the code a client chooses is reported and not scored — and a handshake that ends with no CONNECTION_CLOSE at all is inconclusive rather than a failure, because a close that was never sent cannot be told from one that was lost.

ImplementationVersionWhat it did
curl libcurl/8.23.0 ngtcp2/1.25.0 Fail abandoned the handshake without any alert. §3.1.2 requires the abort to carry illegal_parameter, and RFC 9001 §4.8 delivers an alert -- a generic one is permitted -- in a CONNECTION_CLOSE. None was sent: this client runs on the endpoint's own host, where nothing is lost in transit.
lsquic 4.9.4 (2b30189) Fail abandoned the handshake without any alert. §3.1.2 requires the abort to carry illegal_parameter, and RFC 9001 §4.8 delivers an alert -- a generic one is permitted -- in a CONNECTION_CLOSE. None was sent: this client runs on the endpoint's own host, where nothing is lost in transit.

NEW_CONNECTION_ID followed by RETIRE_CONNECTION_ID

q-cid-rotation · RFC 9000 §5.1 · resilience · MUST · 1 of 12 implementations that reached a verdict

Adopt the new connection ID, retire the old one, and stay connected.

ImplementationVersionWhat it did
xquic 2060a44 Fail Gave up (closed the connection with transport error FRAME_ENCODING_ERROR ("local error") and retired none of the connection IDs this endpoint asked it to. The port's only departure from an ordinary one is the rotation: a NEW_CONNECTION_ID whose retire_prior_to retires every earlier ID) instead of recovering.

SETTINGS frame on a request stream

h-settings-on-request-stream · RFC 9114 §7.2.4 · correctness · MUST · 1 of 12 implementations that reached a verdict

Close the connection with H3_FRAME_UNEXPECTED. SETTINGS belongs to the control stream alone: §7.2.4 says that if an endpoint receives one on a different stream it "MUST respond with a connection error of type H3_FRAME_UNEXPECTED".

ImplementationVersionWhat it did
curl libcurl/8.23.0 ngtcp2/1.25.0 Fail Objected at the QUIC layer (rejected at the QUIC layer with INTERNAL_ERROR) instead of signalling the HTTP/3 error. The violation was detected and rejected, which is the right instinct; §8.1 requires an HTTP/3 connection error to be carried in an application close, and this test names 0x105. A transport close carries no HTTP/3 code at all, so a peer has no way to learn what was wrong.