Hi, HAProxy 3.0.24 was released on 2026/06/25. It added 101 new commits after version 3.0.23.
This large maintenance release addresses bugs across many subsystems. Many of them were never encountered and were only found by AI-assisted scans. The most notable areas covered are the HTTP/1 parser hardening, extensive DNS/resolver fixes, QUIC and HTTP/3 correctness improvements, multiplexer draining bugs, Lua and applet issues, cache correctness, dictionary locking bugs, SSL/TLS fixes, server connection management, and health check correctness. Users are encouraged to upgrade. The HTTP/1 parser received several hardening fixes around Upgrade header handling. All "h2c" values in Upgrade headers are now filtered out during parsing to avoid protocol confusion. Additionally, the handling of Upgrade headers when making outgoing requests was reworked to properly sanitize and deduplicate upgrade tokens. The WebSocket protocol is no longer masked when multiple protocols are listed in the Upgrade header. A large number of bugs were fixed in the DNS resolver code. A validation bug was fixed in resolv_read_name() where a name compression pointer was not validated against the response bounds, potentially causing out-of-bounds memory reads. A bugs in resolv_dn_label_to_str() were fixed: the label size check used an incorrect comparison. A related fix prevents appending garbage past the domain name. When the xprt prepare_srv callback fails for a newly resolved server, HAProxy now waits a bit before retrying to avoid busy-looping. A memory leak of a sockaddr structure was fixed in the dns_session_init() error path. Authority record parsing was relaxed to be more tolerant of label sizes. Several minor error-path leaks and dangling pointer issues were also fixed. The expression error is now reported in the do-resolve() action parser. Query ID generation was switched to a better PRNG for unpredictability. Several bugs were fixed in the QUIC multiplexer. An issue in qcs_http_reset_buf() caused the entire buffer to be reset unconditionally instead of draining only the requested amount, which could discard unsent data when a filter was registered on the data forwarding stage. A bug was fixed in qcc_is_dead() which was not accounting for detached streams (streams whose upper layer had been removed but still had data to emit), allowing connections to be freed prematurely with remaining data. The QUIC connection inactivity timeout was being refreshed on every I/O callback execution even when no actual send or receive had occurred; the timeout is now only refreshed when I/O progress is made, which helps detect and close stuck connections more reliably. Various smaller fixes addressed ODCID lookup from derived values, Initial packet length values, and ACK range pool deallocation. The CUBIC congestion control algorithm received two fixes: the congestion window was not reset in slow start on persistent congestion, and an ECONNREFUSED error on the receive side was not handled, causing the QUIC connection to stall instead of being properly terminated. Two HTTP/3 improvements were made. Client push streams are now properly rejected, as they are not supported. CANCEL_PUSH frames sent by the client are also now rejected as required. Two fixes were applied to the hq-interop HTTP/0.9 transcoder: streams with unknown payload length were incorrectly closed with RESET_STREAM instead of FIN and responses exceeding the supported size are now properly rejected. Numerous bugs were fixed in the QPACK decoder. Index calculation in debug functions was incorrect. A potential null-pointer dereference was fixed in qpack_dht_insert() when the memory pool was exhausted. A sign bit mask was wrong in qpack_decode_fs_pfx(), potentially causing incorrect header value decoding. Error handling in huff_dec() called from qpack_decode_fs() was not properly propagating the error. Two multiplexers were affected by the same draining bug: when trying to send data to a server whose stream was closed (in error or half-closed state), the mux claimed to have consumed the buffered data without actually draining them. This means the upper stream would keep trying to send these data in a loop, potentially triggering the watchdog. The FCGI and H2 multiplexers were both fixed. In the FCGI mux, an integer overflow was fixed: the unsigned 16-bit integer used for the record length overflowed to 0 when the record length was 65535 and padding was non-zero, causing the state machine to skip consuming buffer data and parse the remaining bytes as a new FCGI record header. This could only happen for unknown records or for STDOUT records received after the end of the response. CVE-2026-55203 was assigned to this overflow. It must be noted however that the assigned severity is excessively high relative to the actual exploitability: triggering this bug requires the FCGI backend server itself to be compromised and to craft a malicious response with exactly those values, which is well outside a normal threat model where the backend is trusted. Additionally, stream ID 0 was not rejected for application records as required by the FCGI specification, potentially causing incorrect demultiplexing. A minor fix corrected the computation of contiguous data in the demux buffer by using a relative offset. The H1 multiplexer received two fixes. The connection/upgrade header value is now duplicated before being parsed when building outgoing request headers, as the parsing was modifying the value in-place which could corrupt shared data. The 'h1-case-adjust-file' directive was not resolving a relative file path to an absolute path at parse time, causing failures when multiple configuration files were loaded. A possible failure from strdup() was also left unhandled in one code path. The H2 multiplexer received two minor fixes: padding was not correctly accounted for in connection-level flow control on certain error paths, and the HEADERS frame length was not validated before reading the stream dependency field. Several bugs were fixed in the Lua and applet subsystems. An integer underflow was possible in the Lua cosocket line-read path, causing memory corruption when fewer bytes than expected were received. The HTX data transfer to applets was reworked: data were not consumed from the channel buffer after being copied to the applet's input buffer, and changes to the input buffer were not committed back after sending HTX data. Receives of size 0 were not properly handled, potentially leaving the channel in an inconsistent state. Lua was also not filtering CR, LF, and NUL characters from HTTP headers, allowing header injection. Multiple bugs were fixed in the cache subsystem. The primary hash was not always being verified in get_secondary_entry(), which could cause incorrect cache hits. A refcount leak was fixed for missed secondary entries where the reference count was incremented on lookup but not decremented when the matching entry was not found. Cache tree iteration was broken due to an incorrect traversal condition. The maxage value was being overwritten instead of copied during parsing of cache-control directives. Two locking bugs were fixed in the dictionary subsystem. The read lock was not held while incrementing the refcount in dict_insert(), creating a race condition where two concurrent inserts could corrupt the refcount of a shared entry. The write lock was not held while decrementing the refcount in dict_entry_unref(), allowing a concurrent lookup to observe an inconsistent state. A further race was fixed on insert collision where the refcount was not properly updated when two threads inserted the same key simultaneously. Several SSL/TLS fixes are included. With TLS 1.3 0-RTT, the early data buffer was freed when emptied, but more early data could still arrive; the buffer is now reset rather than freed until all early data has been received. The LRU certificate cache lock was not released on error in the SSL certificate generation path. SNI character validation was added to the dynamic certificate generation code to prevent SAN certificate injection through crafted SNI values. The null-terminated servername is now properly used in the SSL hello parser. A memory leak in the ECDSA signature conversion error path was also fixed. The OCSP responder now handles dates that are too far in the future. A few server management bugs were fixed. The server lock was not properly released on failure in cli_parse_set_server(), potentially causing a deadlock when a "set server" CLI command failed. Consistent hash balancing for newly added servers was broken. When L7 retries were configured and the maximum retry count was reached on a reused connection, the stream was silently closed on the client side to let it safely retry the request instead of returning a 502 or 425 error. The SF_SRV_REUSED flag is now ignored in this path so that the error is properly reported to the client. The set-var-fmt action was not properly evaluating empty log-format strings. The Forwarded header parser did not check whether sufficient data remained when reading the nodeport field. The HTTP authentication token checker was not verifying against the whole token, allowing partial matches. The HTTP client CLI now properly destroys its context if it fails to start. Several health check bugs were fixed. When a server configured with tune.max-checks-per-thread was deleted while its check was still queued, a use-after-free would occur; the fix dequeues the check before freeing the server. External checks were not being excluded from the tcpcheck post-config phase, which could cause a NULL pointer dereference. The LDAP health check could read more data than available from the buffer. The agent-check reply parser could read past the buffer end. The error code was not properly reported via errno in chk_report_conn_err(). A NULL pointer dereference was fixed in the HPACK table code: when the memory pool was exhausted, hpack_dht_defrag() could return NULL and be immediately dereferenced, crashing the worker process. CVE-2026-55204 was assigned to this issue. Here again the assigned severity is excessively high: the condition only arises when the process is already out of memory, at which point the allocator failure itself is likely to cause other problems before this specific code path is even reached. The probability of hitting this particular NULL dereference in a production environment is extremely thin, and a crash under memory exhaustion, while undesirable, is not a meaningful security primitive an attacker could reliably control or exploit. A NULL pointer dereference was fixed in the authentication code when an unconfigured password entry (NULL) was accessed during authentication. The log-forward module treated the month field as a signed integer, which could cause incorrect log timestamps when the high bit was set. Finally, a few minor issues were also addressed: the error message for ACL identifier lookup failures incorrectly said "map" instead of "ACL"; the niced_task counter was incremented for the wrong counter; the be2hex sample converter chunk size was not bounded; base64dec() returned NULL for empty input instead of an empty string; the client hello parser had incorrect bounds checks for the handshake length; the addons/51d module was not NUL-terminating headers before passing them to the Trie API; the cluster secret was initialized using only ha_random64() which does not provide sufficient entropy; parameter value validation in get_server_ph_post() had an incorrect comparison; hash calculation was wrong when using hash-type none; and log priority parsing did not check for the end of buffer properly. Some documentation improvements were also included: the documentation now clarifies that a "default" resolver exists, and incorrect init tags were removed from the Lua documentation. Thanks to everyone for your help on this release! Please find the usual URLs below : Site index : https://www.haproxy.org/ Documentation : https://docs.haproxy.org/ Wiki : https://github.com/haproxy/wiki/wiki Discourse : https://discourse.haproxy.org/ Slack channel : https://slack.haproxy.org/ Issue tracker : https://github.com/haproxy/haproxy/issues Q&A from devs : https://github.com/orgs/haproxy/discussions Sources : https://www.haproxy.org/download/3.0/src/ Git repository : https://git.haproxy.org/git/haproxy-3.0.git/ Git Web browsing : https://git.haproxy.org/?p=haproxy-3.0.git Changelog : https://www.haproxy.org/download/3.0/src/CHANGELOG Dataplane API : https://github.com/haproxytech/dataplaneapi/releases/latest Pending bugs : https://www.haproxy.org/l/pending-bugs Reviewed bugs : https://www.haproxy.org/l/reviewed-bugs Code reports : https://www.haproxy.org/l/code-reports Latest builds : https://www.haproxy.org/l/dev-packages --- Complete changelog : Amaury Denoyelle (11): BUG/MEDIUM: mux_quic: adjust qcc_is_dead() to account detached streams BUG/MINOR: quic: fix ODCID lookup from derived value BUG/MEDIUM: h3: reject client push stream BUG/MINOR: h3: reject client CANCEL_PUSH frame BUG/MINOR: quic: reject packet too short for HP decryption BUG/MINOR: quic: fix Initial length value in sent packets BUG/MINOR: server: fix add server with consistent hash balancing BUG/MINOR: hq-interop: reject too big content BUG/MINOR: hq-interop: prevent reset if missing content-length MINOR: mux-quic: adapt return value of qcc_decode_qcs() BUG/MINOR: mux_quic: refresh timeout only if I/O performed Christian Ruppert (1): REGTESTS: Don't try to use real nameservers for testcases Christopher Faulet (30): MINOR: htx: Add htx_move_blks() to move blocks from a message to another BUG/MEDIUM: applet: Fix transfer of HTX data to the applet BUG/MINOR: httpclient-cli: Destroy http-client context if failing to start it BUG/MEDIUM: h1: Skip all h2c values from Upgrade headers during parsing BUG/MINOR: h1: Don't mask websocket protocol if multiple protocols used BUG/MEDIUM: applet: Properly handle receives of size 0 BUG/MEDIUM: resolvers: Fix test on dn label size in resolv_dn_label_to_str() BUG/MEDIUM: ssl-gencert: Unlock LRU cache if failing to generate certificate BUG/MEDIUM: dict: hold lock while decrementing refcount in dict_entry_unref BUG/MINOR: tcpchecks: Limit parsing of agent-check reply to the buffer BUG/MEDIUM: hlua: Fix integer underflow when receiving line from lua cosocket BUG/MEDIUM: mux-fcgi: reject stream ID 0 for application records MINOR: http: Add function to remove all occurrences of a value in a header MINOR: h1: Add a H1M flag to specify a non-empty 'Upgrade:' header was parsed BUG/MEDIUM: h1-htx: Sanitize parsing to properly handle upgrade requests BUG/MINOR: mux-fcgi: Use relative offset to compute contig data in demux buf BUG/MINOR: tcpcheck: Check LDAP response to not read more data than available Revert "BUG/MEDIUM: dns: fix long loops in additional records parse on name failure" BUG/MINOR: mux-h2: Count padding for connection flow control on error path BUG/MINOR: cache: Fix copy of value when parsing maxage BUG/MEDIUM: mux-h1: Dup connection/upgrade value to parse it when making headers BUG/MINOR: applet: Commit changes into input buffer after sending HTX data BUG/MEDIUM: vars: Properly eval set-var-fmt action for emtpy log-format string BUG/MEDIUM: check: Skip tcpcheck post-config for external checks MINOR: check: Don't dump buffers state in check traces for external checks BUG/MEDIUM: http-ana: Don't ignore L7 retry errors BUG/MINOR: mux-h1: Properly resolve file path for 'h1-case-adjust-file' BUG/MEDIUM: mux-fcgi: Truly drain outgoing HTX data when the stream is closed BUG/MEDIUM: mux-h2: Truly drain outgoing HTX data when the stream is closed BUG/MEDIUM: mux-quic: Drain the given amount of data in qcs_http_reset_buf() Frederic Lecaille (10): BUG/MINOR: qpack: Fix index calculation in debug functions BUG/MINOR: qpack: fix potential null-pointer dereference in qpack_dht_insert() CLEANUP: qpack: fix copy-paste typo in value Huffman debug string BUG/MINOR: qpack: fix sign bit mask in qpack_decode_fs_pfx() CLEANUP: qpack: fix copy-paste typo in value Huffman debug string for WLN BUG/MINOR: qpack: fix huff_dec() error handling in qpack_decode_fs() CLEANUP: qpack: move encoded macros to qpack-t.h to avoid duplication BUG/MEDIUM: quic: handle ECONNREFUSED on RX side BUG/MINOR: quic: fix ack range node pool_free call passing wrong pointer type BUG/MEDIUM: quic: reset cwnd in slow_start on persistent congestion (cubic) Ilia Shipitsin (2): BUILD: 51d.c: cleanup, fix preprocessor ifdefs BUG/MINOR: mux_h1: handle a possible strdup() failure Maxime Henrion (1): BUG/MINOR: cache: fix cache tree iteration Olivier Houchard (4): BUG/MEDIUM: resolvers: Wait a bit before calling the xprt prepare_srv BUG/MINOR: tasks: Increase the right niced_task counter BUG/MEDIUM: checks: Dequeue checks on purge BUG/MEDIUM: ssl: Don't free the early data buffer too early Remi Tricot-Le Breton (1): BUG/MINOR: ocsp: Manage date too far away in the future Thayne McCombs (1): DOC: lua: remove incorrect init tags Tristan Madani (2): BUG/MINOR: hpack-tbl: add missing NULL check after hpack_dht_defrag() BUG/MEDIUM: mux-fcgi: fix uint16_t overflow in drl += drp William Lallemand (2): BUG/MEDIUM: auth: fix unconfigured password NULL deref BUG/MINOR: ssl-gencert: validate SNI characters to prevent SAN certificate injection Willy Tarreau (36): BUG/MEDIUM: dict: hold read lock while incrementing refcount in dict_insert BUG/MINOR: backend: correct parameter value validation in get_server_ph_post() BUG/MINOR: config/dns: properly fail on duplicate nameserver name detection BUG/MEDIUM: dns: fix long loops in additional records parse on name failure BUG/MEDIUM: resolvers: fix name compression pointer validation in resolv_read_name() BUG/MEDIUM: dns: fix memory leak of sockaddr in dns_session_init() error path CLEANUP: proxy: fix tiny mistakes in parse error messages BUG/MEDIUM: server/cli: unlock server lock on failure in cli_parse_set_server BUG/MINOR: dns: fix dangling dgram pointer on dns_dgram_init() failure path BUG/MINOR: resolvers: report the expression error in the do-resolve() action parser BUG/MINOR: resolvers: fix leaked dgram and dns_ring struct in parse_resolve_conf() BUG/MINOR: check: properly report errno in chk_report_conn_err() BUG/MINOR: jwt: fix possible memory leak in convert_ecdsa_sig() error path DOC: config: further clarify that resolvers "default" exists BUG/MINOR: backend: fix balance hash calculation when using hash-type none BUG/MEDIUM: log-forward: make sure the month is unsigned BUG/MEDIUM: cache: fix a refcount leak for missed secondary entries BUG/MINOR: resolvers: fix room for trailing zero in resolv_dn_label_to_str() BUG/MINOR: resolvers: fix risk of appending garbage past the domain name BUG/MINOR: mux-h2: validate HEADERS frame length before reading stream dep BUG/MINOR: log: look for the end of priority before the end of the buffer BUG/MINOR: dict: fix refcount race on insert collision BUG/MINOR: init: use more than ha_random64() for the cluster secret BUG/MINOR: sample: limit the be2hex converter's chunk size BUG/MEDIUM: cache: always verify the primary hash in get_secondary_entry() BUG/MINOR: resolvers: relax size checks in authority record parsing BUG/MINOR: http-fetch: check against the whole token in get_http_auth() BUG/MINOR: http-ext: always check remaining data when reading rfc7239 nodeport BUG/MINOR: base64: return empty string for empty input in base64dec() BUG/MINOR: payload: fix the handshake length bounds check smp_client_hello_parse() BUG/MINOR: ssl-hello: make use of the null-terminated servername BUG/MINOR: resolvers: switch to a better PRNG for query IDs BUG/MINOR: addons/51d: NUL-terminate headers before passing them to Trie API BUG/MINOR: hlua: prevent Lua from passing CR/LF/NUL in HTTP headers CLEANUP: sessions: simplify the sess_priv_conns pool name BUG/MINOR: acl: report "ACL" not "map" in ACL ID lookup failures -- Christopher Faulet

