All fourteen Churust crates release together on 0.3.3.
[dependencies]
churust = "0.3.3"Fixed
-
An HTTP/1.1 request without exactly one
Hostis refused. RFC 9112 §3.2
requires a400for a request that carries none or several, and both were
served. TwoHostfields is a request this server and an intermediary in front
can disagree about — the intermediary routes or authorizes on one, the origin
serves the other — and none at all leavesCall::hostand everyguard::host
route deciding on nothing. The check is gated on the version: HTTP/2 and
HTTP/3 carry the authority in:authorityinstead, HTTP/1.0 predates the
requirement, and an absolute-form target already carries the authority it
needs. -
backlogapplies to the Unix-socket listener. It was honoured on every TCP
listener and silently dropped forserve_unix, which used the platform default
— 128 on Linux — however the knob was set. Neither std's nor tokio's
UnixListener::bindlets the backlog be chosen, so this binds through a socket
the way the TCP path already did. -
Call::hostandguard::hostwork over HTTP/3. HTTP/3 carries the target
authority in:authority, which arrives in the URI, and sends noHostfield
beside it. Normalising the target to origin form dropped the authority, so
Call::hostansweredNoneon that transport and every host guard stopped
matching. A host-scoped route was a silent404; with an unguarded sibling on
the same method and path — the ordinary virtual-host arrangement — the request
was served by the fallback handler with a200. The same request over
HTTP/1.1 or HTTP/2 matched correctly, andadvertise_http3steers clients onto
the affected transport. -
keep_alive_msbounds an idle HTTP/3 connection. The QUIC listener used a
hardcoded 75s idle timeout regardless of the configured value. Both happen to
default to75000, so nothing failed until the knob was lowered — after which
TCP honoured it and QUIC did not. An idle QUIC connection holds a
max_connectionspermit for as long as it lives, which is why the timeout
exists. -
keep_alive_ms = 0closes an HTTP/2 connection.0means "answer and
close". hyper implements that for HTTP/1 and has no HTTP/2 counterpart, and the
idle watchdog was switched off entirely at0— so an h2 connection got
neither, and was held for the life of the process. The strictest setting
available was therefore weaker than the default: a peer that opened
max_connectionsh2 connections, sent one request on each and then idled while
answering keep-alive pings exhausted the budget permanently. -
max_tls_handshakesandtls_handshake_timeout_msbound QUIC handshakes.
Both were enforced by the TCP accept loop and ignored by the HTTP/3 listener,
so enablinghttp3opted a deployment out of them. A QUIC handshake is a TLS
1.3 handshake and asymmetric in the same way — cheap to ask for, expensive to
answer — somax_connectionsalone was too loose a bound. As on TCP, the
deadline covers the wait for the handshake budget as well as the handshake
itself; timing only the work would make the budget a rate limiter while queued
peers held connection permits with no deadline at all. -
h2_max_concurrent_streamsbounds HTTP/3 request streams. The value never
reached quinn'smax_concurrent_bidi_streams, so a connection got quinn's own
default of 100 however the knob was set. Every in-flight h3 request buffers its
body up tomax_body_bytes, so this cap is what decides how much one peer can
make the process hold. -
An HTTP/3 request body the client declares too large is refused before any of
it is read. The h3 reader refused at the chunk that crossed
max_body_bytes, which bounded what was held but still accepted everything up
to it — and aContent-Lengthwith nothing behind it crossed nothing, so the
handler received an empty body and answered200for a request the server had
already said it would refuse. The TCP path made this check; h3 now does too.
Changed
-
serve_unixrefuses an app configured for TLS. A Unix socket carries no
TLS and that listener never consulted the setting, so such an app served
cleartext whileapply_security_headerskept asserting HSTS on every
response — the gate readsconfig.tls.is_some(), not the transport. A
cleartext service was telling its clients it was HTTPS-only. The two settings
contradict each other, so this is now anInvalidInputerror at startup rather
than a silent downgrade. Breaking for anyone who set both: drop thetls
configuration to serve over a Unix socket, or useserve/startto terminate
TLS on a TCP listener. An app behind a TLS-terminating proxy should not be
configuringtlsat all. -
A host guard that never matched over HTTP/3 now matches. This follows from
theCall::hostfix above and is worth stating separately: an application
serving h3 whose host-scoped routes were silently falling through to an
unguarded sibling will start routing to the guarded handler, which is what it
asked for. Anything that had come to depend on the fallback behaviour changes. -
Loading a PEM chain and key, and merging
Vary, each happen in one place.
load_certs/load_keyexisted byte-for-byte in both the TLS and HTTP/3
modules; theVarymerge existed inchurust-corsandchurust-compression
as two implementations kept in step by a comment, where agreeing is a
correctness requirement — a response passes through both. No behaviour change.
Security
-
Several unbounded-resource paths are closed above: an HTTP/3 connection with no
idle bound, QUIC handshakes with no concurrency cap or deadline, h3 request
streams with no per-connection cap, and HTTP/2
connections held forever atkeep_alive_ms = 0. Each let one peer retain a
max_connectionspermit — and in the h3 stream case a task and a parser state
per stream — until the process ended. Deployments that enablehttp3or set
keep_alive_ms = 0are the ones affected. -
churust-auth's examples now compare credentials with
churust_core::secure_compare.BearerandBasichand the credential to a
closure the application writes, so the comparison is the application's to get
right, and every example demonstrated==— which returns at the first
differing byte and leaks how much of a guess was correct.Jwtwas never
affected: it verifies throughjsonwebtoken, which compares in constant time.
Crates
churust-core· docschurust-macros· docschurust-auth· docschurust-client· docschurust-compression· docschurust-cors· docschurust-json· docschurust-lab· docschurust-logging· docschurust-openapi· docschurust-ratelimit· docschurust-redis· docschurust-templates· docschurust· docs
What's Changed
- docs: add mdBook user guide and GitHub Pages deploy by @davthecodercom in #6
- docs: add From Ktor to Churust side-by-side guide by @davthecodercom in #7
- fix: honour server knobs on every transport, not just TCP by @davthecodercom in #8
- release: Churust 0.3.3 by @davthecodercom in #9
Full Changelog: v0.3.2...v0.3.3