kayx23 commented on code in PR #14168: URL: https://github.com/apache/apisix/pull/14168#discussion_r4490479704
########## CHANGELOG.md: ########## @@ -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 a custom value is now rejected; a Consumer that only carries the old schema default `X-` keeps validating and has the field dropped for compatibility. `appid` (now required), `server` and `ssl_verify` stay on the Consumer [#13929](https://github.com/apache/apisix/pull/13929) +- :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 checks each requested resource and scope against the granted list, so a route listing several permissions, a permission with several scopes, or `http_method_as_scope` on a scoped permission answers 403 unless all of them are granted. Each permission is also sent as `resource#scope1,scope2` with every scope trimmed, so a permission written as `res#view, delete` asks for `delete`, where the untrimmed `" delete"` used to be dropped by Keycloak and never enforced. A token endpoint response that is not a permissions list now returns 503 instead of being read as an allow [#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/apisix/pull/14088) Review Comment: The introspection coalescing details can stay in the plugin docs, but the session/revocation failure change is caller-visible and belongs in this warning. PR #14088 compatibility notes say session-destroy and closed-mode revocation-store failures are now propagated instead of ignored. In the authorization-code flow this can surface an error from logout, session initialization, or access-token retrieval and prevent the logout redirect. Please add the affected scope and upgrade action: if a deployment uses a server-side revocation store in closed fail mode, verify its availability and test login/logout before upgrading. ########## CHANGELOG.md: ########## @@ -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 a custom value is now rejected; a Consumer that only carries the old schema default `X-` keeps validating and has the field dropped for compatibility. `appid` (now required), `server` and `ssl_verify` stay on the Consumer [#13929](https://github.com/apache/apisix/pull/13929) +- :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 checks each requested resource and scope against the granted list, so a route listing several permissions, a permission with several scopes, or `http_method_as_scope` on a scoped permission answers 403 unless all of them are granted. Each permission is also sent as `resource#scope1,scope2` with every scope trimmed, so a permission written as `res#view, delete` asks for `delete`, where the untrimmed `" delete"` used to be dropped by Keycloak and never enforced. A token endpoint response that is not a permissions list now returns 503 instead of being read as an allow [#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/apisix/pull/14088) +- :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 aggregating both of them away, for example `sum without (service, service_id) (...)`, reproduces the previous values [#13992](https://github.com/apache/apisix/pull/13992) +- :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 the upstream receives the path the router matched rather than the raw request line. Because that path is the decoded `$uri`, an encoded delimiter in a request nginx normalized is forwarded decoded: `/a/./b%2Fc` reaches the upstream as `/a/b/c`. 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` an exemption applies only when it 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; extend it to cover the encoded form to keep skipping the check [#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 `jwe-decrypt`'s `forward_header` on a `strict: false` route without a token, and `forward-auth`'s `upstream_headers` while the auth service is unavailable with `allow_degradation: true`, reach the upstream unset, the way the normal paths already handle a header they have no value for [#14096](https://github.com/apache/apisix/pull/14096) +- :warning: fix(ai-prompt-guard): inspect embeddings string arrays. The strings of a batch input are joined and checked together, so `allow_patterns` applies to the combined text and a `deny_patterns` match rejects the batch. Token-ID arrays are left as they are, and a recognized request with no selected text keeps being processed [#14073](https://github.com/apache/apisix/pull/14073) +- :warning: fix(openapi-to-mcp): tighten what a tool call can put into the upstream request. 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 and take precedence; and a header name or value that cannot appear in a request header is dropped with a warning. A Route `headers` entry whose name carries `:` or whitespace, or whose value carries CR or LF, is now rejected by the schema, so a stored Route with such a header fails to load. New defaults also apply without any configuration change: `max_response_body_size` 1 MiB and `max_document_size` 4 MiB (a larger upstream response or document fails), `max_expanded_nodes` 50000 (past it a `$ref` degrades to a generic object), an http(s) `$ref` may only leave the origin of `openapi_url` when `allowed_ref_hosts` permits it, a request carrying `Origin` is validated against `allowed_origins`, and an SSE session is bound to the Route and Consumer that opened it [#13959](https://github.com/apache/apisix/pull/13959) Review Comment: The Origin default is still described inaccurately. When allowed_origins is unset, the code does not validate against an absent list: origin_rejected accepts only an Origin matching a literal Route host with the same scheme and effective port, or a loopback-to-loopback request. That default can reject an existing browser client without any configuration change. Please say allowed_origins governs when set; otherwise the literal-Route-host and loopback policy applies. ########## CHANGELOG.md: ########## @@ -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 a custom value is now rejected; a Consumer that only carries the old schema default `X-` keeps validating and has the field dropped for compatibility. `appid` (now required), `server` and `ssl_verify` stay on the Consumer [#13929](https://github.com/apache/apisix/pull/13929) +- :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 checks each requested resource and scope against the granted list, so a route listing several permissions, a permission with several scopes, or `http_method_as_scope` on a scoped permission answers 403 unless all of them are granted. Each permission is also sent as `resource#scope1,scope2` with every scope trimmed, so a permission written as `res#view, delete` asks for `delete`, where the untrimmed `" delete"` used to be dropped by Keycloak and never enforced. A token endpoint response that is not a permissions list now returns 503 instead of being read as an allow [#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/apisix/pull/14088) +- :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 aggregating both of them away, for example `sum without (service, service_id) (...)`, reproduces the previous values [#13992](https://github.com/apache/apisix/pull/13992) +- :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 the upstream receives the path the router matched rather than the raw request line. Because that path is the decoded `$uri`, an encoded delimiter in a request nginx normalized is forwarded decoded: `/a/./b%2Fc` reaches the upstream as `/a/b/c`. 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` an exemption applies only when it 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; extend it to cover the encoded form to keep skipping the check [#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 `jwe-decrypt`'s `forward_header` on a `strict: false` route without a token, and `forward-auth`'s `upstream_headers` while the auth service is unavailable with `allow_degradation: true`, reach the upstream unset, the way the normal paths already handle a header they have no value for [#14096](https://github.com/apache/apisix/pull/14096) +- :warning: fix(ai-prompt-guard): inspect embeddings string arrays. The strings of a batch input are joined and checked together, so `allow_patterns` applies to the combined text and a `deny_patterns` match rejects the batch. Token-ID arrays are left as they are, and a recognized request with no selected text keeps being processed [#14073](https://github.com/apache/apisix/pull/14073) +- :warning: fix(openapi-to-mcp): tighten what a tool call can put into the upstream request. 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 and take precedence; and a header name or value that cannot appear in a request header is dropped with a warning. A Route `headers` entry whose name carries `:` or whitespace, or whose value carries CR or LF, is now rejected by the schema, so a stored Route with such a header fails to load. New defaults also apply without any configuration change: `max_response_body_size` 1 MiB and `max_document_size` 4 MiB (a larger upstream response or document fails), `max_expanded_nodes` 50000 (past it a `$ref` degrades to a generic object), an http(s) `$ref` may only leave the origin of `openapi_url` when `allowed_ref_hosts` permits it, a request carrying `Origin` is validated against `allowed_origins`, and an SSE session is bound to the Route and Consumer that opened it [#13959](https://github.com/apache/apisix/pull/13959) + Path parameters are 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 resolves to an empty, `.` or `..` segment 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 no default `User-Agent`, and `timeout` now bounds the whole response read where `lua-resty-http` applied it to each receive, so a slow auth service can reach `status_on_error` sooner; 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 post-quantum hybrid group `X25519MLKEM768` to the default TLS 1.3 key shares [#14041](https://github.com/apache/apisix/pull/14041) [#14126](https://github.com/apache/apisix/pull/14126) +- 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) Review Comment: The declined rationale does not preserve the existing workaround. The new grpc_ssl_trusted_certificate is emitted in the generated server block, so an existing value in http_configuration_snippet that was previously inherited is now overridden. Adding another value in http_server_configuration_snippet does not win: this directive uses ngx_conf_set_str_slot, so a second value in the same context is a duplicate configuration error. The @grpc_pass location also does not receive http_server_location_configuration_snippet. Please document the affected deployment and migration: add the required CA to apisix.ssl.ssl_trusted_certificate or use per-Upstream tls.ca_certs where a distinct trust set is required. ########## CHANGELOG.md: ########## @@ -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 a custom value is now rejected; a Consumer that only carries the old schema default `X-` keeps validating and has the field dropped for compatibility. `appid` (now required), `server` and `ssl_verify` stay on the Consumer [#13929](https://github.com/apache/apisix/pull/13929) +- :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 checks each requested resource and scope against the granted list, so a route listing several permissions, a permission with several scopes, or `http_method_as_scope` on a scoped permission answers 403 unless all of them are granted. Each permission is also sent as `resource#scope1,scope2` with every scope trimmed, so a permission written as `res#view, delete` asks for `delete`, where the untrimmed `" delete"` used to be dropped by Keycloak and never enforced. A token endpoint response that is not a permissions list now returns 503 instead of being read as an allow [#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/apisix/pull/14088) +- :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 aggregating both of them away, for example `sum without (service, service_id) (...)`, reproduces the previous values [#13992](https://github.com/apache/apisix/pull/13992) +- :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 the upstream receives the path the router matched rather than the raw request line. Because that path is the decoded `$uri`, an encoded delimiter in a request nginx normalized is forwarded decoded: `/a/./b%2Fc` reaches the upstream as `/a/b/c`. 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` an exemption applies only when it 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; extend it to cover the encoded form to keep skipping the check [#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 `jwe-decrypt`'s `forward_header` on a `strict: false` route without a token, and `forward-auth`'s `upstream_headers` while the auth service is unavailable with `allow_degradation: true`, reach the upstream unset, the way the normal paths already handle a header they have no value for [#14096](https://github.com/apache/apisix/pull/14096) +- :warning: fix(ai-prompt-guard): inspect embeddings string arrays. The strings of a batch input are joined and checked together, so `allow_patterns` applies to the combined text and a `deny_patterns` match rejects the batch. Token-ID arrays are left as they are, and a recognized request with no selected text keeps being processed [#14073](https://github.com/apache/apisix/pull/14073) +- :warning: fix(openapi-to-mcp): tighten what a tool call can put into the upstream request. 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 and take precedence; and a header name or value that cannot appear in a request header is dropped with a warning. A Route `headers` entry whose name carries `:` or whitespace, or whose value carries CR or LF, is now rejected by the schema, so a stored Route with such a header fails to load. New defaults also apply without any configuration change: `max_response_body_size` 1 MiB and `max_document_size` 4 MiB (a larger upstream response or document fails), `max_expanded_nodes` 50000 (past it a `$ref` degrades to a generic object), an http(s) `$ref` may only leave the origin of `openapi_url` when `allowed_ref_hosts` permits it, a request carrying `Origin` is validated against `allowed_origins`, and an SSE session is bound to the Route and Consumer that opened it [#13959](https://github.com/apache/apisix/pull/13959) + Path parameters are 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 resolves to an empty, `.` or `..` segment 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 no default `User-Agent`, and `timeout` now bounds the whole response read where `lua-resty-http` applied it to each receive, so a slow auth service can reach `status_on_error` sooner; 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 post-quantum hybrid group `X25519MLKEM768` to the default TLS 1.3 key shares [#14041](https://github.com/apache/apisix/pull/14041) [#14126](https://github.com/apache/apisix/pull/14126) +- 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 connection is reused only by a client configured with the same TLS options [#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): report only identifiers when an `aws` or `vault` reference cannot be resolved, i.e. the AWS secret name and field and the vault path, so the provider's content stays out of `error.log`. A vault request rejected with 403 no longer carries the response body in the error either [#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. The upstream receives the inflated body too, unless `forward_compressed: true` restores the original bytes before proxying. `core.request.get_body` gains an opt-in `opts.decompress`, and `max_req_body_size`, default 1 MiB, bounds the inflated data as well as the raw read [#14016](https://github.com/apache/apisix/pull/14016) Review Comment: Two details still do not match the final merged code. core.request.get_body did not gain opts.decompress; decompression was moved back into request-decompress before merge, while get_body only gained the too_large error classification. Also, forward_compressed true restores the original bytes only when no earlier plugin changed the plaintext; otherwise it recompresses the modified body. Please correct both statements. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
