Skip to content

Releases: samooth/httpx.zig

Release list

0.2.3

Choose a tag to compare

@samooth samooth released this 28 Sep 23:15
0.2.3

Four defects that made 0.2.2 unusable from a real client, all found by
driving the server with curl and openssl s_client instead of with httpx.
None of them were visible to the test suite, which talks to httpx with
httpx and therefore agreed with the bugs.

TLS server unusable from real clients. The ServerHello carried a
server_name acknowledgement that OpenSSL rejects in a TLS 1.3
ServerHello, aborting with illegal_parameter before any key exchange.
Every real client sends SNI, so curl, browsers and openssl were all cut
off. The acknowledgement is now omitted.

TLS server broken for every modern client. The server advertised h2
through ALPN while there is no HTTP/2 framing behind it, so it negotiated
a protocol it could not speak. curl https://host/ failed; the same
request with --http1.1 worked. The ALPN preference is now derived from
what the server can actually serve.

Template arena never freed. Renderer.render declared
ctx: *const Context and mutated the arena through it. LLVM emits
readonly from the parameter type, so Engine.render could prove the
arena's node list was still null and fold defer ctx.deinit() away.
Every node pushed during a render leaked. A comptime guard now makes the
invariant a compile error.

Double free in getOrCompile. An errdefer stayed armed after the
source buffer's ownership passed to the cache, so an error path freed a
buffer the cache still held.

Also in this release

  • Interop tests that drive a live server with curl and openssl s_client,
    plus a CI job on Linux and macOS. They are opt-in via HTTPX_INTEROP so
    the main matrix stays hermetic.
  • A build step that isolates the template engine tests, which makes a leak
    in that module reproducible in seconds rather than a minute.
  • Docs now record what real clients do and where the gaps are.

Known limitation. X25519MLKEM768 is implemented for outgoing
handshakes only. A server answers with x25519, so a client offering only
the hybrid group is rejected. That is the default posture of OpenSSL 3.5
and later. See docs/reference/interop-status.md.

v0.2.2

Choose a tag to compare

@samooth samooth released this 26 Sep 18:29
0.2.2

First release from the samooth/httpx.zig mirror, based on upstream v0.2.0.

Fix

The template cache borrow was never actually protected. get returned
&entry.ast with the entry stored inline in a StringHashMap, so the pointer
targeted the map's value storage. A concurrent invalidate or put could dangle
it while activeRenders still counted the render as live.

The retirement logic was sound but addressed the wrong thing: it copied the AST
into garbage and deferred ast.deinit(), which protects the AST's heap
buffers — not the map slot, released the moment fetchRemove runs.

Impact: any server rendering templates from more than one thread while
invalidating (hot reload, dev mode, a file watcher) could segfault.

The map now stores heap pointers, so a borrow survives until freeEntry, which
the existing counter already deferred.

Security

  • DNS transaction IDs — now io.random instead of a DefaultPrng seeded
    with millisNow(); a classic cache-poisoning surface.
  • QUIC connection IDs — io.randomSecure. The seed parameter was removed
    because it invited callers to pass a clock, which is how both production sites
    went wrong. RFC 9000 Section 8.2 requires unpredictable CIDs.
  • CSRF tokens — new generateCsrfTokenSecure(io, out), which cannot be
    misused. generateCsrfToken(std.Random, ...) is now test-only: its signature
    invited a non-cryptographic PRNG and a therefore predictable token.
  • entropy_hygiene_test blocks reintroducing DefaultPrng under tls/,
    quic/connection.zig, net/dns.zig and web/middleware/security.zig.

Features

  • X25519MLKEM768 post-quantum key exchange (client side). Offered first, with
    plain X25519 as a fallback so peers without PQ support pick it directly instead
    of forcing a HelloRetryRequest. Not offered over QUIC: the 1216-byte share
    exceeds the Initial packet buffer and needs CRYPTO frame fragmentation
    (RFC 9000 Section 14).

Infrastructure

  • CI runs the suite in ReleaseFast — the only mode that catches
    stack-lifetime bugs, where a slice of a returned frame is harmless in Debug and
    reads garbage in ReleaseFast. Five such defects reached main through that gap.
  • Three latent fd-after-close races fixed in the socks4, socks5 and client pool
    test servers.