.. _version_history_1.41.0: 1.41.0 (Pending) ================= Incompatible behavior changes ----------------------------- *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* * **dynamic_forward_proxy**: The dynamic forward proxy cluster now replaces a host with a new host instance when DNS re-resolution changes its address, publishing the change as a regular cluster membership update (old host removed, new host added). Connection pools bound to the old address are therefore drained, instead of existing connections continuing to use the stale address indefinitely. This change can be reverted by setting the runtime guard ``envoy.reloadable_features.dfp_cluster_replace_host_on_address_change`` to ``false``, which restores the previous behavior of mutating the existing host's address in place. * **geoip**: The :ref:`HTTP geoip filter ` now removes every configured geolocation header from the incoming request before the lookup, so a header a client sent is never forwarded upstream as if the lookup had set it. Previously a client-supplied header was kept whenever the lookup had no value for it: an address absent from the database, a database error, a field the database lacks for that address, or a request whose lookup was skipped because the downstream address was not an IP address. Geolocation providers report the headers they are configured to write through the new ``Driver::geoHeaderNames()`` method, which defaults to empty for providers that do not implement it. This behavior can be reverted by setting the runtime guard ``envoy.reloadable_features.geoip_sanitize_geo_headers`` to ``false``. * **router**: A TCP upstream that half closes its connection, such as the origin of a terminated CONNECT tunnel, no longer causes an HTTP/2 or HTTP/3 downstream stream to be reset. The half close is propagated downstream as an end of stream, and the stream is closed once the downstream client half closes its own side. Previously the stream was reset, which discarded any response data that had not yet been written to the downstream connection. This change can be reverted by setting the runtime guard ``envoy.reloadable_features.tcp_tunnel_allow_upstream_half_close`` to ``false``. * **stats**: The tag-extracted names of per-worker stats now keep ``worker`` as a separate name segment when the ``worker_id`` tag is extracted: ``server.worker_1.watchdog_miss`` is now extracted to ``server.worker.watchdog_miss`` instead of ``server.worker_watchdog_miss``, and the same applies to the per-worker stats under ``listener.
.``, ``listener_manager.`` and ``thread_local_cluster_manager.``. Prometheus names are unchanged, because ``.`` and ``_`` are both sanitized to ``_`` there. Metric names change only for sinks that emit the tag-extracted name verbatim, such as ``dog_statsd``, ``metrics_service`` and the OpenTelemetry sink. * **tls**: Upstream TLS connections now default to a maximum TLS version of TLS 1.3 instead of TLS 1.2 when :ref:`tls_maximum_protocol_version ` is not set, which also lets them use the default post-quantum key exchange. When the upstream server supports TLS 1.3, an mTLS client certificate rejected by the upstream is reported as a reset after the connection is established rather than as a connection failure. In that case, ``connect-failure`` HTTP retries and TCP proxy :ref:`max_connect_attempts ` don't retry it on another endpoint, connect-only :ref:`TCP health checks ` pass, and the rejection isn't counted in the ``upstream_cx_connect_fail`` cluster statistic. Outlier detection also records the connection as successful, so the host isn't ejected for consecutive local origin failures caused by TCP proxy connections, or by HTTP requests when :ref:`split_external_local_origin_errors ` is enabled. This behavior can be reverted by setting the runtime guard ``envoy.reloadable_features.default_client_tls_max_version_is_1_3`` to ``false``, or per cluster by setting ``tls_maximum_protocol_version`` to ``TLSv1_2``. * **upstream**: A host health change now re-partitions only the priority that holds the host instead of every priority. EDS clusters are unchanged. This behavior can be reverted by setting the runtime guard ``envoy.reloadable_features.incremental_health_transition`` to ``false``. Minor behavior changes ---------------------- *Changes that may cause incompatibilities for some users, but should not for most* * **build**: GCC builds (``--config=gcc``) now use toolshed's hermetic GCC 13.4.0 toolchain instead of a GCC installed in the build image. GCC uses Envoy's hermetic sysroot (the same as Clang), static libstdc++, and links with hermetic LLD instead of gold. It supports Linux x86_64 only, and ``--config=gcc`` now fails on other platforms instead of building with Clang. The GCC build image, ``--config=libstdc++`` and ``BAZEL_USE_LIBSTDCPP`` are removed. To build with a system GCC, set ``gcc_extension.host(path = "...", toolchains = ["gcc_linux_x86_64"])`` from ``@envoy_toolshed//toolchains/gcc:extensions.bzl`` in the root module. * **cares**: Changes the default value of ``envoy.restart_features.shared_cares_dns_resolver`` to ``false``. Turn this on if ``qcache_max_ttl`` is set in c-ares config and sharing the query cache across DNS clusters is desired. * **config**: xDS resource types that may be subscribed to while a config update is being applied are now paused centrally for every subscription, regardless of its transport (gRPC, REST or filesystem), instead of by each xDS API. As part of this change, Virtual Host Discovery Service (VHDS) requests are now also paused while a Route Discovery Service (RDS) update is being applied, so that the VHDS subscriptions created by the update are batched into a single request. * **contrib**: The Istio peer metadata conversion now takes a peer's app name and version from the ``app`` and ``version`` labels again, and uses ``app.kubernetes.io/name`` and ``app.kubernetes.io/version`` only when those labels are absent or empty. A workload's own stats always use ``app`` and ``version``, so giving the ``app.kubernetes.io/*`` labels precedence made ``source_app``, ``destination_app``, ``source_version`` and ``destination_version`` reported by a peer differ from the values the workload reports for itself, and the same workload showed up under two names. This restores the label precedence Istio proxies used before ``app.kubernetes.io/*`` labels were preferred. * **dns_resolver**: Updated the ``webpki-roots`` and ``rustls-webpki`` crates used by the built-in Hickory DNS resolver dynamic module, so that DNS-over-TLS and DNS-over-HTTPS upstreams no longer trust the compromised eMudhra intermediate CA certificates (see https://emudhra.we-have-a-problem.com/). * **dynamic_modules**: The dynamic modules route specifier no longer records ``specifier_duration`` for a request outside :ref:`runtime_fraction `. * **filter_chain**: The filters of a filter chain that is configured per route in the filter chain filter now get the stats prefix of the enclosing context instead of a hard-coded ``filter_chain.`` prefix. As route specific configurations have no stats prefix today, their stats move from ``filter_chain..*`` to ``.*``. * **http**: The gRPC statistics filter now applies :ref:`replace_dots_in_grpc_service_name ` to allowlisted methods. Previously, this option only affected statistics emitted with ``stats_for_all_methods`` set to ``true``. When enabled with ``individual_method_stats_allowlist``, dots in service names are now replaced with underscores in emitted metric names. Allowlist entries continue to use the original service names, including dots. Dashboards and alerts that refer to the previous metric names need to be updated. * **jwt_authn**: jwt_authn: when a JWT has no ``kid``, keys whose ``x5t#S256`` (or, failing that, ``x5t``) differs from the JWT header's are skipped. Keys without a thumbprint are still tried. This behavior can be reverted by setting runtime guard ``envoy.reloadable_features.jwt_authn_select_key_by_x5t`` to ``false``. * **listener**: A TCP listener that has filter chains from the Filter Chain Discovery Service (FCDS) and no :ref:`filter_chains ` is now updated in place in the same cases as the other listeners, instead of always being drained and replaced. The connections on the FCDS filter chains that the update keeps remain open. This behavior can be temporarily reverted by setting runtime guard ``envoy.reloadable_features.listener_in_place_address_update`` to ``false``. An in place update of any listener also drains its connections on the FCDS filter chains that the update drops. When the ``envoy.reloadable_features.use_connection_event_drain`` runtime guard is disabled, these connections are only closed once the drain completes. * **listener**: A TCP listener update that adds or removes addresses, and otherwise changes at most the filter chains, no longer drains the entire listener. The listener keeps accepting connections on the remaining addresses, and the connections accepted on the removed addresses are drained along with those of the removed filter chains. The new :ref:`total_listener_addresses_draining ` listener manager gauge counts the removed addresses until their drain completes. Such an update of a listener with :ref:`enable_reuse_port ` disabled is also no longer rejected with a duplicate address error. If the listener fails to listen on an added address, it drops the update and keeps serving its addresses instead of removing the listener. See :ref:`filter chain only update ` for the requirements. This behavior can be temporarily reverted by setting runtime guard ``envoy.reloadable_features.listener_in_place_address_update`` to ``false``. * **mcp**: Removed the 10MB (10485760 bytes) upper bound on the MCP filter's :ref:`max_request_body_size ` and the per-route :ref:`max_request_body_size `. Any ``uint32`` value is now accepted; the 8KB default is unchanged. * **on_demand**: On-demand cluster discovery (ODCDS) can now answer repeated requests for a cluster the server has already reported as missing immediately from that remembered answer, instead of starting another discovery and waiting for the response or the discovery timeout. The answer is remembered per config source, only for explicitly requested clusters, and is withdrawn as soon as an update delivers the cluster. Requests answered this way are counted in the new :ref:`cluster_manager.odcds.known_missing_answers ` counter. This behavior is off by default and can be enabled by setting the runtime guard ``envoy.reloadable_features.odcds_missing_cluster_cache`` to ``true``. * **quic**: QUIC listeners now create the connection ID generator factory and apply its worker-routing socket option when the socket factory config is loaded, instead of when the listener config is loaded. This behavior can be reverted by setting runtime guard ``envoy.restart_features.defer_worker_routing_init`` to ``false``. * **rbac**: The default stats tag-extraction rules now extract tags from the RBAC network filter's stats (``.rbac.*``): the rules/shadow rules stat prefix is extracted as the new ``envoy.rbac_rules_prefix`` tag, and the policy name of per-policy stats is extracted as ``envoy.rbac_policy_name``. The policy name is now also extracted for the upstream HTTP RBAC filter's per-policy stats (``cluster..rbac.*``). Previously these tags were only extracted for the downstream HTTP filter's stats. Flat stat names are unchanged; only the tag-extracted names and tags visible to tagged stats sinks (e.g. Prometheus) change. * **spiffe_validator**: The SPIFFE certificate validator now rejects a ``trust_domains`` entry whose ``trust_bundle`` parses but contains no certificate, for example a bundle holding only a CRL. Such a trust domain previously loaded successfully but had no trust anchor, so every handshake for it failed at verification time instead. This matches the behavior of the ``trust_bundles`` (SPIFFE bundle map) configuration, which already rejects a trust domain with no certificate. This change can be reverted by setting the runtime guard ``envoy.reloadable_features.spiffe_validator_reject_empty_trust_bundle`` to ``false``. * **stats**: Added the new ``envoy.upstream_from_zone`` and ``envoy.upstream_to_zone`` tags, extracted by the default stats tag-extraction rules from a cluster's upstream per-zone stats (``cluster..zone...*``). Flat stat names are unchanged. * **tls**: Removed the ``AAA Certificate Services`` and ``emSign ECC Root CA - G3`` roots from the root certificate bundle built into Envoy Mobile. Both roots issued or cross-signed eMudhra intermediate CA certificates whose private keys were compromised (see https://emudhra.we-have-a-problem.com/). Servers whose certificate chains only lead to these roots are no longer trusted by Envoy Mobile when it uses its built-in bundle. * **udp**: Updated the GSO UDP packet writer, used by QUIC and UDP listeners, to send datagrams larger than 1452 bytes through ordinary UDP sends instead of failing with ``EMSGSIZE``. Multi-slice datagrams also use ordinary sends, after flushing any older batch. Blocked writers now return ``EAGAIN`` without accepting new datagrams. The ``internal_buffer_size`` gauge now sums buffered bytes across writers sharing a statistics scope instead of reporting the last writer's value. * **vhds**: When a virtual host discovered through :ref:`VHDS ` has the same :ref:`name ` as a virtual host defined in the RDS :ref:`virtual_hosts `, the VHDS virtual host now replaces the RDS one in the merged route configuration. Previously both virtual hosts were kept. Removing the VHDS virtual host restores the RDS one. This behavior change can be reverted by setting the runtime guard ``envoy.reloadable_features.vhds_overrides_rds_virtual_hosts_with_same_name`` to ``false``. Bug fixes --------- *Changes expected to improve the state of the world and are unlikely to have negative effects* * **alts**: Fixed ALTS connections holding their handshaker service stream until the connection closed. With a gRPC client that limits concurrent calls per connection (gRFC A105), each open ALTS connection used up one slot on the handshaker channels, so once enough connections were open, new handshakes waited until their deadline, blocking the worker thread, and then failed. The handshaker is now released as soon as the handshake completes. * **compressor**: Fixed compression of empty request and response bodies when no ``content-length`` header is present. The compressor now waits for nonempty body data before enabling compression for these streams. This change can be temporarily reverted by setting the runtime guard ``envoy.reloadable_features.compressor_skip_empty_body`` to ``false``. * **contrib**: Fixed a crash in the ``rocketmq_proxy`` network filter (contrib) where a request whose custom header failed to decode (for example a request frame with the RPC flag set, which routes decoding to the response-header path) left the typed custom header null. The request handlers then dereferenced the null header. The handlers now reject such requests with a system error response instead of crashing. * **contrib**: Fixed a use-after-free in the ``kafka_mesh`` network filter (contrib). A Fetch request that finished while its downstream connection was closing delivered its answer through a dispatcher callback that held a raw pointer to the request and called the filter directly, so the callback ran on the freed request and filter. The callback now only runs if the request is still alive, and reaches the filter only while the filter has not abandoned the request. * **contrib**: Fixed a use-after-free in the ``sip_proxy`` network filter (contrib). The per-worker transaction table only marked an ended transaction as deleted and removed it later, so an upstream response with that transaction's branch, arriving on an upstream connection shared with another downstream connection, was delivered to the freed transaction. Ended transactions are now removed right away, an upstream request no longer refers to a transaction that has moved away from it or ended, and a late response after all transactions on an upstream connection have ended is dropped instead of dereferencing a null pointer. * **dns_resolver**: Fixed a data race and cross-thread resolver sharing introduced with the ``envoy.restart_features.shared_cares_dns_resolver`` runtime guard. The shared resolver cache lived on the process wide c-ares DNS resolver factory and was consulted by every caller, without synchronization. Callers that create resolvers on worker thread could therefore race on the cache, and could be handed a resolver bound to another thread's dispatcher. Moving the shared logic into the upstream cluster similar to the default shared resolver eliminates any future issues and avoid locking in the worker thread. Also switch default for ``envoy.restart_features.shared_cares_dns_resolver`` to false. * **dynamic_forward_proxy**: Fixed a race in the dynamic forward proxy cluster where a request arriving while DNS resolution for a cold host was completing could receive a spurious 503 (``UH``) with the successful resolver detail string even though resolution succeeded. The cluster now re-selects the host from its host map after observing a completed cache entry instead of treating a cache hit following a host-map miss as a failure. As part of this change, host selection that completes with no DNS host info now reports ``no_host`` as the response code details instead of an empty string. * **dynamic_modules**: Fixed dynamic module metric IDs. The first metric defined with labels got the same ID as the first one defined without labels, and metrics of different types also shared IDs. A module that recorded a metric with the wrong number of label values, or through the call of another metric type, then changed a different metric instead of getting an error. Every metric now has its own ID. * **dynamic_modules**: Fixed the gRPC status of local replies sent by HTTP dynamic modules. ``send_response`` passed ``grpc-status: 0`` (OK) for every status code, so a gRPC client denied with, for example, a 403 was told the call succeeded. A 200 reply still maps to OK; any other code now maps to the matching gRPC status, as for other local replies (403 to ``PERMISSION_DENIED``, 429 to ``UNAVAILABLE``). * **formatter**: Fixed a performance issue where metadata command operators such as ``%DYNAMIC_METADATA(...)%`` copied the whole metadata each time they produced a typed value, for example for the fields of a :ref:`json_format ` access log. * **golang**: Fixed the Go HTTP filter dropping buffered body data when an asynchronous ``DecodeData`` or ``EncodeData`` returned ``StopAndBuffer`` after the trailers had arrived and the trailers were then handled synchronously. The trailers were forwarded without the buffered body. * **golang**: Fixed the Go HTTP filter forwarding buffered body data with ``end_stream`` set when the filter resumed processing the trailers, for example after an asynchronous ``StopAndBuffer`` returned once the trailers had already arrived. The trailers were then sent after the stream had ended, which asserted in debug builds. * **golang**: Fixed the Go HTTP filter's access log callbacks not seeing the request headers when an earlier filter sent a local reply before the Go filter decoded them. Reading the request headers in ``OnLog`` panicked with an invalid phase error, and request properties such as ``request.referer`` were not found. * **grpc_field_extraction**: Fixed the ``grpc_field_extraction`` filter corrupting streaming gRPC requests when the data chunk completing the first message also carried the head of the next pipelined message. The filter buffers request data internally to extract fields from the first message but never forwarded the buffered leftover bytes once extraction completed, so the remainder of the next message was delivered without its gRPC frame header and the stream was corrupted. The leftover bytes are now forwarded after extraction completes. * **http**: Fixed downstream span finalization to run after ``onStreamComplete`` so filter-set span tags are exported instead of silently dropped. * **http**: grpc: fixed core retry policy conversion so ``retry_back_off.max_interval`` is no longer used as the per-try timeout. A dedicated :ref:`per_try_timeout ` field can now be configured independently. * **http2**: Fixed oghttp2 (``envoy.reloadable_features.http2_use_oghttp2``) dropping trailers when the body exactly exhausted the peer's flow-control window before the trailers were encoded, for example a 65535-byte gRPC response to a client using the default initial window. The stream was ended by an empty DATA frame and the client never received the trailers. This behavior can be reverted by setting the runtime guard ``envoy.reloadable_features.http2_trailers_after_exhausted_window`` to false. * **http2**: Fixed oghttp2 (``envoy.reloadable_features.http2_use_oghttp2``) sending outbound DATA frames as large as the peer's ``SETTINGS_MAX_FRAME_SIZE``, which caused legitimate flow-control ``WINDOW_UPDATE`` traffic from clients that acknowledge data in small increments to be misclassified as a flood (``http2.inbound_window_update_frames_flood``) and the connection to be closed. Outbound DATA frame payloads are now capped at 16 KiB, matching nghttp2. This behavior can be reverted by setting the runtime guard ``envoy.reloadable_features.http2_oghttp2_cap_data_frame_payload`` to false. * **listener**: Fixed a use-after-free during worker shutdown. When a connection handler was destroyed with connections still open, closing those connections could re-enter the handler, e.g. an upstream connection pool reconnecting to an internal listener, and look up the already destroyed internal listener map. This could crash Envoy or make the worker thread spin forever and hang shutdown. * **logging**: Fixed fatal signal backtraces re-entering the logger mutex, which could abort or deadlock when the mutex was already held on the crashing thread or inherited locked across a fork. Native and coroutine backtraces now write directly to stderr in fatal signal handlers, preserving fault descriptions and backtrace headers and stack decoder compatibility without using the logging infrastructure. The alternate signal stack is sized using the runtime ``_SC_SIGSTKSZ`` minimum with headroom for nested fatal signals. * **mcp**: Fixed handling of MCP ``2026-07-28`` requests whose ``params._meta["io.modelcontextprotocol/protocolVersion"]`` is missing or is not a string. The :ref:`MCP filter ` in ``REJECT_NO_MCP`` mode and the ``mcp_json_rest_bridge`` HTTP filter now reject them with HTTP ``400`` and JSON-RPC error ``-32602``. Previously, the MCP filter accepted missing metadata and rejected non-string metadata as a ``-32020`` header mismatch counted in ``header_mismatch``, and the bridge handled requests with an ``MCP-Protocol-Version: 2026-07-28`` header but without valid metadata as legacy requests. * **mcp_json_rest_bridge**: Fixed MCP header Base64 decoding so values with an incomplete Base64 wrapper are treated as literals, and an empty Base64 payload correctly decodes to an empty string. * **mcp_json_rest_bridge**: Fixed the ``mcp_json_rest_bridge`` HTTP filter per-route configuration validation to reject duplicate ``(host, path)`` endpoint configurations instead of silently merging tools and ignoring subsequent ``tool_list_http_rule`` or ``tool_list_local`` settings. * **mcp_json_rest_bridge**: Fixed the ``mcp_json_rest_bridge`` HTTP filter so that a ``POST`` request that ends at the headers, such as an HTTP/1 request with ``Content-Length: 0`` or an HTTP/2 request whose ``HEADERS`` frame carries ``END_STREAM``, is rejected with HTTP ``400 Bad Request`` and a JSON-RPC ``-32700`` parse error, as requests with an empty body already were. Previously, such requests hung until the stream idle timeout. * **mcp_json_rest_bridge**: Fixed the ``mcp_json_rest_bridge`` HTTP filter to respond with HTTP ``404 Not Found`` instead of ``200 OK`` when a stateless MCP ``2026-07-28`` request uses a method that the bridge does not implement, as required by the Streamable HTTP transport. The JSON-RPC error code is unchanged (``-32601``). This also applies to the legacy ``initialize`` and ``notifications/initialized`` methods sent as stateless requests. Requests that use MCP ``2025-11-25`` or earlier still receive ``200 OK``. * **mcp_router**: Added optional ``session_signing_key`` to the ``mcp_router`` HTTP filter. When set, composite session IDs minted by the filter carry an HMAC-SHA256 of the session blob and a presented session whose MAC does not verify is rejected with a 400 error. Previously the session blob was Base64 encoded only, so the ``ENFORCE`` session identity check compared the authenticated request identity against a subject read out of the client-supplied, editable blob, and did not bind the session to the authenticated principal. The ENFORCE integration tests were also fixed: the test helper inserted the subject unencoded, so the parsed subject was always empty and the accept path was never exercised. * **memory**: Fixed jemalloc memory accounting so that reserved memory includes retained pages, matching tcmalloc semantics and preventing underflow in physical memory calculations. * **overload_manager**: Fixed a use-after-free that could crash a worker thread during Envoy shutdown when a TCP listener is configured with :ref:`exact_balance ` and the ``envoy.resource_monitors.global_downstream_max_connections`` overload manager resource monitor is configured. A connection accepted on one worker could be handed off to a different worker via connection balancing while still holding a reference into the *originating* worker's thread-local overload state; because worker threads shut down independently with no ordering guarantee relative to one another, the destination worker could destroy the connection after the originating worker had already torn down its own thread-local state, dereferencing freed memory. The connection's overload state reference is now rebound to the receiving worker's own thread-local state once the connection has settled on its final worker, so it is always valid for as long as the connection itself is. * **quic**: Fixed a use-after-free in HTTP/3 upstream connections after their cluster was removed. The per-cluster QUIC state (including the clock and alarm factory the connections use) was destroyed with the cluster, while the cluster's connection pools were only drained, so a connection with active streams read freed memory on its next write or timer and crashed the process. The state now lives as long as any connection pool using it. * **ratelimit**: Fixed local cluster rate limit sharing when the shared cost of a request is larger than ``max_tokens``, for example when the local cluster has more members than ``max_tokens``. Before this fix, every such request was rejected. Now, a request that fits in the bucket without sharing passes when the bucket holds ``max_tokens`` tokens. The request still consumes its full shared cost, so the bucket can go below zero and the next request waits until the bucket refills. The aggregate rate across the local cluster stays at the configured rate, but the aggregate burst can be one request for each member. Requests that cost more than ``max_tokens`` without sharing remain rejected, and ``max_tokens: 0`` continues to reject every request. This behavior can be temporarily reverted by setting runtime guard ``envoy.reloadable_features.local_ratelimit_local_cluster_preserve_one_request`` to ``false``. * **router**: Fixed a bug in Scoped RDS (SRDS) where a single invalid scope in an update batch aborted processing of the remaining scopes in the push and left already-processed scopes in the main-thread map without propagating them to worker threads. When enabled, valid scopes in a partially rejected push are applied to worker threads while failed scopes are skipped and NACKed. Guarded by runtime guard ``envoy.reloadable_features.srds_partial_acceptance`` (disabled by default). * **router**: Fixed the stats prefix that the router passes to its upstream HTTP filters to keep its trailing dot (``http..``). Upstream HTTP filters that appended their own name to it directly, such as the Lua, compressor and admission control filters, emitted stats under names like ``http.lua.*`` instead of ``http..lua.*``. * **server**: Fixed a deadlock between the two main threads of a hot restart: the parent forwarded UDP/QUIC packets to the child with a blocking send from its main thread, while the child's main thread waited for the parent's replies (stats merge, listen socket hand-off) with a blocking receive and read no forwarded packets meanwhile, so once the child's forwarding socket filled up both processes stopped handling admin, xDS, timers and signals for good. The parent now forwards through a bounded non-blocking queue, the child keeps servicing forwarded packets while it waits and gives up on a parent that stops answering, and the hot restart sockets carry send/receive timeouts. New ``server.hot_restart_udp_forwarding_datagrams``, ``server.hot_restart_udp_forwarding_retries`` and ``server.hot_restart_udp_forwarding_dropped`` statistics report that path. * **server**: Fixed a heap buffer overflow in the hot restart IPC receive path: a datagram whose length prefix is within 8 bytes of ``UINT64_MAX`` wrapped the receive buffer size computation to a near-zero value, causing the next ``recvmsg()`` to overflow the buffer. Oversized datagrams are now dropped and the stream state reset, so both blocking and non-blocking receives continue with the next datagram. * **server**: Fixed the ``server.seconds_until_first_ocsp_response_expiring`` statistic, which kept reporting the expiration of the last OCSP response once no TLS context had an OCSP response, for example after a certificate with an OCSP staple was replaced by one without a staple or its listener was removed. It now drops to 0, its value before the first OCSP response. * **sockets**: Fixed a listener with a ``network_namespace_filepath`` leaving the main thread in that network namespace when setting up its socket failed, for example because the address was already in use. The main thread then kept creating its sockets in that namespace, such as those of other listeners and of xDS and health check connections. Worker threads started afterwards inherited it as well. * **sockets**: Fixed internal listener connections retaining response buffers when a blocked write flush outlived its peer. Closing a user-space socket now wakes its peer's pending write so the connection can finish closing. * **sockets**: sockets: fixed shutdown hangs in the opt-in experimental ``io_uring`` socket interface when a peer stops reading or a partial write races with cancellation, and premature socket close while cancellation completions remain outstanding. The write timeout must be greater than zero when configured. * **tls**: Fixed TLS session resumption across server certificates or filter chains whose names differ but concatenate to the same bytes, for example the DNS SANs ``a.example`` and ``b.example`` versus ``a.exampleb.example``. The session context ID that scopes resumption to a TLS context hashed the common name and subject alternative names of each certificate, and each server name, without their types or lengths. It now hashes the type and length of every field, which changes every session context ID once, so a session established before the upgrade, or by an Envoy instance without this change that shares the session ticket keys, takes a full handshake. This change can be temporarily reverted by setting the runtime guard ``envoy.reloadable_features.unambiguous_tls_session_context_id`` to ``false``. * **tls**: Fixed a bug in the TLS inspector's ``JA4`` fingerprint implementation where GREASE values in the ``signature_algorithms`` extension were included in the ``JA4_c`` hash input, contrary to the `JA4 specification `_. GREASE values in ``signature_algorithms`` are now excluded, matching the treatment already applied to cipher suites, extension type IDs, and supported versions. This behavior can be temporarily reverted by setting the runtime guard ``envoy.reloadable_features.ja4_filter_grease_signature_algorithms`` to ``false``. * **tls**: Fixed a crash when a :ref:`custom TLS certificate selector ` switched a handshake from a TLS context with session ticket keys to one without them, for example a context that the selector built before the keys arrived over SDS. Such a handshake now completes without a session ticket. The selectors that shipped with earlier Envoy releases were not affected, since they only switched to separately built TLS contexts when session resumption was disabled. * **tls**: Fixed a crash when an upstream TLS context with a :ref:`custom certificate selector ` had an invalid ``common_tls_context``, for example an invalid ``key_log`` address range or an unknown static secret. Envoy created the selector from the partly created configuration, so a selector that built TLS contexts while it was created, such as the ``envoy.tls.certificate_selectors.on_demand_secret`` selector prefetching a secret that was already available, read settings that were never set. Envoy now rejects the configuration before it creates the selector. * **udp**: Fixed UDP listeners enabling the ``UDP_GRO`` socket option when :ref:`prefer_gro ` is false or unset. Coalesced datagrams could previously be forwarded as a single datagram or dropped as truncated. * **udp**: Fixed UDP replies to IPv4 clients through IPv6 dual-stack listeners on macOS, including HTTP/3 listeners using the default UDP packet writer. * **udp**: Fixed a hot restart child reading packets from an inherited UDP socket while its listener was still paused. The QUIC listener injects a read event on its listener to process the handshakes it buffered from packets the parent forwarded, and the listener then read the socket the parent was still serving, so the packets of the parent's connections were dequeued by the child and answered with stateless resets until the clients had reconnected. A paused listener now runs its read-ready callback on such an event without reading the socket. * **upstream**: Fixed a crash (SIGSEGV) in load stats reporting and health discovery responses when EDS removed the last host from a locality after active health checking failed. Both reporting paths now skip empty locality groups. Removed config or runtime ------------------------- *Normally occurs at the end of the* :ref:`deprecation period ` * **config**: Removed runtime flag ``envoy.reloadable_features.xds_legacy_delta_skip_subsequent_node`` and legacy code paths. * **decompression**: Removed runtime flag ``envoy.reloadable_features.enable_compression_bomb_protection`` and legacy code paths. * **dynamic_forward_proxy**: Removed runtime flag ``envoy.reloadable_features.dfp_cluster_resolves_hosts`` and legacy code paths. * **dynamic_forward_proxy**: Removed runtime flag ``envoy.reloadable_features.skip_dns_lookup_for_proxied_requests`` and legacy code paths. * **header_mutation**: Removed runtime flag ``envoy.reloadable_features.header_mutation_url_encode_query_params`` and legacy code paths. * **health_check**: Removed runtime flag ``envoy.reloadable_features.health_check_after_cluster_warming`` and legacy code paths. * **http**: Removed runtime flag ``envoy.reloadable_features.codec_client_enable_idle_timer_only_when_connected`` and legacy code paths. * **http**: Removed runtime flag ``envoy.reloadable_features.http1_close_connection_on_zombie_stream_complete`` and legacy code paths. * **http**: Removed runtime flag ``envoy.reloadable_features.allow_multiplexed_upstream_half_close`` and its code paths. HTTP/2 and HTTP/3 upstreams can no longer half close the stream before the downstream: the stream is reset when the response completes before the request, as it is for HTTP/1 upstreams. Independent half close is kept for TCP upstreams, see ``envoy.reloadable_features.tcp_tunnel_allow_upstream_half_close``. * **listener**: Removed runtime flag ``envoy.reloadable_features.strict_stats_matcher_unpacked`` and legacy code paths. * **mcp**: Removed the runtime guard ``envoy.reloadable_features.mcp_filter_use_new_metadata_namespace`` and the legacy code path it guarded. The MCP filter always publishes its dynamic metadata under the ``envoy.filters.http.mcp`` namespace (and the MCP router reads it from there); the legacy ``mcp_proxy`` namespace is no longer available. * **on_demand**: Removed runtime flag ``envoy.reloadable_features.on_demand_cluster_no_recreate_stream`` and legacy code paths. * **proxy_protocol**: Removed runtime flag ``envoy.reloadable_features.proxy_protocol_allow_duplicate_tlvs`` and legacy code paths. * **rbac**: Removed runtime flag ``envoy.reloadable_features.rbac_match_headers_individually`` and legacy code paths. New features ------------ * **access_log**: Added the ``%DOWNSTREAM_PEER_CERT_CHAIN%`` :ref:`substitution formatter` which returns the full downstream peer certificate chain in URL-encoded PEM format. * **ai_protocol_manager**: Added :ref:`external_buffer_config ` to the :ref:`AI Protocol Manager filter ` (alpha), making the store that offloaded payloads are written to a pluggable ``envoy.ai_protocol_manager.external_buffer`` extension. When unset, :ref:`envoy.ai_protocol_manager.external_buffer.in_memory ` is used; it holds the payload in memory and does not bound its size. * **bootstrap**: Added :ref:`runtime_stats_eviction_interval ` to read the stats eviction interval from a runtime key, allowing it to be changed without restarting Envoy. * **config**: YAML configuration now honors the explicit ``!!float`` tag: a scalar such as ``!!float 5000.0`` is loaded as a JSON number instead of a string, so it stays a number inside a ``google.protobuf.Struct`` (e.g. a ``typed_config`` packed as a ``Struct``). Untagged float literals are unchanged and still load as strings. Non-finite values are loaded as the strings ``Infinity``, ``-Infinity`` and ``NaN``. A ``!!float`` scalar that cannot be decoded as a double now fails configuration loading. See :ref:`special YAML usage `. * **contrib**: Added experimental :ref:`Istio HBONE CONNECT wait ` extensions that defer internal connection readiness and application I/O until HTTP/2 CONNECT acceptance. Transient establishment failures retain connection-failure retries; permanent CONNECT rejections terminate HTTP or TCP requests without retry or outlier failure reporting. CONNECT 401 is exposed as an HTTP 403 with the ``UF`` response flag, retaining the original CONNECT status in failure diagnostics. Single and double HBONE origination are supported, including wrapped TLS and Istio's baggage peer-metadata exchange. * **dynamic_modules**: Added :ref:`built_route_cache ` to the dynamic modules route specifier, a per worker cache of the routes a module builds from route templates. A module looks a route up by key with ``lookup_built_route``, also in the Rust SDK, after selecting a template, so a route that is a function of a small key is built once per worker rather than once per request. Templates whose cluster is resolved per request are not cached, and a sampled share of hits set by :ref:`verify_one_in ` is verified against what the module records. With ``config_has_built_route_cache``, ``has_built_route_cache`` in the Rust SDK, a module whose routes rely on the cache can reject a configuration without one. * **dynamic_modules**: Added HTTP filter dynamic module callback ``envoy_dynamic_module_callback_http_drain_connection_upon_completion`` and corresponding C++, Go, and Rust SDK methods to initiate graceful draining of the downstream connection when the current HTTP stream completes. * **dynamic_modules**: Added ``envoy_dynamic_module_callback_cluster_config_use_persistent_host_partitions`` so a cluster module can opt its cluster into :ref:`use_persistent_host_partitions ` during ``envoy_dynamic_module_on_cluster_config_new``, without the field in the cluster's configuration. * **dynamic_modules**: Added ``set_dynamic_metadata_struct`` and ``set_dynamic_typed_metadata`` to dynamic module network filters, listener filters and cluster load balancers. A module can merge a serialized ``google.protobuf.Struct`` into a dynamic metadata namespace or set the typed metadata of a namespace from a serialized ``google.protobuf.Any``. Unlike the HTTP filter callbacks of the same names, they return whether the call succeeded, and the struct setter does not create the namespace for an empty struct. The typed setter also replaces an existing entry instead of merging into it and rejects an ``Any`` without a type URL. The Rust SDK exposes them on ``EnvoyNetworkFilter``, ``EnvoyListenerFilter`` and ``ClusterLbContext``. The C++ SDK exposes them as ``setMetadataStruct`` and ``setTypedMetadata`` on ``NetworkFilterHandle`` and as ``setDynamicMetadataStruct`` and ``setDynamicTypedMetadata`` on ``ListenerFilterHandle``. The Go SDK exposes them as ``SetMetadataStruct`` and ``SetTypedMetadata`` on ``NetworkFilterHandle`` and as ``SetDynamicMetadataStruct`` and ``SetDynamicTypedMetadata`` on ``ListenerFilterHandle``. * **dynamic_modules**: Added ``set_prefix_rewrite_rule`` to the dynamic modules route specifier and its Rust SDK. It records a prefix rewrite that Envoy applies to the path the request carries when the request headers are finalized, the way a configured route applies its :ref:`prefix_rewrite `, so the route it produces is not specific to the request it was built for. Requests for which neither the rule nor the regex rewrite after it yields a path at that point are counted in ``rewrite_not_applied``. * **dynamic_modules**: Added support for implementing a custom TLS certificate selector as a dynamic module through the :ref:`dynamic module TLS certificate selector ` extension. For each handshake the module selects one of the configured certificates or lets Envoy select one. With :ref:`allow_dynamic_certificates ` set, the module can also select certificates that it provides at runtime, and a handshake can wait for one that is not provided yet. Modules can schedule work on the main thread and record metrics. This works for both downstream and upstream TLS contexts. The dynamic certificates that are served are listed by the ``/certs`` admin endpoint and count in the ``server.days_until_first_cert_expiring`` and ``server.seconds_until_first_ocsp_response_expiring`` statistics, and the selector emits :ref:`statistics ` about them. * **dynamic_modules**: Added the ``envoy_dynamic_module_callback_health_checker_set_health_metadata`` callback to the :ref:`dynamic module health checker `. A module can attach string key/value pairs to each health check result, and HDS forwards them to the management server in :ref:`health_metadata `. The pairs are replaced on every check. The ``http_status_code`` key is reserved and rejected. * **dynamic_modules**: Added the filter config handle to ``HttpFilterConfig::on_scheduled`` in the Rust SDK, so that a module can emit the metrics defined on its config from an event scheduled to the main thread, with no stream involved. * **dynamic_modules**: Added the optional ``on_route_specifier_user_data_discard`` hook to the dynamic modules route specifier, ``on_user_data_discard`` in the Rust SDK. Envoy calls it with the user data a module recorded when the decision builds no route to carry it, such as a failed decision, a decision other than ``Unspecified`` or a ``Hit`` from ``lookup_built_route``, so that a module can release what it leased for the route. The ``user_data_discard`` statistic counts the values handed back. * **dynamic_modules**: dynamic modules: added :ref:`use_persistent_host_partitions ` to the dynamic modules cluster. When set, adding, removing, or changing the health of hosts updates only those hosts instead of rebuilding the host set, and workers adopt the result as a snapshot. It requires ``lb_policy: CLUSTER_PROVIDED``. * **dynamic_modules**: dynamic modules: added ``SetFilterStateObject`` and ``GetFilterStateObject`` to the Go SDK HTTP filter handle. A Go module can now store a Go value in filter state and read it back from any of its filters on the same stream, without serializing it. A value that implements ``FilterStateObjectDestroyer`` is notified when Envoy destroys the entry. * **dynamic_modules**: dynamic modules: added ``envoy_dynamic_module_callback_cluster_lb_get_member_update_health_changed_host_count`` and ``envoy_dynamic_module_callback_cluster_lb_get_member_update_health_changed_host``, which report the hosts whose health changed since the previous update a worker applied, so a cluster load balancer can patch its healthy set instead of rereading every host. The host count getters now read the counts without building the host vectors. * **ext_proc**: Added :ref:`max_streamed_body_chunk_size ` and per-route :ref:`max_streamed_body_chunk_size ` to limit the body payload size of individual ``ProcessingRequest`` messages sent in ``STREAMED`` and ``FULL_DUPLEX_STREAMED`` body processing modes. * **formatter**: Added the ``%ENVOY_VERSION%`` :ref:`substitution formatter` which returns the version string of the Envoy binary. * **http**: Added :ref:`per_stream_buffer_limit_bytes ` to the Envoy gRPC client, matching the Google gRPC field of the same name. It sets the buffer limit of each stream, which bounds both the request data buffered for retries and the data buffered before the upstream is ready, above which the stream reports that it is above its write buffer high watermark. Without it, the 64 KiB default applies, so a request or stream that sends more than 64 KiB before the response headers is no longer retried, for example a gRPC access log stream configured with ``grpc_stream_retry_policy``. * **http**: Added downstream watermark callbacks support to the HTTP async client to enable downstream flow control backpressure on sidestream requests. This behavior is disabled by default and can be enabled by setting the runtime guard ``envoy.reloadable_features.async_client_downstream_flow_control2`` to ``true``. * **http**: Added support for configuring :ref:`formatter extensions ` used in early request header mutation values. This allows formatter-specific options such as CEL string functions to be enabled. * **http2**: Added the ``metadata_dropped`` HTTP/2 :ref:`codec stat `. It counts outbound metadata that is dropped because ``allow_metadata`` isn't enabled on the connection. * **jwt_authn**: Added :ref:`allowed_algorithms ` to restrict the signing algorithms a provider accepts. A JWT whose ``alg`` is not listed is rejected before any key is tried. * **matching**: Added the :ref:`URI template input matcher `, which matches a path against a URI template with the same semantics as route :ref:`path_match_policy `. * **mcp_json_rest_bridge**: Added support for configurable cache metadata (``ttlMs`` and ``cacheScope``) in locally-generated stateless ``tools/list`` responses via :ref:`cache_config `. * **mcp_json_rest_bridge**: Added validation of ``Mcp-Param-{name}`` request headers for MCP ``2026-07-28`` tools/call requests, configured per tool with the new :ref:`mcp_param_headers ` field. Each mapping names the header, the literal property path of the argument, and its ``STRING``, ``INTEGER`` or ``BOOLEAN`` type. A missing, unexpected, malformed, or mismatched header is rejected with a ``HeaderMismatch`` error. * **mcp_json_rest_bridge**: Added validation that stateless MCP ``2026-07-28`` requests include ``params._meta["io.modelcontextprotocol/clientCapabilities"]`` as a JSON object. Requests where the field is missing or is not an object are rejected with a ``400`` ``Invalid params`` (``-32602``) JSON-RPC error. Individual capability contents are not validated. * **overload_manager**: Added load shed point ``envoy.load_shed_points.http2_server_reset_stream_on_begin_headers`` that rejects new HTTP/2 streams at the codec level on the initial header frame by sending a ``RST_STREAM(REFUSED_STREAM)`` before allocating stream or header objects. When a ``RST_STREAM`` frame is submitted by this load shed point, the counter ``http2.stream_refused_errors`` will be incremented. * **overload_manager**: Added the new HTTP priority load shed filter :ref:`envoy.filters.http.priority_load_shed `, which maps request header integer ranges to overload manager load shed points. It supports fallback shedding via a default load shed point. * **ratelimit**: Added :ref:`shadow_over_limit ` to the rate limit service descriptor status. When any descriptor sets it, the HTTP, network and Thrift rate limit filters increment the ``shadow_over_limit`` counter, so descriptors over limit in shadow mode can be observed. * **set_filter_state**: Added ``life_span`` configuration parameter to the HTTP set_filter_state. This allows the filter to update filter state with ``Connection``, ``FilterChain`` (the default) or ``Request`` life times. * **set_filter_state**: Added support for using the :ref:`set filter state HTTP filter ` as an upstream HTTP filter, so filter state can be set per upstream attempt from the selected host, for example with ``%UPSTREAM_METADATA(...)%``. * **tracing**: Added the ``tracing.opentelemetry.spans_export_failed`` counter to the OpenTelemetry tracer, which counts spans whose export request to the trace collector could not be sent. * **udp**: Added :ref:`upstream_packet_writer_config ` to support optional GSO batching on upstream UDP sockets. * **upstream**: Added ``cluster_manager.odcds.discovery_attempt``, ``cluster_manager.odcds.discovery_success``, ``cluster_manager.odcds.discovery_missing``, and ``cluster_manager.odcds.discovery_timeout`` counters for :ref:`on-demand cluster discovery `. Each counter increments once per discovery, regardless of how many requests were waiting for that cluster. * **upstream**: Added an :ref:`adaptive LIFO queue policy ` for pending requests. The policy uses FIFO ordering until a connection pool's queue reaches a configured high watermark and LIFO ordering until the size reaches a lower configured watermark. Separate watermarks avoid repeated order changes near the overload boundary. * **upstream**: Extended :ref:`wait_for_warm_on_init ` to all cluster types. Setting it to ``false`` now opts the cluster out of pausing CDS while it warms, so a cluster that warms slowly or indefinitely (for example one waiting on an SDS secret with ``initial_fetch_timeout: 0s``) no longer holds back the CDS ACK and other cluster updates. * **wasm**: Added the ``wasm..memory_size`` and ``wasm..vm_memory_size`` gauges, reporting the linear memory size in bytes of the Wasm VM instances of a runtime and of a plugin. See :ref:`Wasm runtime statistics `. * **watchdog**: Added :ref:`log_level ` to the ``envoy.watchdog.backtrace_action`` watchdog action to make the level of the emitted backtrace logs configurable. The level defaults to critical.