{"host":"conformance.pqcrypta.com","how":"Connect to each port in turn. To collect results under one session, use SNI <session>.<host>; obtain a session from /session.","tests":[{"anomaly":"transport","class":"correctness","documents":["RFC 9000"],"expectation":"Abandon the connection attempt, or retry with a version both ends support. §6.2 requires a client that supports only one version to abandon it rather than persist.","id":"q-version-negotiation","layer":"quic","port":4460,"requirement":"must","requirement_label":"MUST","spec":"RFC 9000 §6","title":"Version Negotiation offering a reserved version alongside v1"},{"anomaly":"transport","class":"correctness","documents":["RFC 9000"],"expectation":"Echo the Retry token in a second Initial packet and complete the handshake.","id":"q-retry","layer":"quic","port":4461,"requirement":"must","requirement_label":"MUST","spec":"RFC 9000 §8.1.2","title":"Retry packet for source-address validation"},{"anomaly":"transport","class":"extensibility","documents":["RFC 9000"],"expectation":"Ignore the unknown parameter and complete the handshake normally.","id":"q-reserved-transport-param","layer":"quic","port":4462,"requirement":"must","requirement_label":"MUST","spec":"RFC 9000 §18.1","title":"Reserved transport parameter (31·N+27)"},{"anomaly":"transport","class":"correctness","documents":["RFC 9000"],"expectation":"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.","id":"q-reserved-frame","layer":"quic","port":4463,"requirement":"must","requirement_label":"MUST","spec":"RFC 9000 §12.4","title":"Unknown frame type in a 1-RTT packet"},{"anomaly":"transport","class":"resilience","documents":["RFC 9000"],"expectation":"Adopt the new connection ID, retire the old one, and stay connected.","id":"q-cid-rotation","layer":"quic","port":4464,"requirement":"must","requirement_label":"MUST","spec":"RFC 9000 §5.1","title":"NEW_CONNECTION_ID followed by RETIRE_CONNECTION_ID"},{"anomaly":"transport","class":"correctness","documents":["RFC 9000"],"expectation":"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.","id":"q-stateless-reset","layer":"quic","port":4465,"requirement":"must","requirement_label":"MUST","spec":"RFC 9000 §10.3","title":"Stateless reset"},{"anomaly":"transport","class":"discretionary","documents":["RFC 9000"],"expectation":"Respect the limit. Announcing the stall with DATA_BLOCKED or STREAM_DATA_BLOCKED is a SHOULD in §4.1, not a MUST, so a client that stays silent is still conformant.","id":"q-flow-control","layer":"quic","port":4466,"requirement":"should","requirement_label":"SHOULD","spec":"RFC 9000 §4","title":"Deliberately tight MAX_DATA and MAX_STREAM_DATA"},{"anomaly":"transport","class":"discretionary","documents":["draft-ietf-quic-ack-frequency"],"expectation":"Negotiate the extension, or ignore it. Either is correct; failing is not.","id":"q-ack-frequency","layer":"quic","port":4467,"requirement":"may","requirement_label":"MAY","spec":"draft-ietf-quic-ack-frequency","title":"ACK Frequency extension offered"},{"anomaly":"transport","class":"correctness","documents":["RFC 9000"],"expectation":"Echo the ECN counts back in ACK frames carrying an ECN section (type 0x03). §13.4.1 makes this a conditional requirement — an endpoint MUST report the markings it receives \"if these are accessible\", and explicitly permits an endpoint with no access to the ECN field to report nothing. So counts coming back is a pass, and silence is never a failure.\n\nSilence is read against the path rather than left unresolved. Both directions cross the same path: a client whose own datagrams reach this endpoint still carrying ECT has shown the codepoint survives and that its stack sets it, and so has a port where any peer has ever echoed the markings sent from it. Either makes the silence the client's own and the result `unsupported` -- a property of the client, which §13.4.1 expressly allows. Only where nothing has shown the codepoint surviving is the run inconclusive, because only there is a stripped path still a live possibility.","id":"q-ecn","layer":"quic","port":4468,"requirement":"must","requirement_label":"MUST","spec":"RFC 9000 §13.4","title":"Packets marked ECT(0)"},{"anomaly":"transport","class":"resilience","documents":["RFC 9000","RFC 8899"],"expectation":"Detect the black hole, probe down to a working size, and keep the connection. Path-MTU discovery is driven past the limit on this port, so it engages on every connection.","id":"q-pmtu-blackhole","layer":"quic","port":4469,"requirement":"should","requirement_label":"SHOULD","spec":"RFC 9000 §14, RFC 8899","title":"Path MTU black hole above a threshold"},{"anomaly":"transport","class":"correctness","documents":["RFC 9000"],"expectation":"Reply with PATH_RESPONSE carrying the identical 8-byte payload.","id":"q-path-challenge","layer":"quic","port":4470,"requirement":"must","requirement_label":"MUST","spec":"RFC 9000 §8.2","title":"Server-initiated PATH_CHALLENGE"},{"anomaly":"transport","class":"resilience","documents":["RFC 9001"],"expectation":"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.\n\nThis 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.","id":"q-zero-rtt-reject","layer":"quic","port":4471,"requirement":"must","requirement_label":"MUST","spec":"RFC 9001 §4.6.2","title":"0-RTT rejected after the client sends early data"},{"anomaly":"transport","class":"discretionary","documents":["draft-ietf-quic-multipath"],"expectation":"Use the additional path, or decline it cleanly. Do not abort the connection.","id":"q-multipath","layer":"quic","port":4472,"requirement":"may","requirement_label":"MAY","spec":"draft-ietf-quic-multipath","title":"A second path offered mid-connection"},{"anomaly":"transport","class":"interoperability","documents":["RFC 9001"],"expectation":"Update to the next key phase and carry on. Once a packet protected with the next phase is processed, §6.2 is a MUST — \"The endpoint MUST update its send keys to the corresponding key phase in response\" — so the response written after the update has to be read with the new keys and the request completed.","id":"q-key-update","layer":"quic","port":4487,"requirement":"must","requirement_label":"MUST","spec":"RFC 9001 §6.2","title":"Spontaneous 1-RTT key update"},{"anomaly":"transport","class":"discretionary","documents":["RFC 9000"],"expectation":"Stay inside the advertised limits. Respecting them is a MUST — §4.6 says \"Endpoints MUST NOT exceed the limit set by their peer\" — but announcing the stall with STREAMS_BLOCKED is only a SHOULD, so a client that stays silent is still conformant.","id":"q-stream-limit","layer":"quic","port":4488,"requirement":"must_not","requirement_label":"MUST NOT","spec":"RFC 9000 §4.6","title":"Stream limits set to the minimum a request needs"},{"anomaly":"transport","class":"resilience","documents":["RFC 9000"],"expectation":"Reassemble the stream and deliver the whole body. §2.2 requires an endpoint to buffer data received out of order and deliver it as an ordered byte stream, and §13.3 has the lost data sent again in new STREAM frames — so a response full of gaps must still arrive complete and in order.","id":"q-loss-recovery","layer":"quic","port":4489,"requirement":"must","requirement_label":"MUST","spec":"RFC 9000 §2.2, §13.3","title":"One datagram in twelve dropped once the path is established"},{"anomaly":"transport","class":"discretionary","documents":["RFC 9000"],"expectation":"Keep using the address you are already talking to. §9.6 says a client \"SHOULD ignore packets received from a server address other than the one it is currently using for sending packets\" — a SHOULD, so quietly discarding them and objecting are both conformant. Following the new address is not: nothing is listening there, and a server may only move a client to an address it advertised as its preferred one.","id":"q-connection-migration","layer":"quic","port":4495,"requirement":"should","requirement_label":"SHOULD","spec":"RFC 9000 §9.6","title":"Datagrams arriving from a second server address"},{"anomaly":"transport","class":"correctness","documents":["RFC 9000"],"expectation":"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.\"\n\nDistinct 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.\n\nThe 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.","id":"q-invalid-transport-param","layer":"quic","port":4496,"requirement":"must","requirement_label":"MUST","spec":"RFC 9000 §7.4, §18.2","title":"Transport parameter carrying a value the specification forbids"},{"anomaly":"response_stream","class":"discretionary","documents":["RFC 8470"],"expectation":"Handle being told the request arrived too early. §5.2 says a user agent \"SHOULD retry automatically, but any retries MUST NOT be sent in early data\" — so retrying on the 1-RTT keys and handing the 425 back to the caller are both conformant, and the report says which happened. Falling over is not one of the options.\n\nThis is the only port that *accepts* early data instead of refusing it, which is what makes the 425 exchange possible at all. What it does not check is §4's rule that unsafe methods must never be sent in early data: reading the method would mean QPACK-decoding the request, which this suite deliberately never does.\n\nReaching it needs a session ticket from an earlier connection to this same port, so a client that connects once has none.","id":"q-zero-rtt-replay","layer":"quic","port":4497,"requirement":"should","requirement_label":"SHOULD","spec":"RFC 8470 §5.2","title":"425 (Too Early) in answer to a request sent as early data"},{"anomaly":"transport","class":"correctness","documents":["RFC 9000"],"expectation":"Report the CE count back in the ECN section of an ACK. §13.4.1 makes this the same conditional requirement as reporting ECT(0) — an endpoint MUST provide feedback about the markings it receives \"if these are accessible\" — and CE is the marking that matters, since it is how a path says it is congested.\n\nDistinct from the ECT(0) test on the neighbouring port: that one asks whether a client reports markings at all, and this asks whether it distinguishes the one that means something. A client that echoes ECT(0) counts faithfully and never reports a CE has a congestion signal it cannot see.\n\nSilence is never a failure, for the same reason, and it is read against the path in the same way: where this client's own datagrams arrived carrying ECT, or where any peer has echoed the markings this port sent, the codepoint demonstrably survives and the silence is the client's own -- `unsupported`, which §13.4.1 expressly allows. Only where nothing has shown the codepoint surviving is a stripped path still possible, and only there is the run inconclusive.","id":"q-ecn-congestion","layer":"quic","port":4508,"requirement":"must","requirement_label":"MUST","spec":"RFC 9000 §13.4","title":"Path marking packets CE, not just ECT(0)"},{"anomaly":"transport","class":"interoperability","documents":["RFC 9001"],"expectation":"Follow both updates and carry on. §6.1 lets an endpoint update again once the previous phase has been acknowledged, so a long-lived connection changes keys repeatedly and a client has to track the phase rather than assume one change.\n\nDistinct from the single update on the neighbouring port. An implementation that hardcodes the first transition — treating key phase as a one-way flag rather than a bit that alternates — passes that test and fails here, which is precisely the bug worth finding.","id":"q-key-update-repeated","layer":"quic","port":4509,"requirement":"must","requirement_label":"MUST","spec":"RFC 9001 §6.1, §6.5","title":"A second key update after the first is acknowledged"},{"anomaly":"transport","class":"discretionary","documents":["RFC 9000"],"expectation":"Wait for the credit, then open the request. This port grants no bidirectional streams in its transport parameters and issues MAX_STREAMS a moment later. §4.6 is unambiguous that a client may not jump the gun — \"Endpoints MUST NOT exceed the limit set by their peer\" — and announcing the wait with STREAMS_BLOCKED is a SHOULD, so a client that waits quietly is equally conformant.\n\nGiving up rather than waiting is not scored as a failure: nothing obliges a one-shot client to sit on a connection it cannot use yet, and the report says which it did.","id":"q-max-streams-credit","layer":"quic","port":4510,"requirement":"must_not","requirement_label":"MUST NOT","spec":"RFC 9000 §4.6","title":"No stream credit at first, then MAX_STREAMS mid-connection"},{"anomaly":"control_stream","class":"extensibility","documents":["RFC 9114"],"expectation":"Ignore the unknown setting and complete the request.","id":"h-grease-settings","layer":"http3","port":4473,"requirement":"must","requirement_label":"MUST","spec":"RFC 9114 §7.2.4.1","title":"Reserved SETTINGS identifier (0x1f·N+0x21)"},{"anomaly":"response_stream","class":"extensibility","documents":["RFC 9114"],"expectation":"Skip the frame using its length and read the response that follows.","id":"h-grease-frame","layer":"http3","port":4474,"requirement":"must","requirement_label":"MUST","spec":"RFC 9114 §7.2.8","title":"Reserved frame type on the response stream"},{"anomaly":"other_uni_stream","class":"extensibility","documents":["RFC 9114"],"expectation":"Abort reading the stream or discard it — §6.2.3 permits either. What it forbids is treating it as meaningful, or as fatal to the connection.","id":"h-reserved-uni-stream","layer":"http3","port":4475,"requirement":"must","requirement_label":"MUST","spec":"RFC 9114 §6.2.3","title":"Unidirectional stream with a reserved stream type"},{"anomaly":"control_stream","class":"discretionary","documents":["RFC 9114"],"expectation":"Either reject with H3_SETTINGS_ERROR or ignore the repeat — the specification says a receiver MAY treat this as an error, so both are conformant.","id":"h-duplicate-setting","layer":"http3","port":4476,"requirement":"may","requirement_label":"MAY","spec":"RFC 9114 §7.2.4","title":"SETTINGS containing the same identifier twice"},{"anomaly":"control_stream","class":"correctness","documents":["RFC 9114"],"expectation":"Close the connection with H3_FRAME_UNEXPECTED.","id":"h-control-frame-unexpected","layer":"http3","port":4477,"requirement":"must","requirement_label":"MUST","spec":"RFC 9114 §7.2.1","title":"A DATA frame on the control stream"},{"anomaly":"control_stream","class":"correctness","documents":["RFC 9114"],"expectation":"Close the connection with H3_MISSING_SETTINGS.","id":"h-missing-settings","layer":"http3","port":4478,"requirement":"must","requirement_label":"MUST","spec":"RFC 9114 §6.2.1","title":"Control stream whose first frame is not SETTINGS"},{"anomaly":"control_stream","class":"correctness","documents":["RFC 9114"],"expectation":"Close the connection with H3_STREAM_CREATION_ERROR.","id":"h-second-control-stream","layer":"http3","port":4479,"requirement":"must","requirement_label":"MUST","spec":"RFC 9114 §6.2.1","title":"A second control stream opened by the server"},{"anomaly":"response_stream","class":"interoperability","documents":["RFC 9204"],"expectation":"Apply the encoder-stream insertions and decode the headers correctly.","id":"h-qpack-dynamic-table","layer":"http3","port":4480,"requirement":"must","requirement_label":"MUST","spec":"RFC 9204 §4.3.3, §4.5.2","title":"Field lines referencing dynamic table insertions"},{"anomaly":"response_stream","class":"interoperability","documents":["RFC 9204","RFC 7541"],"expectation":"Decode without error. Padding of up to 7 bits is legal, not corruption.","id":"h-qpack-huffman","layer":"http3","port":4481,"requirement":"must","requirement_label":"MUST","spec":"RFC 9204 §4.1.2, RFC 7541 §5.2","title":"Huffman-coded field lines with maximal padding"},{"anomaly":"response_stream","class":"interoperability","documents":["RFC 9114"],"expectation":"Handle it as an error against that one request, not the whole connection.","id":"h-oversized-field-section","layer":"http3","port":4482,"requirement":"must","requirement_label":"MUST","spec":"RFC 9114 §4.2.2","title":"Field section larger than the client's advertised maximum"},{"anomaly":"response_stream","class":"interoperability","documents":["RFC 9114"],"expectation":"Deliver the trailers to the application after the body completes.","id":"h-trailers","layer":"http3","port":4483,"requirement":"must","requirement_label":"MUST","spec":"RFC 9114 §4.1","title":"Trailing field section after the body"},{"anomaly":"response_stream","class":"resilience","documents":["RFC 9110","RFC 8297"],"expectation":"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.","id":"h-early-hints","layer":"http3","port":4484,"requirement":"must","requirement_label":"MUST","spec":"RFC 9110 §15.2, RFC 8297","title":"103 Early Hints before the final response"},{"anomaly":"control_stream","class":"resilience","documents":["RFC 9114"],"expectation":"Stop opening requests, finish those in flight, and retry idempotent ones elsewhere. The GOAWAY is written once the client's request is running and names the stream after it, so §5.2 puts that request inside the range the server promises to process: finishing it is the behaviour under test. Sent before the request instead, the correct answer would be to close and reconnect, and the test would be measuring a race rather than recovery.","id":"h-goaway","layer":"http3","port":4485,"requirement":"should","requirement_label":"SHOULD","spec":"RFC 9114 §5.2","title":"GOAWAY sent mid-connection"},{"anomaly":"control_stream","class":"correctness","documents":["RFC 9114"],"expectation":"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.","id":"h-max-push-id","layer":"http3","port":4486,"requirement":"must_not","requirement_label":"MUST NOT","spec":"RFC 9114 §7.2.7","title":"MAX_PUSH_ID sent by the server"},{"anomaly":"response_stream","class":"correctness","documents":["RFC 9114"],"expectation":"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\".","id":"h-settings-on-request-stream","layer":"http3","port":4490,"requirement":"must","requirement_label":"MUST","spec":"RFC 9114 §7.2.4","title":"SETTINGS frame on a request stream"},{"anomaly":"response_stream","class":"correctness","documents":["RFC 9114"],"expectation":"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.","id":"h-data-before-headers","layer":"http3","port":4491,"requirement":"must","requirement_label":"MUST","spec":"RFC 9114 §4.1","title":"DATA frame before any HEADERS on the response stream"},{"anomaly":"control_stream","class":"correctness","documents":["RFC 9114"],"expectation":"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.","id":"h-cancel-push-unsolicited","layer":"http3","port":4492,"requirement":"must","requirement_label":"MUST","spec":"RFC 9114 §7.2.3","title":"CANCEL_PUSH for a push ID that was never promised"},{"anomaly":"response_stream","class":"interoperability","documents":["RFC 9204"],"expectation":"Hold the field section, apply the insertions when they arrive on the encoder stream, and complete the request. §2.2.1 makes a section whose Required Insert Count exceeds the decoder's Insert Count a blocked stream — something to be waited on, not an error.\n\nOnly run when the client advertised both a table capacity and at least one blocked stream; §2.1.2 forbids the encoder from blocking more streams than the decoder promised to support, so a client that permits none cannot be tested on this.","id":"h-qpack-blocked-stream","layer":"http3","port":4493,"requirement":"must","requirement_label":"MUST","spec":"RFC 9204 §2.1.2, §2.2.1","title":"Field section that blocks until the encoder stream catches up"},{"anomaly":"response_stream","class":"discretionary","documents":["RFC 9114"],"expectation":"Abandon the partial response — §4.1 says a response cancelled after a partial delivery \"SHOULD NOT be used\". Whether the connection survives is the client's to choose: §8 lets an endpoint \"treat a stream error as a connection error under certain circumstances\", so keeping the connection and closing it are both conformant. Stalling is the one wrong answer.","id":"h-response-stream-reset","layer":"http3","port":4494,"requirement":"may","requirement_label":"MAY","spec":"RFC 9114 §8, §4.1","title":"Response stream reset mid-body with H3_REQUEST_CANCELLED"},{"anomaly":"control_stream","class":"discretionary","documents":["RFC 9218"],"expectation":"Reject it with H3_FRAME_UNEXPECTED, or ignore it — which is conformant depends on whether you implement extensible priorities at all, and that is not observable from here.\n\nA client that does implement RFC 9218 is bound by §7.2: servers \"MUST NOT send PRIORITY_UPDATE frames of either type\", and a client receiving one MUST treat it as a connection error of that type. A client that does not implement it sees frame type 0xf0700 as simply unknown, and RFC 9114 §9 requires unknown frame types to be ignored — so ignoring it is equally correct, for a different reason.\n\nScoring this as a failure either way would accuse one of those two clients of a violation it did not commit, so the report says which answer was given rather than grading it.","id":"h-priority-update","layer":"http3","port":4498,"requirement":"must_not","requirement_label":"MUST NOT","spec":"RFC 9218 §7.2","title":"PRIORITY_UPDATE sent by the server"},{"anomaly":"control_stream","class":"discretionary","documents":["RFC 9220","RFC 8441"],"expectation":"Reject it or ignore it, but keep working. RFC 8441 §3 says the value \"MUST be 0 or 1\" and RFC 9220 carries that into HTTP/3 unchanged — yet neither names a behaviour for a receiver that sees anything else, so both answers are conformant and stalling is not. The setting is how a client learns Extended CONNECT is available, so a WebTransport-capable client has real parsing behind it.","id":"h-extended-connect","layer":"http3","port":4499,"requirement":"must","requirement_label":"MUST","spec":"RFC 9220 §3, RFC 8441 §3","title":"SETTINGS_ENABLE_CONNECT_PROTOCOL with a value outside 0 and 1"},{"anomaly":"response_stream","class":"correctness","documents":["RFC 9114"],"expectation":"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.\n\nWHERE 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\".","id":"h-push-promise-unsolicited","layer":"http3","port":4500,"requirement":"must","requirement_label":"MUST","spec":"RFC 9114 §7.2.5, §4.6","title":"PUSH_PROMISE for a push the client never allowed"},{"anomaly":"control_stream","class":"correctness","documents":["RFC 9297"],"expectation":"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\".\n\nThis 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.","id":"h-datagram-setting-invalid","layer":"http3","port":4501,"requirement":"must","requirement_label":"MUST","spec":"RFC 9297 §2.1.1","title":"SETTINGS_H3_DATAGRAM with a value that is neither 0 nor 1"},{"anomaly":"other_uni_stream","class":"correctness","documents":["RFC 9204"],"expectation":"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\".\n\nUnlike 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.","id":"h-qpack-encoder-overflow","layer":"http3","port":4502,"requirement":"must","requirement_label":"MUST","spec":"RFC 9204 §4.3.1, §6","title":"Encoder stream setting a dynamic table capacity above the client's limit"},{"anomaly":"control_stream","class":"correctness","documents":["RFC 9114"],"expectation":"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.\"","id":"h-goaway-increasing","layer":"http3","port":4503,"requirement":"must","requirement_label":"MUST","spec":"RFC 9114 §5.2","title":"A second GOAWAY naming a larger identifier than the first"},{"anomaly":"other_uni_stream","class":"correctness","documents":["RFC 9114"],"expectation":"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.\n\nDistinct 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.","id":"h-push-stream-unpromised","layer":"http3","port":4504,"requirement":"must","requirement_label":"MUST","spec":"RFC 9114 §6.2.2","title":"Push stream opened for a push nobody allowed"},{"anomaly":"response_stream","class":"correctness","documents":["RFC 9204"],"expectation":"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.\"\n\nThe static table needs no permission and never changes size, so unlike the dynamic-table tests this one applies to every client.","id":"h-qpack-static-index-invalid","layer":"http3","port":4505,"requirement":"must","requirement_label":"MUST","spec":"RFC 9204 §3.1, §4.5.2","title":"Field line indexing a static table entry that does not exist"},{"anomaly":"transport","class":"resilience","documents":["RFC 9000"],"expectation":"Put the stream back in order and deliver the whole body. §2.2 requires an endpoint to be \"able to deliver stream data to an application as an ordered byte stream\", and says plainly that doing so \"requires that an endpoint buffer any data that is received out of order\".\n\nDistinct from the loss test, which needs retransmission before the gap can be filled. Nothing is lost here: every byte arrives, some of it early, and a client that assumes arrival order is delivery order will produce a corrupt body or stall waiting for data it already has.","id":"q-packet-reordering","layer":"quic","port":4506,"requirement":"must","requirement_label":"MUST","spec":"RFC 9000 §2.2","title":"Datagrams delivered out of order"},{"anomaly":"other_uni_stream","class":"correctness","documents":["RFC 9204"],"expectation":"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\".\n\nPaired 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.","id":"h-qpack-encoder-bad-name-index","layer":"http3","port":4507,"requirement":"must","requirement_label":"MUST","spec":"RFC 9204 §3.1, §4.3.2","title":"Encoder instruction naming a static index that does not exist"},{"anomaly":"transport","class":"interoperability","documents":["RFC 8446"],"expectation":"Complete the handshake. A client whose first key share was classical must recover through HelloRetryRequest, which §4.1.4 requires it to answer with a second ClientHello carrying a share for the named group. A client that does not offer the group at all must abandon the attempt cleanly rather than stall — that is a correct outcome for a client without post-quantum support.\n\nA client that cannot negotiate the group at all never establishes a connection, so the server records no verdict and the result is `not run` rather than a pass or a failure. On this tier that reads as \"no post-quantum key exchange\", which is itself the finding: every other tier reaches `not run` only when the runner skipped something. curl/ngtcp2 1.11.0 lands here.\n\nThis is the round trip that breaks first in a real migration, because it is the one that never happens until a server somewhere stops offering the classical group.","id":"t-hybrid-only","layer":"tls","port":4511,"requirement":"must","requirement_label":"MUST","spec":"RFC 8446 §4.1.4, draft-ietf-tls-hybrid-design","title":"Server negotiates only the post-quantum hybrid X25519MLKEM768"},{"anomaly":"transport","class":"discretionary","documents":["RFC 8446"],"expectation":"Either outcome is conformant and the report says which was taken. A client that proceeds has chosen availability: the connection is classically secure and it accepted that. A client that refuses has chosen a post-quantum floor, which is a policy some deployments now require and no RFC yet mandates.\n\nNo grade is attached, because attaching one would invent a requirement. What is worth knowing is that the answer is a deliberate choice rather than an accident, and today most clients cannot express it either way.","id":"t-classical-only","layer":"tls","port":4512,"requirement":"may","requirement_label":"MAY","spec":"RFC 8446 §4.1.1, draft-ietf-tls-hybrid-design §5","title":"Server negotiates only classical X25519 against a hybrid offer"},{"anomaly":"transport","class":"resilience","documents":["RFC 9000","RFC 9001"],"expectation":"Complete the handshake with a ClientHello that does not fit one QUIC Initial packet. An ML-KEM-768 key share is 1,216 bytes, which pushes a ClientHello past the 1,200-byte floor RFC 9000 §14.1 sets for an Initial, so the flight must be spread over more than one packet and every one of them padded to the full size.\n\nThis is the concrete reason post-quantum TLS deployments fail in the field, and it interacts with the §8.1 anti-amplification limit: the server may not send more than three times what it has received, so a client that under-pads its Initials can stall the handshake without either side doing anything invalid.","id":"t-hybrid-large-hello","layer":"tls","port":4513,"requirement":"must","requirement_label":"MUST","spec":"RFC 9000 §8.1, §14.1, RFC 9001 §4.4","title":"Hybrid key share large enough to split the Initial across packets"},{"anomaly":"transport","class":"correctness","documents":["RFC 8446","RFC 9001"],"expectation":"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.\n\nThe 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.\n\nWhat 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.","id":"t-group-not-offered","layer":"tls","port":4514,"requirement":"must","requirement_label":"MUST","spec":"RFC 8446 §4.1.3, §4.2.8, RFC 9001 §4.8","title":"Server selects a key exchange group the client never offered"},{"anomaly":"transport","class":"correctness","documents":["RFC 8446"],"expectation":"Fail the handshake. The hybrid secret is the concatenation of both shares fed through the key schedule, so corrupting either half must produce a transcript mismatch and a failed Finished verification.\n\nWhat is being looked for is the failure mode, not the failure: a client that falls back to the classical half alone has silently downgraded itself to exactly the security level the hybrid exists to avoid, and would do so against an attacker who can corrupt one half at will.\n\nThe server share for X25519MLKEM768 is the 1,088-byte ML-KEM ciphertext followed by the 32-byte X25519 key. One bit is flipped early in the ciphertext and the classical tail is left untouched, so a client that still completes has used the classical half alone. A single bit rather than a scribble on purpose: ML-KEM decapsulation never fails, it returns an implicit-rejection secret, so the handshake has to die at Finished verification rather than at a decode error — damaging the length or the structure would test the parser instead.","id":"t-corrupt-hybrid-share","layer":"tls","port":4515,"requirement":"must","requirement_label":"MUST","spec":"draft-ietf-tls-hybrid-design §3.2, RFC 8446 §4.1.3","title":"Hybrid key share with an intact X25519 half and a corrupt ML-KEM half"},{"anomaly":"transport","class":"correctness","documents":["RFC 8446","RFC 9001"],"expectation":"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.\"\n\nThe 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.\n\nNeither 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.\n\nWhat 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.","id":"t-hybrid-share-length","layer":"tls","port":4517,"requirement":"must","requirement_label":"MUST","spec":"draft-kwiatkowski-tls-ecdhe-mlkem §3.1.2, RFC 8446 §6.2, RFC 9001 §4.8","title":"Hybrid key share whose length does not match the group named with it"},{"anomaly":"transport","class":"discretionary","documents":["RFC 8879","RFC 8446"],"expectation":"Decompress and parse an ML-DSA-87 chain, then judge it on its merits. Nothing here is graded: RFC 8879 is optional, no RFC requires support for ML-DSA certificates, and §4 expressly lets a receiver cap the decompressed size and abort. What the port reports is which of those a client does.\n\nThe chain is deliberately issued by a private CA nobody trusts, and that is what makes the measurement work rather than spoiling it. A client that rejects it for its *trust anchor* — unknown_ca, or bad_certificate — has already decompressed a certificate message several times the size of a classical one, parsed ML-DSA-87 structures it may never have seen and got as far as chain building. That is the whole capability under test, and the rejection that follows is correct behaviour, not a failure.\n\nA client that cannot get that far answers differently: decode_error or a record-size abort says the compressed chain itself defeated it, which is the outcome the post-quantum migration needs to know about. Certificate sizes are the half of that migration nobody can configure their way out of — ML-DSA-87 signatures are 4,627 bytes each and every chain carries several.","id":"t-cert-compression-pq","layer":"tls","port":4518,"requirement":"may","requirement_label":"MAY","spec":"RFC 8879 §4, RFC 8446 §4.4.2","title":"Post-quantum certificate chain, compressed per RFC 8879"},{"anomaly":"transport","class":"extensibility","documents":["RFC 8701","RFC 8446"],"expectation":"Ignore the unrecognised group and complete the handshake. RFC 8701 reserves these values precisely so that an endpoint meeting one learns to tolerate a future real group sharing the same shape.\n\nThe value goes in the server's supported_groups in EncryptedExtensions, second in a list of real groups. RFC 8446 §4.2.7 permits a server to send that list — \"regardless of whether they are currently supported by the client\" — and requires that a client \"MUST NOT act upon any information found in supported_groups prior to successful completion of the handshake\". So the handshake must complete, and a client that aborts over a codepoint it was told not to act on has ossified against groups that do not exist yet.\n\nWHERE the value goes is the whole test, and getting it wrong the first time is on the record deliberately. It was built by naming a GREASE value in the ServerHello key_share, which is not an extensibility test at all: §4.1.3 makes a key_share naming any group the client did not offer illegal whatever the value is, so every conformant client correctly answered illegal_parameter — and an Extensibility test demanding tolerance scored all of them as failures. neqo caught it on first contact. It also duplicated t-group-not-offered, which measures that rejection properly.\n\nThe honest version needed an extension rustls had no reason to send, so the fork's ServerExtensions gained a named_groups field. The port and the id never moved while it was unimplemented.","id":"t-grease-group","layer":"tls","port":4516,"requirement":"must","requirement_label":"MUST","spec":"RFC 8701 §4, RFC 8446 §4.2.7","title":"GREASE named group a client must tolerate"}],"verdicts":"A failure requires positive evidence the client read the anomaly. Where `anomaly` is response_stream or transport the client had to process it to be served, so completing the exchange without objecting is a failure. Where it is control_stream, a one-shot request can close before ever reading the stream, so silence is inconclusive rather than a failure."}