This is an automated email from the ASF dual-hosted git repository. shreemaan-abhishek pushed a commit to branch chore/release-3.20.0 in repository https://gitbox.apache.org/repos/asf/apisix.git
commit 6b402ee1b0fc091e6b9d72ff2d03e287c9c167d0 Author: Abhishek Choudhary <[email protected]> AuthorDate: Fri Nov 6 11:50:47 2026 +0545 chore: release 3.20.0 --- .requirements | 2 +- CHANGELOG.md | 62 +++++++++++++++++++++++++++++++++++++ apisix/core/version.lua | 2 +- docs/en/latest/admin-api.json | 2 +- docs/en/latest/config.json | 2 +- docs/en/latest/control-api.json | 4 +-- docs/openapi/admin-api/openapi.yaml | 2 +- docs/zh/latest/config.json | 2 +- 8 files changed, 70 insertions(+), 8 deletions(-) diff --git a/.requirements b/.requirements index 4552899f1..663d155fd 100644 --- a/.requirements +++ b/.requirements @@ -18,4 +18,4 @@ APISIX_PACKAGE_NAME=apisix APISIX_RUNTIME=1.4.2 -APISIX_DASHBOARD_COMMIT=fa2fd0f60f8afffb096476333b9ba63b4c518fa3 +APISIX_DASHBOARD_COMMIT=e5e1457ed972a0dc9634a84ee58bb9aa5a6fa4af diff --git a/CHANGELOG.md b/CHANGELOG.md index 03359db9b..172898973 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ title: Changelog ## Table of Contents +- [3.20.0](#3200) - [3.19.0](#3190) - [3.18.0](#3180) - [3.17.0](#3170) @@ -87,6 +88,67 @@ title: Changelog - [0.7.0](#070) - [0.6.0](#060) +## 3.20.0 + +**The changes marked with :warning: are not backward compatible.** + +### Change + +- :warning: feat!: remove the `mcp-bridge` plugin, which was deprecated in 3.18.0 with a note that it would be removed in a future release. A configuration that still lists `mcp-bridge` under `plugins` has to drop the entry, and `openapi-to-mcp` is the MCP plugin going forward [#14019](https://github.com/apache/apisix/pull/14019) +- :warning: refactor(wolf-rbac): move `header_prefix` to the Route or Service configuration, where it defaults to `X-`, so a Route owns its identity headers and clears `{prefix}UserId`, `{prefix}Username` and `{prefix}Nickname` before any authenticator runs. Move a custom `header_prefix` from each Consumer to every Route or Service using `wolf-rbac` (including inside `multi-auth`) before upgrading, because a Consumer carrying one is now rejected; `appid` (now required), `server` and `ssl [...] +- :warning: fix(oas-validator): verify the TLS certificate of the `spec_url` host. `ssl_verify` now defaults to `true`, so an `https` spec served with a certificate outside the trust store fails the fetch and the route answers 500; set `ssl_verify: false` to keep the old behavior, which is now logged as an opt-out [#14146](https://github.com/apache/apisix/pull/14146) +- :warning: fix(authz-keycloak): require every requested permission to be granted. The plugin asks Keycloak for `response_mode=permissions` and forwards the request only once every requested resource and scope is in the granted list, so a route listing several permissions, a permission with several scopes, or `http_method_as_scope` on a scoped permission now answers 403 when only part of them is granted [#14098](https://github.com/apache/apisix/pull/14098) +- :warning: feat(openid-connect): upgrade `lua-resty-openidc` to 1.9.3 and `lua-resty-session` to 4.2.0, so an introspection failure is reported for what it is: a confirmed inactive token returns 401, an endpoint, transport or response failure returns 503, and a configuration failure returns 500. An introspection response whose `active` claim is missing or is not a boolean is now rejected as an invalid upstream response instead of being read as truthy [#14088](https://github.com/apache/a [...] +- :warning: feat(prometheus): add `service` and `service_id` to all four stream metrics, so `apisix_stream_connection_total`, `apisix_stream_active_connections`, `apisix_stream_status` and `apisix_stream_bandwidth` gain two labels. Update PromQL selectors, recording rules, dashboards and alerts that match those series exactly; both labels are empty for a session that never reached a Stream Route with a Service, so summing over `service` reproduces the previous values [#13992](https://git [...] +- :warning: fix(cli): don't capture openresty stdout in `apisix start`. With `access_log: /dev/stdout`, `apisix start` used to block forever while swallowing the logs, and it now returns once openresty has daemonized. A container whose only command is `apisix start` therefore exits, and should run openresty in the foreground with `daemon off;`, the way the official image entrypoint does [#14087](https://github.com/apache/apisix/pull/14087) +- :warning: fix: return 421 for SNI and Host mismatches on mTLS requests, so an HTTP/2 client that reused a connection opened for another origin can retry on a new connection with the target origin's SNI and client certificate. The mismatch check also moves ahead of the client certificate verification result; other verification failures keep returning 400 [#14013](https://github.com/apache/apisix/pull/14013) +- :warning: fix(router): forward the path the route was matched on whenever nginx normalized the request path, so an upstream receives `/api/x` for `/admin/..%2Fapi/x` matched on `/api/*`, where it used to receive the raw path. A request that differs from `$uri` only by percent-encoding or repeated slashes still forwards the original request line, and a plugin that sets `$upstream_uri` still takes precedence [#14035](https://github.com/apache/apisix/pull/14035) +- :warning: fix(ssl): match `skip_mtls_uri_regex` against the encoded-slash route URI as well, so with `match_uri_encoded_slash: true` the client certificate check is skipped only when the exemption matches both the decoded URI and the URI that keeps `%2F` inside a path segment. An exemption written for the decoded form alone stops applying to a path with an encoded slash [#14108](https://github.com/apache/apisix/pull/14108) +- :warning: fix(jwe-decrypt, forward-auth): clear the forwarded headers on the paths that skip writing them, so a client-supplied value no longer reaches the upstream under `jwe-decrypt`'s `forward_header` on a `strict: false` route without a token, or under `forward-auth`'s `upstream_headers` while the auth service is unavailable with `allow_degradation: true` [#14096](https://github.com/apache/apisix/pull/14096) +- :warning: fix(ai-prompt-guard): inspect embeddings string arrays before forwarding. A batch such as `{"input": ["secret", "hello"]}` skipped inspection; its strings are now joined and checked together, so a configured `deny_patterns` entry rejects a batch that was forwarded before. A recognized request with no selected text keeps being processed [#14073](https://github.com/apache/apisix/pull/14073) +- :warning: fix(openapi-to-mcp): harden the plugin against a hostile document and a hostile caller. Tool arguments are filtered to the parameters the operation declares, so an undeclared header or query key is dropped; the Route's own `headers` are applied last, so a call can no longer replace a credential the Route adds; and a header whose name or value cannot appear in a request header is dropped with a warning [#13959](https://github.com/apache/apisix/pull/13959) + A path parameter is also confined to one path segment: a value containing `/` or `\` is rejected unless the Route sets `allow_encoded_path_separators: true`, and a filled path segment that ends up empty, `.` or `..` is rejected [#14154](https://github.com/apache/apisix/pull/14154) +- :warning: perf(forward-auth): send the authorization request through `ngx_http_ffi_client`, which is now the default and is selected by `plugin_attr.forward-auth.http_client`. The auth service sees a `Connection` header, `Content-Length: 0` on a GET auth request and a different default `User-Agent`; set `http_client: lua-resty-http` to keep the previous exchange [#14144](https://github.com/apache/apisix/pull/14144) + +### Core + +- feat(core): make the readable request header count configurable through `apisix.max_request_headers_readable`, so plugins that read headers via `core.request.headers()`, such as `request-validation`, `opa` and `forward-auth`, can see past the first 100. The default stays `100`, and `0` reads every header [#14136](https://github.com/apache/apisix/pull/14136) +- feat(kubernetes): select clusters explicitly with `discovery_args.cluster_ids`, so an upstream resolves `namespace/name:port_name` to the union of the matching endpoints of the listed clusters, cached per cluster endpoint version [#13991](https://github.com/apache/apisix/pull/13991) +- feat(consul): support `https` consul server addresses. The scheme is picked per address, so one `servers` list can mix `http` and `https`, certificates are verified against `apisix.ssl.ssl_trusted_certificate` by default, and the new `discovery.consul.ssl_verify` turns verification off for https servers [#14018](https://github.com/apache/apisix/pull/14018) +- chore: bump APISIX-Runtime to 1.4.2, which moves OpenResty to 1.31.8.1 (nginx 1.31.8), OpenSSL to 3.5.9, apisix-nginx-module to 1.20.3 and ngx_multi_upstream_module to 1.4.1. This carries the nginx and OpenSSL security fixes released since nginx 1.29.2 and OpenSSL 3.4.1, restores the upstream keepalive wrapping order nginx 1.29.7 changed, fixes `gzip` compression when the plugin's `buffers.size` is below the page size, fixes 500s on multiplexed dubbo upstream connections, and adds the [...] +- chore: bump brotli to 1.2.1 in the CI and dev image install scripts and in the `brotli` plugin build steps [#14138](https://github.com/apache/apisix/pull/14138) +- fix(upstream): verify against `ssl_trusted_certificate` when only `tls.verify` is set. An upstream with `tls.verify: true` and no `tls.ca_certs` was verified against an empty trust store and failed with `unable to get local issuer certificate`; the runtime now loads `*_ssl_trusted_certificate` and the CRL into the upstream SSL context, and `grpc_ssl_trusted_certificate` is rendered so grpcs upstreams get the same trust anchors [#14113](https://github.com/apache/apisix/pull/14113) +- fix(router): keep the query string when `normalize_uri_like_servlet` is on, where every proxied request reached the upstream with the path alone. This also fixes `proxy-mirror`, which reads the same variable [#13982](https://github.com/apache/apisix/pull/13982) +- fix: preserve the `client-control` body limit for gRPC upstreams, where the redirect to `@grpc_pass` dropped the limit the plugin had set [#14036](https://github.com/apache/apisix/pull/14036) +- fix: guard the node version helpers against a resource config that has already been deleted, which raised an attempt to index a nil value [#13986](https://github.com/apache/apisix/pull/13986) +- fix(consul): run the catalog and health watchers as independent timers, so a watcher is never killed while reading a response body and a whole fetch round no longer fails with a bogus `resty.http` error when both indexes change together [#14032](https://github.com/apache/apisix/pull/14032) +- fix(consul): upgrade `lua-resty-consul` to 0.4.0, whose connect puts the scheme, SNI and `ssl_verify` into the connection pool name, so a verifying registry can no longer reuse a connection another registry opened without certificate verification [#14118](https://github.com/apache/apisix/pull/14118) +- fix(kubernetes): fail the whole list when a later endpoints page fails, so the informer re-lists instead of deleting the endpoints of the pages that never loaded and leaving those services on `no valid upstream node` until a restart [#14053](https://github.com/apache/apisix/pull/14053) +- fix(nacos): keep the cached nodes of a service whose refresh failed, where its last successful snapshot was deleted as if the service had been removed from the configuration [#13971](https://github.com/apache/apisix/pull/13971) +- fix(secret): keep secret values out of the `aws` and `vault` error messages, which carried the stored secret string and the raw vault response body into `error.log` [#14049](https://github.com/apache/apisix/pull/14049) + +### Plugins + +- feat: add the `request-decompress` plugin, which inflates a request body sent with `Content-Encoding: gzip` or `deflate`, so plugins that read the body, such as `oas-validator`, `request-validation`, `chaitin-waf` and `body-transformer`, see the plaintext. `core.request.get_body` gains an opt-in `opts.decompress`, and the configured size cap bounds the inflated data as well as the raw read [#14016](https://github.com/apache/apisix/pull/14016) +- feat(openid-connect): map verified claims to upstream request headers through `upstream_headers`, a list of `{header, path}` mappings. Every declared header is reset on each outcome, so it carries the verified claim or nothing [#14030](https://github.com/apache/apisix/pull/14030) +- feat(prometheus): export per-process timers, connection slots and Lua memory as `apisix_nginx_timers`, `apisix_nginx_timers_max`, `apisix_nginx_process_connections`, `apisix_nginx_process_connections_max` and `apisix_nginx_lua_memory_bytes`, so a worker or the privileged agent running out of any of them is visible before it turns into errors [#14034](https://github.com/apache/apisix/pull/14034) +- feat(prometheus): configure the expired metric cleanup interval with `plugin_attr.prometheus.remove_expired_keys_interval`, default 3600 seconds, and upgrade `nginx-lua-prometheus-api7` to 1.1.0 [#13981](https://github.com/apache/apisix/pull/13981) +- feat(chaitin-waf): add `allow_degradation`, default `false`, so a request passes through when the WAF service is unavailable or its detection fails, instead of the service being a single point of failure [#13311](https://github.com/apache/apisix/pull/13311) +- feat(limit-count): report the counting window in `$rate_limiting_info`, with the window type and size, the current window (and the previous window for a sliding window) in millisecond timestamps, the decision and the cost of the request. The four existing fields keep their names and values, and the variable is now encoded from a table, so the field order is no longer fixed [#13996](https://github.com/apache/apisix/pull/13996) +- feat(ai-rate-limiting): expose nested usage fields to `cost_expr` under names that join the parent and child keys with `__`, for example `input_tokens_details__cached_tokens`. Top-level fields keep their plain names, so existing expressions behave the same [#13984](https://github.com/apache/apisix/pull/13984) +- fix(limit-count): give each rule of a `rules` configuration its own delayed-sync queue, timer and limiter, where all rules shared one queue and synced their keys to Redis through whichever rule's `count` and `time_window` started the timer [#14064](https://github.com/apache/apisix/pull/14064) +- fix(limit-count): flush a missing local delta as 0, so a node that only rejects keeps refreshing its cached quota instead of rejecting on a stale sliding-window weight for the rest of the window [#14015](https://github.com/apache/apisix/pull/14015) +- fix(traffic-split): apply the `tls` and `keepalive_pool` of an inline `weighted_upstreams[].upstream`, which were both ignored, so a client certificate is sent, `tls.verify` and `tls.ca_certs` take effect, `tls.client_cert_id` resolves from that upstream, and the connection pool uses the configured settings [#14054](https://github.com/apache/apisix/pull/14054) +- fix(csrf): validate the shape and the fields of the decoded token, so a token that decodes to a JSON primitive is rejected with 401 instead of raising a Lua error and answering 500 [#14025](https://github.com/apache/apisix/pull/14025) +- fix(oas-validator): release a remote spec that no request has used for `spec_url_ttl` seconds, through the new `inactive` option of `core.lrucache`, so a route whose `spec_url` changes over time stops growing worker memory on every change until the process is OOM-killed [#14061](https://github.com/apache/apisix/pull/14061) +- fix(ai-proxy): count the Bedrock cache-read and cache-write tokens in `prompt_tokens` and expose both counters to the LLM logging variables, in streaming and non-streaming responses [#14044](https://github.com/apache/apisix/pull/14044) +- fix(ai-proxy): include the Anthropic cache tokens in prompt usage and accumulate them across `message_start` and `message_delta`, so prompt caching no longer reports a cache hit ratio above 100% and no longer undercounts the tokens that token-based rate limits and metrics are built on [#14028](https://github.com/apache/apisix/pull/14028) +- fix(syslog): return when the logger fails to initialize, where the already built error fell through to indexing a nil logger and raised a Lua error inside the log phase batch processor [#13970](https://github.com/apache/apisix/pull/13970) +- fix(syslog): separate the error cause from the message with `": "`, so the batch processor stops logging run-together text such as `failed to log messagebuffer overflow` [#13974](https://github.com/apache/apisix/pull/13974) +- fix(prometheus): bump `nginx-lua-prometheus-api7` to 1.1.1, which updates a pending metric increment in place instead of allocating a table per `incr()`, so the latency histograms stop allocating dozens of short-lived tables per request [#14150](https://github.com/apache/apisix/pull/14150) +- perf(limit-count): cache the parsed templates of `core.utils.resolve_var` in an LRU, cache `local` policy limiters by their resolved count, window and window type, and cache the response header names, so a templated `key` or `count` stops redoing configuration work on every request. `resolve_var` gains a `no_cache` argument for templates built per request, which `proxy-rewrite` passes for a header value carrying `regex_uri` captures [#14141](https://github.com/apache/apisix/pull/14141) +- perf(request-id): read only the configured request and response header instead of building every header, and generate v4 ids through a hex table. `core.request.set_header(name, value)` and `add_header(name, value)`, the forms without `ctx`, now also update the cached header table, which a later `core.request.header(s)` read used to miss [#14140](https://github.com/apache/apisix/pull/14140) + ## 3.19.0 **The changes marked with :warning: are not backward compatible.** diff --git a/apisix/core/version.lua b/apisix/core/version.lua index 2f6b77207..d28bc0eb9 100644 --- a/apisix/core/version.lua +++ b/apisix/core/version.lua @@ -20,5 +20,5 @@ -- @module core.version return { - VERSION = "3.19.0" + VERSION = "3.20.0" } diff --git a/docs/en/latest/admin-api.json b/docs/en/latest/admin-api.json index 04856a22d..53024c204 100644 --- a/docs/en/latest/admin-api.json +++ b/docs/en/latest/admin-api.json @@ -3,7 +3,7 @@ "info": { "title": "Apache APISIX Admin API", "description": "The **Apache APISIX Admin API** is a RESTful interface for managing all APISIX gateway resources — routes, upstreams, services, consumers, SSL certificates, plugins, and more.\n\nAPISIX Admin API allows you to dynamically configure the API gateway without restarts. Accepted changes propagate to workers asynchronously.\n\nUnless API key authentication is explicitly disabled with `deployment.admin.admin_key_required: false`, endpoints require an **API key**. Pass a key [...] - "version": "3.19.0", + "version": "3.20.0", "license": { "name": "Apache 2.0", "url": "https://www.apache.org/licenses/LICENSE-2.0.html" diff --git a/docs/en/latest/config.json b/docs/en/latest/config.json index e56d30759..a986b507c 100644 --- a/docs/en/latest/config.json +++ b/docs/en/latest/config.json @@ -1,5 +1,5 @@ { - "version": "3.19.0", + "version": "3.20.0", "sidebar": [ { "type": "category", diff --git a/docs/en/latest/control-api.json b/docs/en/latest/control-api.json index 988be5d6a..6f0e2114a 100644 --- a/docs/en/latest/control-api.json +++ b/docs/en/latest/control-api.json @@ -3,7 +3,7 @@ "info": { "title": "APISIX Control API", "description": "Use the APISIX Control API to inspect or control the runtime state of one APISIX instance. It is enabled by default at `http://127.0.0.1:9090`; configure `apisix.enable_control` and `apisix.control` in [`conf/config.yaml`](https://github.com/apache/apisix/blob/master/conf/config.yaml.example). The API does not provide authentication, so keep it on a loopback or otherwise trusted internal interface and never expose it to public traffic.\n\nMost endpoints in this refere [...] - "version": "3.19.0", + "version": "3.20.0", "license": { "name": "Apache 2.0", "url": "https://www.apache.org/licenses/LICENSE-2.0.html" @@ -1477,7 +1477,7 @@ "boot_time": 1608522102, "hostname": "gateway-1", "id": "b7ce1c5c-b1aa-4df7-888a-cbe403f3e948", - "version": "3.19.0", + "version": "3.20.0", "etcd_version": "3.5.0" } } diff --git a/docs/openapi/admin-api/openapi.yaml b/docs/openapi/admin-api/openapi.yaml index 65cae7c43..ba0bb7b33 100644 --- a/docs/openapi/admin-api/openapi.yaml +++ b/docs/openapi/admin-api/openapi.yaml @@ -27,7 +27,7 @@ info: By default, the Admin API binds to `0.0.0.0:9180` and allows clients from `127.0.0.0/24`. The examples use `http://127.0.0.1:9180`; configure the listener and client allowlist in `config.yaml` under `deployment.admin.admin_listen` and `deployment.admin.allow_admin`. To learn how to configure and operate APISIX, see [Get Started](https://apisix.apache.org/docs/apisix/next/getting-started/README/). - version: 3.19.0 + version: 3.20.0 license: name: Apache 2.0 url: https://www.apache.org/licenses/LICENSE-2.0.html diff --git a/docs/zh/latest/config.json b/docs/zh/latest/config.json index 36da7ac93..999731cdb 100644 --- a/docs/zh/latest/config.json +++ b/docs/zh/latest/config.json @@ -1,5 +1,5 @@ { - "version": "3.19.0", + "version": "3.20.0", "sidebar": [ { "type": "category",
