This is an automated email from the ASF dual-hosted git repository.
Yilialinn pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-website.git
The following commit(s) were added to refs/heads/master by this push:
new e61266ad03d docs: add Apache APISIX 3.20.0 release notes (#2149)
e61266ad03d is described below
commit e61266ad03ddfa8f95f9a24bcd6aa4f0ad10b796
Author: Traky Deng <[email protected]>
AuthorDate: Thu Nov 12 17:57:01 2026 +0800
docs: add Apache APISIX 3.20.0 release notes (#2149)
---
.../2026/11/11/release-apache-apisix-3.20.0.md | 330 +++++++++++++++++++++
.../2026/11/11/release-apache-apisix-3.20.0.md | 330 +++++++++++++++++++++
2 files changed, 660 insertions(+)
diff --git a/blog/en/blog/2026/11/11/release-apache-apisix-3.20.0.md
b/blog/en/blog/2026/11/11/release-apache-apisix-3.20.0.md
new file mode 100644
index 00000000000..f6c2db9102e
--- /dev/null
+++ b/blog/en/blog/2026/11/11/release-apache-apisix-3.20.0.md
@@ -0,0 +1,330 @@
+---
+title: "Release Apache APISIX 3.20.0"
+authors:
+ - name: "Abhishek Choudhary"
+ title: "Author"
+ url: "https://github.com/shreemaan-abhishek"
+ image_url: "https://github.com/shreemaan-abhishek.png"
+ - name: "Traky Deng"
+ title: "Technical Writer"
+ url: "https://github.com/kayx23"
+ image_url: "https://github.com/kayx23.png"
+keywords:
+ - Apache APISIX
+ - API Gateway
+ - API Management Platform
+ - New Release
+ - Cloud Native
+description: Apache APISIX 3.20.0 was released on Nov 11, 2026. This release
adds request decompression, multi-cluster Kubernetes discovery, OIDC claim
forwarding, new process metrics, and important security and upgrade changes.
+tags: [Community]
+---
+
+We are glad to present Apache APISIX 3.20.0 with new request-processing,
service-discovery, authentication, rate-limiting, and observability
capabilities.
+
+<!--truncate-->
+
+This release adds request decompression, explicit multi-cluster Kubernetes
discovery, HTTPS Consul discovery, verified OIDC claim forwarding, richer
rate-limit diagnostics, and per-process NGINX metrics. It also upgrades APISIX
Runtime to 1.4.2.
+
+The release contains backward-incompatible security and behavior changes.
Review every applicable upgrade plan before rollout.
+
+## Breaking Changes
+
+APISIX 3.20.0 changes several defaults and compatibility contracts across
plugins, authentication, request handling, metrics, and deployment behavior.
Complete the applicable upgrade plans before rollout.
+
+### The deprecated `mcp-bridge` plugin has been removed
+
+APISIX no longer ships or loads `mcp-bridge`, its local-process transport, or
its supporting MCP broker modules. A configuration that still enables the
plugin fails to load it. `openapi-to-mcp` remains available for exposing HTTP
APIs described by OpenAPI as MCP tools, but it is not a drop-in replacement for
a bridge to an arbitrary stdio MCP server.
+
+**Upgrade plan:** If `mcp-bridge` is listed in `plugins`, remove it before
upgrading. Remove the plugin from every Route, Service, Plugin Config, or
Global Rule that uses it. If the bridged process exposes an HTTP API with an
OpenAPI document, redesign the integration with `openapi-to-mcp`; otherwise run
the MCP server outside APISIX and choose a supported network integration. There
is no compatibility switch that restores `mcp-bridge`.
+
+For more information, see [PR
#14019](https://github.com/apache/apisix/pull/14019).
+
+### `wolf-rbac` moves `header_prefix` from Consumer to Route or Service
+
+`wolf-rbac` now configures `header_prefix` on the Route or Service, including
inside `multi-auth`, so the authentication boundary owns and clears its
identity-header namespace. The default remains `X-`. `appid` is now required on
the Consumer, while `server` and `ssl_verify` remain Consumer fields.
+
+A Consumer with a custom `header_prefix` is rejected. A stored Consumer value
equal to the old default `X-` is removed during validation for compatibility,
but it no longer controls the emitted headers.
+
+**Upgrade plan:** If you use `wolf-rbac`, move each custom Consumer
`header_prefix` to every Route or Service that authenticates with that
Consumer, including `multi-auth` entries. Remove the field from the Consumer
and ensure `appid` is present. Test both successful authentication and a
fallback authentication path with client-supplied identity headers. Routes that
relied on the default can omit the field and continue using `X-`.
+
+For more information, see [PR
#13929](https://github.com/apache/apisix/pull/13929).
+
+### `oas-validator` verifies remote specification certificates by default
+
+When `oas-validator` fetches an HTTPS `spec_url`, `ssl_verify` now defaults to
`true`. A certificate outside the configured trust store, or a hostname
mismatch, prevents the specification from loading and the Route returns 500.
This also prevents credentials in `spec_url_request_headers` from being exposed
to an impersonated specification host.
+
+**Upgrade plan:** If you use an HTTPS `spec_url`, verify that its certificate
chain and hostname are trusted by APISIX before upgrading. Add the issuing CA
to `apisix.ssl.ssl_trusted_certificate` when necessary, then test the Route
with a cold specification cache. Set `ssl_verify: false` only as an explicit,
risk-accepted compatibility option; APISIX logs that opt-out.
+
+For more information, see [PR
#14146](https://github.com/apache/apisix/pull/14146).
+
+### Encrypted plugin values now carry a self-describing marker
+
+The Admin API now prefixes newly encrypted plugin configuration and plugin
metadata values with `$ENC$v1$`, so a stored value identifies itself as
ciphertext. While `enable_encrypt_fields` is enabled, APISIX decrypts marked
strings before plugins use them or the Admin API returns them. Existing
unmarked encrypted values continue to follow the plugin schema's
`encrypt_fields` declaration.
+
+An earlier APISIX version does not recognize the marker and uses the complete
marked string as a literal value. Once APISIX 3.20.0 saves a resource with the
marker, downgrading without restoring the earlier data can therefore pass
ciphertext to a plugin.
+
+**Upgrade plan:** If you use encrypted plugin fields, back up etcd before
upgrading. During a rolling upgrade, avoid Admin API writes until every
instance runs a version that recognizes `$ENC$v1$`. Keep the complete keyring
available. If you must downgrade after 3.20.0 has saved marked values, restore
the backup taken before the upgrade. There is no compatibility switch that
makes an older version understand the marker.
+
+For more information, see [PR
#14162](https://github.com/apache/apisix/pull/14162).
+
+### Shared gRPC trust anchors can replace an HTTP-level custom CA
+
+For gRPCS upstreams with `tls.verify: true` and no per-Upstream
`tls.ca_certs`, APISIX now renders `grpc_ssl_trusted_certificate` from
`apisix.ssl.ssl_trusted_certificate`. This fixes verification against an empty
trust store. However, the generated server-level directive takes precedence
over a different `grpc_ssl_trusted_certificate` previously injected at the HTTP
level with `http_configuration_snippet`. Adding the directive again through
`http_server_configuration_snippet` is not a f [...]
+
+**Upgrade plan:** If you set a custom gRPC trust file through
`http_configuration_snippet` and also configure
`apisix.ssl.ssl_trusted_certificate`, add the required gRPC CA to the shared
trust file before upgrading, or configure the distinct CA with the affected
Upstream's `tls.ca_certs`. Validate one trusted and one untrusted gRPCS
connection. Deployments without this custom directive require no migration.
+
+For more information, see [PR
#14113](https://github.com/apache/apisix/pull/14113).
+
+### `authz-keycloak` requires every requested permission and scope
+
+The plugin now requests Keycloak's `permissions` response and checks every
configured resource and scope. A request is rejected with 403 when any
requested permission is absent. This corrects cases where a route listing
several permissions, several scopes, or `http_method_as_scope` could be
accepted without the complete grant. Scopes are trimmed before they are sent,
so `res#view, delete` now requests and enforces `delete`; a token-endpoint
response that is not a permissions list returns 503.
+
+**Upgrade plan:** If a Route uses static `permissions`, compare every resource
and scope with the Keycloak authorization policies. If it uses
`lazy_load_paths`, resolve representative request URIs and audit the resources
and scopes returned by the Protection API. When `http_method_as_scope` is
enabled, grant the HTTP method together with the other required scopes. Grant
the complete requested set or narrow the Route configuration, then test a fully
authorized token and tokens missing ind [...]
+
+For more information, see [PR
#14098](https://github.com/apache/apisix/pull/14098).
+
+### OpenID Connect introspection distinguishes invalid tokens from service
failures
+
+The `openid-connect` plugin now uses `lua-resty-openidc` 1.9.3 and
`lua-resty-session` 4.2.0. An explicitly inactive token returns 401 with an
`invalid_token` challenge. An unavailable endpoint, transport failure, timeout,
or malformed response returns 503 without that challenge, while a configuration
failure returns 500. The `active` claim must be a boolean; a missing or
non-boolean value is treated as an invalid introspection response.
+
+The dependency upgrade also propagates session-start and session-destroy
failures that were previously ignored. In an authorization-code flow that
stores sessions in Redis, an unavailable session store can stop session
initialization or access-token retrieval, and a failed session destroy prevents
the logout redirect.
+
+**Upgrade plan:** If clients or monitors consume introspection errors, update
them to distinguish 401, 503, and 500. Ensure the authorization server returns
a boolean `active` claim and verify one inactive-token case, one
endpoint-failure case, and one valid token. If the authorization-code flow sets
`session.storage: redis`, verify Redis availability and test login, token
refresh, and logout before upgrading. There is no setting that restores the
previous all-401 mapping or makes sessio [...]
+
+For more information, see [PR
#14088](https://github.com/apache/apisix/pull/14088).
+
+### Stream Prometheus metrics gain `service` and `service_id` labels
+
+`apisix_stream_connection_total`, `apisix_stream_active_connections`,
`apisix_stream_status`, and `apisix_stream_bandwidth` now include `service` and
`service_id`. Both are empty when a session never reaches a Stream Route with a
Service. With `prefer_name: true`, `service` contains the Service name while
`service_id` retains the ID. Per-Service active-connection and bandwidth
accounting starts with the first session, including a session that ends before
the next `refresh_interval`.
+
+**Upgrade plan:** If you query these four metrics, update exact label sets,
recording rules, alerts, dashboards, and remote-write consumers before a
mixed-version rollout. Use an aggregation such as `sum without (service,
service_id) (...)` when you need the pre-3.20 series shape, or retain the
labels for per-Service visibility. Test sessions with a Service and unmatched
or early-failing sessions. There is no option to remove the new labels.
+
+For more information, see [PR
#13992](https://github.com/apache/apisix/pull/13992) and [PR
#14101](https://github.com/apache/apisix/pull/14101).
+
+### `apisix start` no longer blocks while capturing OpenResty stdout
+
+The CLI now starts OpenResty without capturing its stdout. This prevents
`apisix start` from waiting forever and buffering logs when `access_log` points
to `/dev/stdout`. As a consequence, a container whose only foreground command
is `apisix start` exits after OpenResty daemonizes.
+
+**Upgrade plan:** If a container or process supervisor relies on `apisix
start` remaining in the foreground, run OpenResty with `daemon off;`, following
the official image entrypoint, or supervise the daemon explicitly. If stdout
logging is enabled, verify that the supervisor still collects the inherited
stream. There is no CLI option to restore the accidental blocking behavior.
+
+For more information, see [PR
#14087](https://github.com/apache/apisix/pull/14087).
+
+### mTLS SNI and Host mismatches return 421
+
+For an mTLS-enabled host, APISIX now returns `421 Misdirected Request` when
the request Host differs from the TLS SNI, allowing an HTTP/2 client to open a
connection for the intended origin. On the normal access path, APISIX compares
Host and SNI before checking the client certificate. If the connection's SSL
object configures `skip_mtls_uri_regex` and the URI is not exempt, APISIX
checks the certificate first and can return 400 before the comparison. A failed
TLS handshake ends the requ [...]
+
+**Upgrade plan:** If clients, tests, or monitoring expect 400 for an mTLS
SNI/Host mismatch, accept 421 and allow the client to retry on a new connection
with the target origin's SNI and certificate. Keep handling certificate
failures separately: they can still fail the handshake or return 400, and a
non-exempt URI under `skip_mtls_uri_regex` can be rejected before the Host/SNI
comparison. There is no setting that restores the old mismatch status.
+
+For more information, see [PR
#14013](https://github.com/apache/apisix/pull/14013).
+
+### Upstreams receive the normalized path used for routing
+
+When NGINX normalizes a path beyond percent-decoding or merging repeated
slashes, APISIX now forwards the path the router matched instead of the raw
request line. This prevents a request containing dot segments or equivalent
normalization from being authorized on one path and forwarded as another.
Because the forwarded `$uri` is decoded, `/a/./b%2Fc` reaches the upstream as
`/a/b/c`. A plugin-set `$upstream_uri` still takes precedence, and
encoding-only or repeated-slash differences cont [...]
+
+**Upgrade plan:** If an upstream intentionally depends on raw dot segments or
other NGINX-normalized path syntax, test those requests against 3.20 and move
any required transformation into an explicit rewrite. Security and routing
policies should use the normalized path. If the original request URI must be
forwarded temporarily, configure `proxy-rewrite.use_real_request_uri_unsafe:
true` only on the affected Route and treat it as a risk-accepted compatibility
option because it bypasses U [...]
+
+For more information, see [PR
#14035](https://github.com/apache/apisix/pull/14035).
+
+### Encoded-slash routes tighten mTLS exemptions
+
+When `apisix.match_uri_encoded_slash` is enabled, `client.skip_mtls_uri_regex`
must match both the decoded URI and the route URI that retains `%2F` within a
segment. An exemption written only for the decoded form no longer lets an
encoded-slash request bypass client-certificate verification.
+
+**Upgrade plan:** If both settings are used, audit each exemption against
decoded and `%2F`-preserving forms. Extend only the patterns that are intended
to exempt the encoded-slash route; for example, `^/open(?:/|%2F).*` covers both
`/open/foo` and `/open%2Ffoo`. Test the plain path, an encoded-slash path
without a certificate, and a certified request. Disabling
`match_uri_encoded_slash` restores the older routing model but changes route
matching globally.
+
+For more information, see [PR
#14108](https://github.com/apache/apisix/pull/14108).
+
+### Authentication plugins clear untrusted forwarded headers on skip paths
+
+`jwe-decrypt` now removes its `forward_header` before processing, including
when `strict: false` lets a tokenless request continue. `forward-auth` removes
every configured `upstream_headers` value when the authorization service is
unavailable and `allow_degradation: true`. Client-supplied values therefore no
longer reach the upstream on paths where APISIX has no trusted value.
+
+**Upgrade plan:** If an upstream intentionally consumed a client-provided
header with one of these protected names during a skip or degradation path,
move that input to a different, explicitly untrusted header. Test `jwe-decrypt`
with a missing token and `strict: false`, and test `forward-auth` with an
unavailable authorization service and `allow_degradation: true`. There is no
option to preserve the spoofable header behavior.
+
+For more information, see [PR
#14096](https://github.com/apache/apisix/pull/14096).
+
+### `ai-prompt-guard` inspects string-array Embeddings input
+
+For OpenAI Embeddings requests, the plugin now joins every string in an
`input` array and checks the combined text. `allow_patterns` applies to that
combined value and any `deny_patterns` match rejects the batch. Arrays of token
IDs remain uninspected because APISIX does not decode them.
+
+**Upgrade plan:** If `ai-prompt-guard` protects an Embeddings endpoint, test
representative batched string inputs and adjust the allow and deny expressions
to the combined-text behavior. Split a batch when each item needs an
independent policy decision. There is no option to restore the previous
omission for string arrays.
+
+For more information, see [PR
#14073](https://github.com/apache/apisix/pull/14073).
+
+### `openapi-to-mcp` applies stricter trust boundaries and size limits
+
+Tool arguments are now filtered to parameters declared by the operation.
Route-configured headers take precedence, unsafe or connection-level headers
are dropped, and undeclared query keys no longer reach the API. A stored Route
is rejected if a configured header name contains `:`, whitespace, or a value
containing CR or LF. When `allowed_origins` is set, it governs MCP browser
requests. Otherwise, an Origin must match a literal Route host with the same
scheme and effective port; only a [...]
+
+The plugin also defaults to a 1 MiB `max_response_body_size`, a 4 MiB
`max_document_size`, and a 50,000-node `max_expanded_nodes` budget. Path
parameters cannot contain `/` or `\` unless `allow_encoded_path_separators:
true`; empty and dot components remain forbidden.
+
+**Upgrade plan:** If you use `openapi-to-mcp`, test every tool whose caller
previously supplied undeclared parameters or special headers and declare
legitimate parameters in the OpenAPI document. For browser clients that send an
Origin, verify that the default same-origin check matches the Route's literal
host, scheme, and effective port; configure `allowed_origins` only for
additional intended origins. If the OpenAPI document uses off-origin `$ref`
documents, configure `allowed_ref_host [...]
+
+For more information, see [PR
#13959](https://github.com/apache/apisix/pull/13959) and [PR
#14154](https://github.com/apache/apisix/pull/14154).
+
+### `forward-auth` uses the FFI HTTP client by default
+
+Authorization requests now use `ngx_http_ffi_client` by default. Compared with
`lua-resty-http`, the request carries a `Connection` header, a GET carries
`Content-Length: 0`, no default `User-Agent` is added, and the timeout bounds
the complete response read. A runtime without the FFI module fails the
authorization request instead of silently switching clients.
+
+**Upgrade plan:** If the authorization service validates exact request
framing, update it for the new headers and test GET, POST, keepalive, timeout,
and TLS behavior. If a custom runtime does not include the compatible FFI
client module, add the module before upgrading or set
`plugin_attr.forward-auth.http_client: lua-resty-http` in `config.yaml` to
preserve the previous client during migration.
+
+For more information, see [PR
#14144](https://github.com/apache/apisix/pull/14144).
+
+## New Features
+
+APISIX 3.20.0 adds new controls for request bodies, discovery, identity
propagation, rate-limit diagnostics, and process-level capacity monitoring.
+
+### Decompress request bodies before plugin processing
+
+The new `request-decompress` plugin inflates `gzip` and `deflate` request
bodies so validation, transformation, WAF, and logging plugins can inspect
plaintext. It rejects unsupported encodings with 415, malformed compressed data
with 400, and raw or inflated bodies above `max_req_body_size` with 413.
Concatenated gzip members are supported and bounded cumulatively.
+
+By default the upstream receives the decompressed body. Set
`forward_compressed: true` when plugins should inspect plaintext but the
upstream should retain a compressed representation. The plugin then restores
the original bytes when no earlier plugin changed the body, or recompresses the
modified body before proxying.
+
+The default `max_req_body_size` is 1 MiB. `request-decompress` runs at
priority 2850, before `hmac-auth` at its default priority, so
`hmac-auth.validate_request_body` hashes the decompressed body. If clients sign
the compressed bytes, raise the `hmac-auth` priority above 2850. Decompression
also occurs before authentication at the default order; keep both the APISIX
client-body limit and the plugin limit bounded for untrusted clients. With
`forward_compressed: true`, log-phase plugins re [...]
+
+```json
+{
+ "plugins": {
+ "request-decompress": {
+ "max_req_body_size": 4194304,
+ "forward_compressed": false
+ },
+ "request-validation": {
+ "body_schema": {"type": "object"}
+ }
+ }
+}
+```
+
+For more information, see [PR
#14016](https://github.com/apache/apisix/pull/14016).
+
+### Select several Kubernetes discovery clusters per upstream
+
+In multi-cluster mode, `discovery_args.cluster_ids` selects the exact clusters
whose endpoints form an upstream. The `service_name` then uses
`namespace/name:port_name`, and APISIX returns the union of matching endpoints
while deduplicating identical `host:port` values. Unknown clusters are rejected
by the Admin API; if stale standalone configuration reaches a data plane,
unknown IDs are skipped and logged.
+
+```json
+{
+ "type": "roundrobin",
+ "discovery_type": "kubernetes",
+ "service_name": "default/orders:http",
+ "discovery_args": {
+ "cluster_ids": ["production-a", "production-b"]
+ }
+}
+```
+
+For more information, see [PR
#13991](https://github.com/apache/apisix/pull/13991).
+
+### Connect to Consul over HTTPS
+
+Consul discovery accepts both `http` and `https` server addresses in one list.
HTTPS certificates are verified by default against
`apisix.ssl.ssl_trusted_certificate`, with SNI derived from each address.
`discovery.consul.ssl_verify: false` disables verification for HTTPS entries
and has no effect on HTTP entries.
+
+For more information, see [PR
#14018](https://github.com/apache/apisix/pull/14018).
+
+### Forward verified OIDC claims as upstream headers
+
+`openid-connect.upstream_headers` maps a verified claim path to an upstream
request header. APISIX clears each declared header on every outcome, preventing
a client from supplying a spoofed value when authentication fails open.
Authorization-code flows prefer ID-token claims and fall back to user info;
bearer flows use the verified token or introspection result. Strings and
numbers are written as text, while arrays and objects are base64-encoded JSON.
+
+```json
+{
+ "upstream_headers": [
+ {"header": "X-Profile-Id", "path": ["profile", "id"]},
+ {"header": "X-Scopes", "path": ["scope"]}
+ ]
+}
+```
+
+For more information, see [PR
#14030](https://github.com/apache/apisix/pull/14030).
+
+### Observe NGINX process capacity with Prometheus
+
+The Prometheus plugin now exports per-process Lua timers, connection-slot
usage, and Lua VM memory through `apisix_nginx_timers`,
`apisix_nginx_timers_max`, `apisix_nginx_process_connections`,
`apisix_nginx_process_connections_max`, and `apisix_nginx_lua_memory_bytes`.
These gauges distinguish workers, the privileged agent, and HTTP or stream
subsystems, helping operators alert before timer, connection, or memory
exhaustion becomes request failure.
+
+For more information, see [PR
#14034](https://github.com/apache/apisix/pull/14034).
+
+### Configure expired Prometheus metric cleanup
+
+`plugin_attr.prometheus.remove_expired_keys_interval` controls how often
expired metric entries are reclaimed. It defaults to 3600 seconds, values above
3600 are capped, and non-positive values disable automatic cleanup. It is
independent of `refresh_interval`. Shorter intervals reclaim shared-dictionary
space sooner at the cost of more frequent scans; after shortening the value on
reload, the next cleanup can still wait for the previous interval because
workers share the cleanup marker.
+
+For more information, see [PR
#13981](https://github.com/apache/apisix/pull/13981).
+
+### Choose fail-open behavior for `chaitin-waf`
+
+`allow_degradation` lets requests continue when Chaitin WAF metadata is
missing, no healthy WAF node is available, or the detection request fails. The
effective default is `false`. A Route-level value overrides plugin metadata,
including an explicit `false`; successful WAF decisions still follow the
configured mode.
+
+Use this option only when availability takes precedence over inspection during
WAF failure, and monitor the response debug headers and warnings because
requests can still wait for the configured timeout before continuing.
+
+For more information, see [PR
#13311](https://github.com/apache/apisix/pull/13311).
+
+### Inspect rate-limit decisions and windows
+
+`limit-count` expands `$rate_limiting_info` with the window type and size,
evaluated timestamp, decision, request cost, current window, previous sliding
window, and delayed-sync snapshot. Existing quota fields retain their names,
but JSON field order is not fixed. Keys containing quotes or backslashes are
now encoded as valid JSON. The same change keeps the local fixed-window end
time stable, so an `ai-rate-limiting` zero-cost check no longer pushes
`X-AI-RateLimit-Reset` later than the [...]
+
+For more information, see [PR
#13996](https://github.com/apache/apisix/pull/13996).
+
+### Use nested AI usage fields in rate-limit expressions
+
+`ai-rate-limiting.cost_expr` can reference nested numeric usage fields by
joining path components with `__`, such as
`input_tokens_details__cached_tokens`. Top-level variables keep their existing
names, and missing values continue to evaluate as zero.
+
+For more information, see [PR
#13984](https://github.com/apache/apisix/pull/13984).
+
+### Read more request headers in plugins
+
+`apisix.max_request_headers_readable` controls how many request headers
plugins can read through `core.request.headers()`. The default remains 100,
while `0` removes the limit. Headers beyond the readable count are still
forwarded to the upstream; raise the value only for plugins that must inspect
them and account for the extra per-request work.
+
+For more information, see [PR
#14136](https://github.com/apache/apisix/pull/14136).
+
+## Bug Fixes
+
+APISIX 3.20.0 improves discovery convergence, routing and upstream
correctness, rate-limit state, AI usage accounting, security, validation,
memory safety, and logging reliability.
+
+### Service discovery and configuration lifecycle
+
+- Consul catalog and health watches now run as independent timers, avoiding a
race where one watcher was killed while reading a response and a simultaneous
index change could fail the whole refresh. [PR
#14032](https://github.com/apache/apisix/pull/14032)
+- `lua-resty-consul` 0.4.0 separates connection pools by scheme, SNI, and
`ssl_verify`, preventing reuse across incompatible TLS settings. [PR
#14118](https://github.com/apache/apisix/pull/14118)
+- Kubernetes and Tars discovery now store each service snapshot in one
shared-dictionary key. Each worker caches the value or an unknown-service miss
for one second after reading it, then reads shared memory again on the next
lookup after expiry. This removes the separate version key that could leave a
worker with a torn or stale pair; the interval is not measured from the
discovery update. [PR #14167](https://github.com/apache/apisix/pull/14167)
+- Kubernetes discovery now fails an incomplete paginated list instead of
deleting endpoints from pages that were never loaded. [PR
#14053](https://github.com/apache/apisix/pull/14053)
+- Nacos discovery retains the last successful node snapshot when one service
refresh fails, while still removing a service after its APISIX configuration is
deleted. [PR #13971](https://github.com/apache/apisix/pull/13971)
+- Resource-version helpers now tolerate a configuration deleted during an
update instead of indexing a missing value. [PR
#13986](https://github.com/apache/apisix/pull/13986)
+
+### Routing, request limits, and upstream TLS
+
+- With `normalize_uri_like_servlet`, APISIX removes semicolon parameters only
from the path used for route matching. The upstream receives the URI-encoded
pre-servlet path and current query string unless a plugin writes
`$upstream_uri`; that write is a complete override, so APISIX does not append
the query afterward. [PR #13982](https://github.com/apache/apisix/pull/13982)
and [PR #14163](https://github.com/apache/apisix/pull/14163)
+- `client-control` body limits survive the internal redirect to a gRPC
upstream, including request bodies without `Content-Length`. [PR
#14036](https://github.com/apache/apisix/pull/14036)
+- Inline `traffic-split` upstreams now apply their `tls` and `keepalive_pool`
settings, including client certificates, verification, CA certificates,
certificate references, and pool sizing. [PR
#14054](https://github.com/apache/apisix/pull/14054)
+
+### Rate limiting
+
+- Every `limit-count.rules` entry now owns its delayed-sync queue, timer, and
limiter, so one rule cannot flush another rule's keys with the wrong quota or
window. [PR #14064](https://github.com/apache/apisix/pull/14064)
+- A delayed-sync node that only rejects requests now refreshes a quota after
its local delta expires instead of continuing to reject against stale
sliding-window state. [PR #14015](https://github.com/apache/apisix/pull/14015)
+
+### AI Gateway usage accounting
+
+- Bedrock cache-read and cache-write tokens now contribute to normalized
prompt usage and reach the logging variables in streaming and non-streaming
responses. [PR #14044](https://github.com/apache/apisix/pull/14044)
+- Anthropic prompt usage now includes cache-read and cache-creation tokens,
and streaming usage is accumulated across `message_start` and `message_delta`.
This corrects token-based rate limits, metrics, and cache-hit ratios. When an
Anthropic client uses an OpenAI-compatible upstream, buffered and streaming
responses now calculate `input_tokens` as `max(0, prompt_tokens - cache_read -
cache_write)`, avoiding a double-counted cache write. [PR
#14028](https://github.com/apache/apisix/pull/14028)
+
+### Security, validation, and memory safety
+
+- AWS and Vault Secret errors no longer include provider response bodies; logs
retain only the identifiers needed to locate the failed reference. [PR
#14049](https://github.com/apache/apisix/pull/14049)
+- Malformed CSRF token shapes and fields return 401 instead of causing a Lua
error and 500. [PR #14025](https://github.com/apache/apisix/pull/14025)
+- Remote `oas-validator` specifications unused for `spec_url_ttl` seconds are
released, preventing obsolete Route URLs from accumulating compiled
specifications in worker memory. Rebuilding the cache after `spec_url_ttl`
metadata changes also releases the previous cache instead of retaining it for
the worker lifetime. [PR #14061](https://github.com/apache/apisix/pull/14061)
+
+### Syslog reliability
+
+- A syslog logger initialization failure now returns to the batch processor
instead of continuing with a missing logger and raising a Lua error. [PR
#13970](https://github.com/apache/apisix/pull/13970)
+- Syslog error messages now separate the operation from its cause, making
failures readable and searchable. [PR
#13974](https://github.com/apache/apisix/pull/13974)
+
+## Other Changes
+
+The release also updates the supported runtime and reduces per-request
allocation in several hot paths.
+
+- APISIX Runtime is upgraded to 1.4.2, based on OpenResty 1.31.8.1, NGINX
1.31.8, and OpenSSL 3.5.9. It includes current NGINX and OpenSSL security
fixes, the `X25519MLKEM768` TLS 1.3 hybrid group, restored upstream keepalive
integration, a `gzip` small-buffer fix, and fixes for multiplexed Dubbo
upstream connections. [PR #14041](https://github.com/apache/apisix/pull/14041)
[PR #14126](https://github.com/apache/apisix/pull/14126)
+- Brotli build and development scripts now use Brotli 1.2.1. [PR
#14138](https://github.com/apache/apisix/pull/14138)
+- `nginx-lua-prometheus-api7` 1.1.1 updates pending increments in place,
reducing short-lived table allocation for histograms. [PR
#14150](https://github.com/apache/apisix/pull/14150)
+- `limit-count` caches parsed variable templates, local limiters, and
generated quota-header names; `proxy-rewrite` avoids caching templates created
per request. [PR #14141](https://github.com/apache/apisix/pull/14141)
+- `request-id` reads only its configured header and generates UUIDv4 values
through a precomputed hex table. Header setters without an explicit context now
keep the request-header cache coherent. [PR
#14140](https://github.com/apache/apisix/pull/14140)
+
+## Download
+
+To download Apache APISIX 3.20.0, visit the [official release
page](https://apisix.apache.org/downloads/).
+
+## Feedback
+
+If you have questions or encounter problems, open an issue in the [Apache
APISIX repository](https://github.com/apache/apisix/issues).
diff --git a/blog/zh/blog/2026/11/11/release-apache-apisix-3.20.0.md
b/blog/zh/blog/2026/11/11/release-apache-apisix-3.20.0.md
new file mode 100644
index 00000000000..7e212cec67c
--- /dev/null
+++ b/blog/zh/blog/2026/11/11/release-apache-apisix-3.20.0.md
@@ -0,0 +1,330 @@
+---
+title: "Apache APISIX 3.20.0 正式发布"
+authors:
+ - name: "Abhishek Choudhary"
+ title: "Author"
+ url: "https://github.com/shreemaan-abhishek"
+ image_url: "https://github.com/shreemaan-abhishek.png"
+ - name: "Traky Deng"
+ title: "Technical Writer"
+ url: "https://github.com/kayx23"
+ image_url: "https://github.com/kayx23.png"
+keywords:
+ - Apache APISIX
+ - API Gateway
+ - API Management Platform
+ - New Release
+ - Cloud Native
+description: Apache APISIX 3.20.0 于 2026 年 11 月 11 日发布。该版本新增请求解压缩、多集群
Kubernetes 服务发现、OIDC 声明转发和进程级指标,并包含重要的安全与升级变更。
+tags: [Community]
+---
+
+我们很高兴地宣布 Apache APISIX 3.20.0 正式发布。该版本增强了请求处理、服务发现、身份验证、限流和可观测性能力。
+
+<!--truncate-->
+
+本次发布新增请求解压缩、显式选择多个 Kubernetes 集群、Consul HTTPS 服务发现、OIDC 已验证声明转发、更丰富的限流诊断信息和
NGINX 进程级指标,并将 APISIX Runtime 升级至 1.4.2。
+
+该版本包含不向后兼容的安全与行为变更。升级前,请阅读并执行所有与当前部署相关的升级计划。
+
+## 破坏性变更
+
+APISIX 3.20.0 调整了插件、身份验证、请求处理、指标和部署行为中的多项默认值与兼容性约定。发布新版本前,请完成适用于当前部署的升级计划。
+
+### 已移除弃用的 `mcp-bridge` 插件
+
+APISIX 不再包含或加载 `mcp-bridge`、其本地进程传输层及相关 MCP broker
模块。仍启用该插件的配置将无法加载。`openapi-to-mcp` 可继续将 OpenAPI 描述的 HTTP API 暴露为 MCP
工具,但它不能直接替代连接任意 stdio MCP 服务的桥接功能。
+
+**升级计划:** 如果 `plugins` 中仍有 `mcp-bridge`,请在升级前移除该条目,并从所有使用它的
Route、Service、Plugin Config 或 Global Rule 中删除该插件。如果被桥接进程提供带 OpenAPI 文档的 HTTP
API,可重新设计为 `openapi-to-mcp`;否则应在 APISIX 外部运行 MCP 服务并采用受支持的网络集成方式。没有可恢复
`mcp-bridge` 的兼容选项。
+
+更多信息,请参阅 [PR #14019](https://github.com/apache/apisix/pull/14019)。
+
+### `wolf-rbac` 将 `header_prefix` 从 Consumer 移至 Route 或 Service
+
+`wolf-rbac` 现在在 Route 或 Service(包括 `multi-auth` 内部)配置
`header_prefix`,使身份验证边界负责其身份请求头命名空间并在鉴权前清理同名请求头。默认值仍为 `X-`。Consumer 上的 `appid`
现为必填字段,`server` 和 `ssl_verify` 仍属于 Consumer 配置。
+
+带自定义 `header_prefix` 的 Consumer 将被拒绝。为了兼容旧数据,若 Consumer 中保存的是旧默认值
`X-`,校验时会将其移除,但它不再控制输出的请求头。
+
+**升级计划:** 如果使用 `wolf-rbac`,请将每个 Consumer 的自定义 `header_prefix` 移至所有使用该 Consumer
的 Route 或 Service,包括 `multi-auth` 条目;随后从 Consumer 删除该字段并确认存在
`appid`。请测试鉴权成功路径以及带客户端伪造身份请求头的回退鉴权路径。使用默认值的 Route 可省略该字段并继续使用 `X-`。
+
+更多信息,请参阅 [PR #13929](https://github.com/apache/apisix/pull/13929)。
+
+### `oas-validator` 默认校验远程规范的证书
+
+当 `oas-validator` 从 HTTPS `spec_url` 获取规范时,`ssl_verify` 现在默认为
`true`。若证书不受信任或主机名不匹配,规范将无法加载,Route 返回 500。该行为也可避免 `spec_url_request_headers`
中的凭证被发送给伪造的规范服务。
+
+**升级计划:** 如果使用 HTTPS `spec_url`,请在升级前确认其证书链和主机名受 APISIX 信任。必要时将签发 CA 加入
`apisix.ssl.ssl_trusted_certificate`,随后在规范缓存为空时测试该 Route。仅应在明确接受风险的兼容场景中设置
`ssl_verify: false`;APISIX 会记录该退出安全校验的配置。
+
+更多信息,请参阅 [PR #14146](https://github.com/apache/apisix/pull/14146)。
+
+### 加密插件值现包含自描述标记
+
+Admin API 现在会在新加密的插件配置和插件元数据值前添加 `$ENC$v1$`,使存储值可自行标识为密文。启用
`enable_encrypt_fields` 时,APISIX 会在插件使用或 Admin API 返回带标记的字符串前对其解密。已有的无标记密文仍按插件
Schema 中的 `encrypt_fields` 声明处理。
+
+较早版本的 APISIX 无法识别该标记,会将完整的带标记字符串作为普通值使用。因此,APISIX 3.20.0
保存带标记的资源后,若未恢复旧数据便降级,插件可能收到密文。
+
+**升级计划:** 如果使用插件加密字段,请在升级前备份 etcd。滚动升级期间,在所有实例均运行可识别 `$ENC$v1$` 的版本之前,请避免通过
Admin API 写入配置,并保留完整的 keyring。如果 APISIX 3.20.0
已保存带标记的值后必须降级,请恢复升级前的备份。没有能让旧版本识别该标记的兼容开关。
+
+更多信息,请参阅 [PR #14162](https://github.com/apache/apisix/pull/14162)。
+
+### 共享 gRPC 信任锚可能替换 HTTP 级自定义 CA
+
+对于设置 `tls.verify: true` 且未配置每上游 `tls.ca_certs` 的 gRPCS 上游,APISIX 现在会根据
`apisix.ssl.ssl_trusted_certificate` 渲染
`grpc_ssl_trusted_certificate`,从而修复使用空信任存储校验的问题。但新生成的 server 级指令会覆盖此前通过
`http_configuration_snippet` 在 HTTP 级注入并继承的不同 `grpc_ssl_trusted_certificate`。通过
`http_server_configuration_snippet` 再次添加该指令也不能作为回退方案,因为 NGINX 会拒绝同一上下文中的重复值。
+
+**升级计划:** 如果同时配置了 `apisix.ssl.ssl_trusted_certificate`,并通过
`http_configuration_snippet` 设置了自定义 gRPC 信任文件,请在升级前将所需 gRPC CA
加入共享信任文件;如果相关上游需要独立的信任集合,则在该 Upstream 中配置 `tls.ca_certs`。请分别验证一次受信任和不受信任的 gRPCS
连接。未使用该自定义指令的部署无需迁移。
+
+更多信息,请参阅 [PR #14113](https://github.com/apache/apisix/pull/14113)。
+
+### `authz-keycloak` 要求授予全部请求的权限和作用域
+
+插件现在请求 Keycloak 返回 `permissions`,并逐一校验配置的资源及作用域。只要缺少任意一项,请求就会返回 403。这修复了 Route
配置多个权限、多个作用域或使用 `http_method_as_scope` 时,未获得完整授权仍可能通过的问题。发送前会去除每个作用域两端的空格,因此
`res#view, delete` 现在会请求并强制校验 `delete`;若令牌端点返回的不是权限列表,则返回 503。
+
+**升级计划:** 如果 Route 使用静态 `permissions`,请对照 Keycloak 授权策略检查每项资源和作用域。如果使用
`lazy_load_paths`,请解析具有代表性的请求 URI,并审核 Protection API 返回的资源和作用域。启用
`http_method_as_scope` 时,还需同时授予 HTTP 方法作用域。请授予完整的请求集合或缩小 Route
配置,随后测试完整授权令牌以及分别缺少单项权限或作用域的令牌。无法恢复仅部分授权即可通过的旧行为。
+
+更多信息,请参阅 [PR #14098](https://github.com/apache/apisix/pull/14098)。
+
+### OpenID Connect 令牌内省会区分无效令牌与服务故障
+
+`openid-connect` 插件升级至 `lua-resty-openidc` 1.9.3 和 `lua-resty-session`
4.2.0。确认不活跃的令牌返回 401 及 `invalid_token` 质询;端点不可用、传输失败、超时或响应格式错误返回 503
且不带该质询;配置错误返回 500。`active` 必须是布尔值,缺失或类型错误会被视为无效的内省响应。
+
+依赖升级还会向上传递此前被忽略的会话启动和销毁错误。在使用 Redis
保存会话的授权码流程中,会话存储不可用可能阻止会话初始化或访问令牌获取;会话销毁失败时也不会继续执行登出重定向。
+
+**升级计划:** 如果客户端或监控系统处理内省错误,请使其区分 401、503 和 500。确认授权服务器返回布尔类型的
`active`,并分别测试不活跃令牌、端点故障和有效令牌。如果授权码流程设置了 `session.storage: redis`,请在升级前确认 Redis
可用,并测试登录、令牌刷新和登出。没有配置项可恢复所有错误均返回 401,或再次静默忽略会话启动和销毁错误。
+
+更多信息,请参阅 [PR #14088](https://github.com/apache/apisix/pull/14088)。
+
+### Stream Prometheus 指标新增 `service` 和 `service_id` 标签
+
+`apisix_stream_connection_total`、`apisix_stream_active_connections`、`apisix_stream_status`
和 `apisix_stream_bandwidth` 现包含 `service` 与 `service_id`。会话未到达绑定 Service 的
Stream Route 时,两者均为空。启用 `prefer_name: true` 时,`service` 为 Service
名称,`service_id` 仍为 ID。按 Service 统计的活动连接数和带宽从首个会话开始计入,包括在下一次 `refresh_interval`
前已结束的会话。
+
+**升级计划:** 如果查询上述四项指标,请在混合版本发布前更新精确标签集合、记录规则、告警、仪表盘和远程写入消费者。如需恢复 3.20
之前的序列形状,可使用 `sum without (service, service_id) (...)` 聚合;也可保留标签以获得按 Service
的可见性。请测试绑定 Service 的会话,以及未匹配或提前失败的会话。无法通过配置移除新标签。
+
+更多信息,请参阅 [PR #13992](https://github.com/apache/apisix/pull/13992) 和 [PR
#14101](https://github.com/apache/apisix/pull/14101)。
+
+### `apisix start` 不再因捕获 OpenResty 标准输出而阻塞
+
+CLI 启动 OpenResty 时不再捕获标准输出,避免 `access_log` 指向 `/dev/stdout` 时 `apisix start`
永久等待并缓存日志。因此,仅以 `apisix start` 作为前台命令的容器会在 OpenResty 守护进程化后退出。
+
+**升级计划:** 如果容器或进程管理器依赖 `apisix start` 保持前台运行,请按照官方镜像入口使用 `daemon off;` 启动
OpenResty,或显式管理守护进程。启用标准输出日志时,请验证管理器仍能收集继承的输出流。没有恢复意外阻塞行为的 CLI 选项。
+
+更多信息,请参阅 [PR #14087](https://github.com/apache/apisix/pull/14087)。
+
+### mTLS SNI 与 Host 不匹配时返回 421
+
+对于启用 mTLS 的主机,当请求 Host 与 TLS SNI 不同时,APISIX 现在返回 `421 Misdirected Request`,使
HTTP/2 客户端可为目标源重新建立连接。在通常的 access 阶段路径中,APISIX 会先比较 Host 与 SNI,再检查客户端证书。如果当前连接的
SSL 对象配置了 `skip_mtls_uri_regex`,且 URI 未被豁免,APISIX 会先检查证书,并可能在比较 Host 与 SNI 前返回
400。TLS 握手失败时,请求会在这两项 access 阶段检查前结束。
+
+**升级计划:** 如果客户端、测试或监控预期 mTLS SNI/Host 不匹配返回 400,请改为接受 421,并允许客户端以目标源的 SNI
和证书重试新连接。请继续单独处理证书错误:此类错误仍可能导致 TLS 握手失败或返回 400;使用 `skip_mtls_uri_regex` 时,未豁免的
URI 也可能在 Host/SNI 比较前被拒绝。没有恢复旧不匹配状态码的配置项。
+
+更多信息,请参阅 [PR #14013](https://github.com/apache/apisix/pull/14013)。
+
+### 上游会收到路由匹配所使用的规范化路径
+
+当 NGINX 对路径执行了超过百分号解码或合并重复斜杠的规范化时,APISIX
现在会向上游转发路由器实际匹配的路径,而不是原始请求行。这可避免请求在一个路径上通过鉴权,却以另一条路径转发。由于转发的 `$uri`
已解码,`/a/./b%2Fc` 到达上游时会变为 `/a/b/c`。插件设置的 `$upstream_uri`
仍优先;仅编码形式或重复斜杠不同的请求仍保留原始请求行。
+
+**升级计划:** 如果上游有意依赖原始点路径段或其他会被 NGINX 规范化的语法,请在 3.20
上测试这些请求,并将确有需要的转换改为显式重写。安全与路由策略应基于规范化路径。如果迁移期间必须转发原始请求 URI,请仅在受影响的 Route 上配置
`proxy-rewrite.use_real_request_uri_unsafe: true`,并将其视为明确接受风险的兼容选项,因为它会绕过 URI
规范化。
+
+更多信息,请参阅 [PR #14035](https://github.com/apache/apisix/pull/14035)。
+
+### 带编码斜杠的路由会收紧 mTLS 豁免条件
+
+启用 `apisix.match_uri_encoded_slash` 时,`client.skip_mtls_uri_regex` 必须同时匹配解码后的
URI 和在路径段中保留 `%2F` 的路由 URI。仅匹配解码形式的规则,不再允许带编码斜杠的请求跳过客户端证书校验。
+
+**升级计划:** 如果同时使用这两项配置,请针对解码形式和保留 `%2F`
的形式审核每条豁免规则。仅为确实需要豁免的编码斜杠路由扩展表达式;例如,`^/open(?:/|%2F).*` 可同时覆盖 `/open/foo` 和
`/open%2Ffoo`。请测试普通路径、无证书编码斜杠路径和带有效证书的请求。关闭 `match_uri_encoded_slash`
可恢复旧路由模型,但会全局改变路由匹配方式。
+
+更多信息,请参阅 [PR #14108](https://github.com/apache/apisix/pull/14108)。
+
+### 身份验证插件会在跳过路径中清理不可信转发请求头
+
+`jwe-decrypt` 现在会在处理前移除 `forward_header`,包括 `strict: false`
放行无令牌请求时。`forward-auth` 在授权服务不可用且 `allow_degradation: true` 时,会移除所有配置的
`upstream_headers`。因此,当 APISIX 没有可信值时,客户端传入的同名值不会再到达上游。
+
+**升级计划:** 如果上游曾在跳过或降级路径中有意读取这些受保护名称的客户端请求头,请将该输入迁移到另一个明确标记为不可信的请求头。请测试缺少令牌且设置
`strict: false` 的 `jwe-decrypt`,并测试授权服务不可用且设置 `allow_degradation: true` 的
`forward-auth`。无法保留可伪造的旧请求头行为。
+
+更多信息,请参阅 [PR #14096](https://github.com/apache/apisix/pull/14096)。
+
+### `ai-prompt-guard` 会检查 Embeddings 字符串数组输入
+
+对于 OpenAI Embeddings 请求,插件会拼接 `input` 数组中的所有字符串并检查组合文本。`allow_patterns`
针对组合值生效,任意 `deny_patterns` 命中都会拒绝整批请求。由于 APISIX 不解码 token ID,token ID 数组仍不会被检查。
+
+**升级计划:** 如果 `ai-prompt-guard` 保护 Embeddings
端点,请测试具有代表性的批量字符串输入,并根据组合文本行为调整允许与拒绝表达式。如需逐项独立判断策略,请拆分批次。没有恢复忽略字符串数组旧行为的配置项。
+
+更多信息,请参阅 [PR #14073](https://github.com/apache/apisix/pull/14073)。
+
+### `openapi-to-mcp` 采用更严格的信任边界和大小限制
+
+工具参数现在只会保留操作声明的参数;Route 配置的请求头优先,不安全或连接级请求头会被丢弃,未声明的查询参数也不会传给 API。若已保存 Route
的请求头名称包含 `:` 或空白,或者请求头值包含 CR 或 LF,该 Route 会被拒绝。设置 `allowed_origins` 时,浏览器 MCP
请求按该列表校验。未设置时,Origin 必须以相同 scheme 和有效端口匹配 Route 的固定主机名;仅当 Route
没有固定主机名时(通配符主机名不算固定主机名),才会接受回环请求上的回环 Origin。外部 `$ref` 仅允许访问 `openapi_url` 同源地址和
`allowed_ref_hosts`;SSE 会话绑定至创建它的 Route 和 Consumer。
+
+插件默认将 `max_response_body_size` 设为 1 MiB、`max_document_size` 设为 4
MiB、`max_expanded_nodes` 设为 50,000。路径参数默认不能包含 `/` 或 `\`;仅在设置
`allow_encoded_path_separators: true` 时允许,空路径段及 `.`、`..` 路径段始终被拒绝。
+
+**升级计划:** 如果使用 `openapi-to-mcp`,请测试所有曾由调用方传入未声明参数或特殊请求头的工具,并在 OpenAPI
文档中声明合法参数。浏览器客户端发送 Origin 时,请确认默认同源校验能匹配 Route 的固定主机名、scheme 和有效端口;仅为额外的预期来源配置
`allowed_origins`。OpenAPI 文档使用跨源 `$ref` 时,请配置 `allowed_ref_hosts`。只有在评估 worker
内存影响后才提高三项大小限制。如果 API 确实将编码分隔符作为数据处理,请启用
`allow_encoded_path_separators`,并确认上游不会将其解码为路径语法。没有统一的旧版兼容模式,必须逐项配置实际使用的信任边界。
+
+更多信息,请参阅 [PR #13959](https://github.com/apache/apisix/pull/13959) 和 [PR
#14154](https://github.com/apache/apisix/pull/14154)。
+
+### `forward-auth` 默认使用 FFI HTTP 客户端
+
+授权请求现在默认使用 `ngx_http_ffi_client`。与 `lua-resty-http` 相比,请求会携带 `Connection`
请求头,GET 会携带 `Content-Length: 0`,不会添加默认 `User-Agent`,且超时覆盖完整响应读取过程。缺少 FFI
模块的自定义运行时会使授权请求失败,不会静默回退客户端。
+
+**升级计划:** 如果授权服务严格校验请求格式,请适配新请求头,并测试 GET、POST、长连接、超时和 TLS 行为。如果自定义运行时不包含兼容的
FFI 客户端模块,请在升级前加入该模块;也可在 `config.yaml` 中设置
`plugin_attr.forward-auth.http_client: lua-resty-http`,在迁移期间保留旧客户端。
+
+更多信息,请参阅 [PR #14144](https://github.com/apache/apisix/pull/14144)。
+
+## 新功能
+
+Apache APISIX 3.20.0 为请求体、服务发现、身份信息转发、限流诊断和进程容量监控增加了新的控制能力。
+
+### 在插件处理前解压缩请求体
+
+新的 `request-decompress` 插件会解压 `gzip` 和 `deflate` 请求体,使校验、转换、WAF
和日志插件能够检查明文。插件对不支持的编码返回 415,对损坏的压缩数据返回 400,对原始或解压后超过 `max_req_body_size` 的请求体返回
413;多个连续 gzip member 也会按总大小限制。
+
+默认情况下,上游接收解压后的请求体。设置 `forward_compressed: true`
后,插件仍检查明文,但向上游发送压缩形式;若其他插件未修改请求体,则恢复原始字节,否则在代理前重新压缩修改后的内容。
+
+`max_req_body_size` 默认为 1 MiB。`request-decompress` 的优先级为 2850,默认早于 `hmac-auth`
执行,因此 `hmac-auth.validate_request_body` 会对解压后的请求体计算摘要。如果客户端对压缩字节签名,请将
`hmac-auth` 的优先级提高到 2850 以上。按默认顺序,解压也发生在身份验证之前;对于不可信客户端,应同时限制 APISIX
客户端请求体大小和插件限制。启用 `forward_compressed: true` 时,日志阶段插件读取的是实际发送给上游的请求体形式。
+
+```json
+{
+ "plugins": {
+ "request-decompress": {
+ "max_req_body_size": 4194304,
+ "forward_compressed": false
+ },
+ "request-validation": {
+ "body_schema": {"type": "object"}
+ }
+ }
+}
+```
+
+更多信息,请参阅 [PR #14016](https://github.com/apache/apisix/pull/14016)。
+
+### 为一个上游选择多个 Kubernetes 服务发现集群
+
+在多集群模式下,`discovery_args.cluster_ids` 可精确选择参与上游节点合并的集群。此时 `service_name` 使用
`namespace/name:port_name`,APISIX 返回各集群匹配端点的并集,并去重相同的 `host:port`。Admin API
会拒绝未知集群;若陈旧的独立配置到达数据面,则跳过未知 ID 并记录警告。
+
+```json
+{
+ "type": "roundrobin",
+ "discovery_type": "kubernetes",
+ "service_name": "default/orders:http",
+ "discovery_args": {
+ "cluster_ids": ["production-a", "production-b"]
+ }
+}
+```
+
+更多信息,请参阅 [PR #13991](https://github.com/apache/apisix/pull/13991)。
+
+### 通过 HTTPS 连接 Consul
+
+Consul 服务发现的同一 `servers` 列表可同时包含 `http` 和 `https` 地址。默认使用
`apisix.ssl.ssl_trusted_certificate` 验证 HTTPS 证书,并从各地址得到
SNI。`discovery.consul.ssl_verify: false` 会关闭 HTTPS 条目的证书校验,对 HTTP 条目无影响。
+
+更多信息,请参阅 [PR #14018](https://github.com/apache/apisix/pull/14018)。
+
+### 将已验证的 OIDC 声明转发为上游请求头
+
+`openid-connect.upstream_headers` 可把已验证的声明路径映射为上游请求头。APISIX
在每种结果下都会先清理所有已声明请求头,避免鉴权失败开放时客户端伪造值。授权码流程优先使用 ID Token 声明,并以用户信息补充;Bearer
流程使用已验证令牌或内省响应。字符串和数字以文本写入,数组与对象编码为 Base64 JSON。
+
+```json
+{
+ "upstream_headers": [
+ {"header": "X-Profile-Id", "path": ["profile", "id"]},
+ {"header": "X-Scopes", "path": ["scope"]}
+ ]
+}
+```
+
+更多信息,请参阅 [PR #14030](https://github.com/apache/apisix/pull/14030)。
+
+### 使用 Prometheus 观察 NGINX 进程容量
+
+Prometheus 插件新增
`apisix_nginx_timers`、`apisix_nginx_timers_max`、`apisix_nginx_process_connections`、`apisix_nginx_process_connections_max`
和 `apisix_nginx_lua_memory_bytes`,用于导出各进程的 Lua 定时器、连接槽用量和 Lua 虚拟机内存。这些指标可区分
worker、特权 agent 以及 HTTP/Stream 子系统,便于在容量耗尽导致请求错误前告警。
+
+更多信息,请参阅 [PR #14034](https://github.com/apache/apisix/pull/14034)。
+
+### 配置过期 Prometheus 指标清理周期
+
+`plugin_attr.prometheus.remove_expired_keys_interval` 控制清理过期指标条目的周期,默认 3600
秒,大于 3600 的值会被限制为 3600,非正值会关闭自动清理。该配置与 `refresh_interval`
相互独立。缩短周期可更快回收共享字典空间,但会增加扫描频率;重新加载配置并缩短该值后,由于各 worker 共享清理标记,下一次清理仍可能等待原有周期结束。
+
+更多信息,请参阅 [PR #13981](https://github.com/apache/apisix/pull/13981)。
+
+### 为 `chaitin-waf` 选择故障开放行为
+
+`allow_degradation` 可在缺少 Chaitin WAF 元数据、无健康 WAF 节点或检测请求失败时继续处理请求。有效默认值为
`false`。Route 级配置会覆盖插件元数据,包括显式的 `false`;成功获得的 WAF 判定仍按 `mode` 执行。
+
+仅当可用性优先于 WAF 故障期间的检查时才应启用该选项,并监控响应调试头和警告日志,因为请求仍可能等待到配置的 WAF 超时时间后才继续。
+
+更多信息,请参阅 [PR #13311](https://github.com/apache/apisix/pull/13311)。
+
+### 查看限流判定和窗口信息
+
+`limit-count` 在 `$rate_limiting_info`
中新增窗口类型与大小、评估时间、判定结果、请求成本、当前窗口、前一个滑动窗口和延迟同步快照。原有配额字段名称保持不变,但 JSON
字段顺序不固定。包含引号或反斜杠的键现在也会编码为有效 JSON。同一变更还会保持本地固定窗口结束时间稳定,因此 `ai-rate-limiting`
的零成本检查不再使 `X-AI-RateLimit-Reset` 晚于计数器实际 TTL。
+
+更多信息,请参阅 [PR #13996](https://github.com/apache/apisix/pull/13996)。
+
+### 在 AI 限流表达式中使用嵌套用量字段
+
+`ai-rate-limiting.cost_expr` 可使用 `__` 连接路径各部分来引用嵌套数字用量字段,例如
`input_tokens_details__cached_tokens`。顶层变量名称不变,缺失值仍按零处理。
+
+更多信息,请参阅 [PR #13984](https://github.com/apache/apisix/pull/13984)。
+
+### 允许插件读取更多请求头
+
+`apisix.max_request_headers_readable` 控制插件通过 `core.request.headers()`
可读取的请求头数量。默认仍为 100,设置为 `0` 可取消限制。超过可读数量的请求头仍会转发至上游;仅应在插件确需检查时提高该值,并评估额外的单请求开销。
+
+更多信息,请参阅 [PR #14136](https://github.com/apache/apisix/pull/14136)。
+
+## 缺陷修复
+
+APISIX 3.20.0 改进了服务发现收敛、路由和上游处理、限流状态、AI 用量统计、安全、校验、内存安全和日志可靠性。
+
+### 服务发现和配置生命周期
+
+- Consul catalog 与 health watch 现在使用独立定时器,避免一个 watcher
在读取响应时被终止,以及两个索引同时变化导致整轮刷新失败。[PR
#14032](https://github.com/apache/apisix/pull/14032)
+- `lua-resty-consul` 0.4.0 按 scheme、SNI 和 `ssl_verify` 隔离连接池,避免在不兼容的 TLS
配置之间复用连接。[PR #14118](https://github.com/apache/apisix/pull/14118)
+- Kubernetes 和 Tars 服务发现现在会用单个共享字典键保存每个服务的快照。每个 worker 读取后会将该值或未知服务结果缓存 1
秒,并在缓存过期后的下一次查询中重新读取共享内存。该变更移除了可能使 worker 读取到不一致或陈旧数据的独立版本键;这 1
秒并非从服务发现更新时开始计算。[PR #14167](https://github.com/apache/apisix/pull/14167)
+- Kubernetes 服务发现现在会在分页列表未完整加载时使整次 list 失败,不再删除未加载页面中的端点。[PR
#14053](https://github.com/apache/apisix/pull/14053)
+- Nacos 单个服务刷新失败时会保留最近一次成功的节点快照,同时在 APISIX 配置删除服务后仍会清理缓存。[PR
#13971](https://github.com/apache/apisix/pull/13971)
+- 资源版本辅助函数现在可处理更新过程中已被删除的配置,不再索引空值。[PR
#13986](https://github.com/apache/apisix/pull/13986)
+
+### 路由、请求限制和上游 TLS
+
+- 启用 `normalize_uri_like_servlet` 时,APISIX 仅从用于路由匹配的路径中移除分号参数。除非插件写入
`$upstream_uri`,否则上游会收到经过 URI 编码的 servlet 规范化前路径和当前查询字符串;插件写入的值是完整覆盖值,APISIX
不会在其后追加查询字符串。[PR #13982](https://github.com/apache/apisix/pull/13982) 和 [PR
#14163](https://github.com/apache/apisix/pull/14163)
+- `client-control` 的请求体限制在内部跳转至 gRPC 上游后仍然有效,包括不含 `Content-Length` 的请求体。[PR
#14036](https://github.com/apache/apisix/pull/14036)
+- `traffic-split` 内联上游现在会应用 `tls` 和
`keepalive_pool`,包括客户端证书、证书校验、CA、证书引用和连接池大小。[PR
#14054](https://github.com/apache/apisix/pull/14054)
+
+### 限流
+
+- 每个 `limit-count.rules` 条目现在独立拥有延迟同步队列、定时器和
limiter,避免一个规则使用错误的配额或窗口刷新另一个规则的键。[PR
#14064](https://github.com/apache/apisix/pull/14064)
+- 仅产生拒绝请求的延迟同步节点,会在本地增量过期后刷新配额,不再持续使用陈旧的滑动窗口状态拒绝请求。[PR
#14015](https://github.com/apache/apisix/pull/14015)
+
+### AI Gateway 用量统计
+
+- Bedrock 缓存读取和写入 token 现计入标准化 prompt 用量,并在流式及非流式响应中写入日志变量。[PR
#14044](https://github.com/apache/apisix/pull/14044)
+- Anthropic prompt 用量现包含缓存读取和创建 token,流式用量会在 `message_start` 与 `message_delta`
之间累积,从而修正基于 token 的限流、指标和缓存命中率。当 Anthropic 客户端使用 OpenAI 兼容上游时,缓冲响应和流式响应现在都会以
`max(0, prompt_tokens - cache_read - cache_write)` 计算
`input_tokens`,避免重复计算缓存写入量。[PR
#14028](https://github.com/apache/apisix/pull/14028)
+
+### 安全、校验和内存安全
+
+- AWS 和 Vault Secret 错误不再包含提供方响应体,日志仅保留定位失败引用所需的标识符。[PR
#14049](https://github.com/apache/apisix/pull/14049)
+- 格式错误的 CSRF token 结构和字段现在返回 401,而不是触发 Lua 错误并返回 500。[PR
#14025](https://github.com/apache/apisix/pull/14025)
+- 在 `spec_url_ttl` 时间内未使用的远程 `oas-validator` 规范会被释放,避免已删除或已更改 Route 的编译规范持续占用
worker 内存。修改 `spec_url_ttl` 元数据并重建缓存时,也会释放旧缓存,不再使其在整个 worker 生命周期内保持可达。[PR
#14061](https://github.com/apache/apisix/pull/14061)
+
+### Syslog 可靠性
+
+- Syslog logger 初始化失败时会返回 batch processor,不再继续使用不存在的 logger 并触发 Lua 错误。[PR
#13970](https://github.com/apache/apisix/pull/13970)
+- Syslog 错误信息现在会分隔操作描述和错误原因,便于阅读与检索。[PR
#13974](https://github.com/apache/apisix/pull/13974)
+
+## 其他变更
+
+该版本还更新了受支持的运行时,并减少多个热点路径中的单请求内存分配。
+
+- APISIX Runtime 升级至 1.4.2,包含 OpenResty 1.31.8.1、NGINX 1.31.8 和 OpenSSL
3.5.9。该版本包含新的 NGINX 与 OpenSSL 安全修复、TLS 1.3 混合组
`X25519MLKEM768`、上游长连接集成修复、`gzip` 小 buffer 修复和多路复用 Dubbo 上游连接修复。[PR
#14041](https://github.com/apache/apisix/pull/14041) [PR
#14126](https://github.com/apache/apisix/pull/14126)
+- Brotli 构建和开发脚本升级至 Brotli 1.2.1。[PR
#14138](https://github.com/apache/apisix/pull/14138)
+- `nginx-lua-prometheus-api7` 1.1.1 会原地更新待同步增量,减少直方图产生的短生命周期 table 分配。[PR
#14150](https://github.com/apache/apisix/pull/14150)
+- `limit-count` 会缓存解析后的变量模板、本地 limiter 和生成的配额请求头名称;`proxy-rewrite`
不会缓存每个请求动态生成的模板。[PR #14141](https://github.com/apache/apisix/pull/14141)
+- `request-id` 仅读取配置的请求头,并通过预计算十六进制表生成
UUIDv4。未显式传入上下文的请求头设置函数现在也会保持请求头缓存一致。[PR
#14140](https://github.com/apache/apisix/pull/14140)
+
+## 下载
+
+如需下载 Apache APISIX 3.20.0,请访问[官方下载页面](https://apisix.apache.org/zh/downloads/)。
+
+## 反馈
+
+如有疑问或遇到问题,请在 [Apache APISIX 仓库](https://github.com/apache/apisix/issues)中提交
issue。