Skip to content

chore: release 3.18.0 - #13842

Merged
shreemaan-abhishek merged 1 commit into
masterfrom
release/3.18
Aug 20, 2026
Merged

chore: release 3.18.0#13842
shreemaan-abhishek merged 1 commit into
masterfrom
release/3.18

Conversation

@shreemaan-abhishek

@shreemaan-abhishek shreemaan-abhishek commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Description

Release 3.18.0.

This PR:

Breaking changes (see the Change section)

Checklist

  • I have explained the need for this PR and the problem it solves
  • I have explained the changes or the new features added to this PR
  • I have added tests corresponding to this change (N/A: release bookkeeping only, no runtime code changes)
  • I have updated the documentation to reflect this change
  • I have verified that this change is backward compatible (this PR itself only touches release metadata; the breaking changes it documents landed in their own PRs and are listed in the Change section)

@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. doc Documentation things labels Aug 18, 2026
Comment thread CHANGELOG.md Outdated
- :warning: fix(sls-logger): verify the TLS certificate of the log server. New `ssl_verify` option, default `true` [#13785](https://github.com/apache/apisix/pull/13785)
- :warning: fix(openid-connect): enforce audience, issuer and required scopes. Bearer introspection fails closed when the discovery document is unavailable, `claim_validator.audience.match_with_client_id` now implies `required`, and `required_scopes` is enforced on session (authorization code) flows [#13829](https://github.com/apache/apisix/pull/13829)
- :warning: fix(admin): reject duplicate consumer authentication keys instead of silently accepting them [#13529](https://github.com/apache/apisix/pull/13529)
- :warning: perf(core): sanitize the `X-Forwarded-*` headers in the NGINX config. The `$var_x_forwarded_proto/host/port` variables are gone, and a trusted peer that sends no `X-Forwarded-Host`/`X-Forwarded-Port` now receives the APISIX-observed values instead of falling through to `$host`/`$server_port` [#13803](https://github.com/apache/apisix/pull/13803)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #13803 does not remove all three variables. Its merged NGINX template still defines $var_x_forwarded_host and $var_x_forwarded_port as map outputs; they are no longer writable from Lua. Only $var_x_forwarded_proto is removed. Please change this to something like: “$var_x_forwarded_proto is removed, while $var_x_forwarded_host and $var_x_forwarded_port are no longer writable.”

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, fixed in 9bce154 with your wording. Verified: ngx_tpl.lua still defines $var_x_forwarded_host and $var_x_forwarded_port as map outputs and feeds them to the more_set_input_headers directives; only $var_x_forwarded_proto is gone, replaced by $scheme. All three did leave the Lua-writable list in core/ctx.lua, which is what the entry now says.

Comment thread CHANGELOG.md Outdated

- feat: raise default sizes for non-evictable shared dicts: `prometheus-metrics` 15m/10m→128m, `nacos` 10m→64m, consul and kubernetes discovery `shared_size` 1m→64m, `tracing_buffer` 10m→32m [#13688](https://github.com/apache/apisix/pull/13688)
- chore(core): remove the dead `clean_handlers` mechanism. `core.config_util.add_clean_handler`, `cancel_clean_handler` and `fire_all_clean_handlers` no longer exist [#13761](https://github.com/apache/apisix/pull/13761)
- feat(ai-proxy): send LLM requests through `ngx_http_ffi_client`. New `plugin_attr.ai-proxy.http_client`, defaulting to the FFI client, which requires an APISIX-Runtime that ships `ngx_http_ffi_client` (set `lua-resty-http` to opt out) [#13778](https://github.com/apache/apisix/pull/13778)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be marked as backward-incompatible and moved to Change. PR #13778 changes the default from lua-resty-http to ngx_http_ffi_client; on a hand-built or older runtime without the module, AI requests now fail deliberately instead of falling back. Such deployments must set plugin_attr.ai-proxy.http_client: lua-resty-http to preserve the previous behavior.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APISIX v3.18.0 should be installed along with the new runtime which will guarantee no breaking change. We do not guarantee back-compat with a new APISIX version running on an older runtime.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping this one in Core, for the reason above. Every release repins APISIX_RUNTIME in .requirements (1.3.11 -> 1.3.16 this cycle), so "requires this release's runtime" would make most runtime-dependent work backward-incompatible and dilute the Change section. I have reworded the entry in 9bce154 to say the client ships with the runtime pinned by this release, and to name the plugin_attr.ai-proxy.http_client: lua-resty-http opt-out for hand-built runtimes without the module.

Comment thread CHANGELOG.md Outdated
- feat(stream): support `set_real_ip_from` to trust an inbound PROXY protocol header [#13700](https://github.com/apache/apisix/pull/13700)
- feat(stream): support upstream client certificate (mTLS) in the L4 proxy [#13596](https://github.com/apache/apisix/pull/13596)
- feat(router): add `match_uri_encoded_slash` to keep `%2F` in path parameters [#13626](https://github.com/apache/apisix/pull/13626)
- feat: add `max_post_args_readable_size` to bound `post_arg.*` body reads [#13601](https://github.com/apache/apisix/pull/13601)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be marked as backward-incompatible and moved to Change. PR #13601 introduces a default 64 MiB cap for JSON/multipart post_arg.* matching. A request above that size previously could match the route; after this change the predicate resolves to nil and the route returns 404. Operators must raise apisix.max_post_args_readable_size or set it to 0 to preserve the old unlimited behavior.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are right. although we allow backcompat behaviour, request paths with body larger than 64 mb will no longer work. this is something upgraders should be aware of.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, moved to Change with ⚠️ in 9bce154, including the migration action (raise the value, or set it to 0). Worth adding that this is reachable in a default install rather than only on tuned deployments: client_max_body_size defaults to 0 (unlimited), so a body above 64 MiB does reach route matching.

Comment thread CHANGELOG.md
- feat(ai-cache): add streaming support with format tagging [#13644](https://github.com/apache/apisix/pull/13644)
- feat: add ai-lakera-guard plugin [#13570](https://github.com/apache/apisix/pull/13570)
- feat(ai-lakera-guard): scan LLM responses (direction output/both, non-streaming and streaming) [#13606](https://github.com/apache/apisix/pull/13606)
- feat: add ldap-auth-advanced plugin [#13762](https://github.com/apache/apisix/pull/13762)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #13762 also introduces a backward-incompatible change for the existing ldap-auth plugin: the lua-resty-ldap upgrade makes tls_verify: true perform real certificate verification, whereas it was previously a silent no-op. Deployments using self-signed or hostname-mismatched LDAP certificates may start failing. Please add this impact under Change (with the migration action: install a trusted/matching certificate or explicitly disable verification where appropriate), while keeping the new ldap-auth-advanced plugin here as a feature.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed, and added as its own Change entry in 9bce154 while keeping the new plugin under Plugins. The mechanism: in lua-resty-ldap 0.1.0 the handshake read conf.verify_ldap_host (init.lua:78) while ldap-auth passes tls_verify, so the argument was always nil and sslhandshake(true, host, nil) verified nothing. 0.3.1 maps ssl_verify = conf.tls_verify, so the flag now takes effect. Scope is limited to deployments that explicitly set tls_verify: true, which are exactly the ones that believed verification was already on.

Comment thread CHANGELOG.md Outdated
- feat(openid-connect): add the `set_raw_id_token_header` option [#13616](https://github.com/apache/apisix/pull/13616)
- feat(openid-connect): support PAR and DPoP client options [#13649](https://github.com/apache/apisix/pull/13649)
- feat(ldap-auth): add `hide_credentials` [#13832](https://github.com/apache/apisix/pull/13832)
- refactor(ai-providers): make the LLM request client ctx-free [#13699](https://github.com/apache/apisix/pull/13699)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not only a refactor. PR #13699 fixes a credential-leak path: ai-request-rewrite internal LLM calls previously forwarded the downstream client’s Authorization and Cookie headers to the configured third-party LLM endpoint. The new ctx-free/internal-call split stops that forwarding and uses only the plugin’s configured credentials. Please categorize this as a fix and describe the user/security impact.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, recategorised as fix(ai-request-rewrite) in 9bce154 with the user impact described. One refinement to the wording: the pre-image copied every client header and then merged ext_opts_headers over them, so when the provider authenticates through the Authorization header the client's value was overwritten. Cookie and any other client headers (custom API keys, session tokens) leaked unconditionally, and Authorization leaked whenever the provider uses query-param, api-key or SigV4 auth. The entry is phrased to match that.

Comment thread CHANGELOG.md Outdated
- refactor(cas-auth): drop the unnecessary raw Cookie header fallback [#13635](https://github.com/apache/apisix/pull/13635)
- perf(zipkin): skip span tag construction for unsampled requests [#13656](https://github.com/apache/apisix/pull/13656)
- perf(datadog): reuse one UDP socket per batch and coalesce metrics [#13653](https://github.com/apache/apisix/pull/13653)
- perf(loggly): hoist the per-request closure out of the log phase [#13648](https://github.com/apache/apisix/pull/13648)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This entry should also describe the correctness fix and use fix(loggly): the old module-level handle_http_payload closure was overwritten by whichever route logged last, so asynchronous batches from two routes could be sent with the wrong route’s token/tags/config. PR #13648 adds a two-route regression test for that behavior; reducing per-request allocations is an additional performance benefit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, it is fix(loggly) now in 9bce154. Confirmed the mechanism: handle_http_payload was a module-level local assigned inside _M.log on every request, while handle_log resolved it at flush time, so a batch queued for one route could be sent with another route's token, tags and endpoint. The reduced per-request allocation is described as the secondary benefit.

Comment thread CHANGELOG.md Outdated
- fix(key-auth): propagate the real auth error to the multi-auth orchestrator [#13693](https://github.com/apache/apisix/pull/13693)
- fix(basic-auth): validate `anonymous_consumer` against the schema [#13682](https://github.com/apache/apisix/pull/13682)
- fix(hmac-auth): pass ctx when hiding the Authorization header [#13820](https://github.com/apache/apisix/pull/13820)
- fix(ldap-auth): key the consumer lookup on the escaped bind DN [#13805](https://github.com/apache/apisix/pull/13805)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be marked backward-incompatible under Change. PR #13805 states that a Consumer whose user_dn was written in the previous unescaped form will stop matching when the LDAP username contains RFC 4514 structural characters (for example comma,user). Operators must update those Consumer user_dn values to the escaped bind-DN form, such as cn=comma\\,user,....

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, moved to Change in 9bce154 with the escaped-DN migration example. Scope note for upgraders: only usernames containing RFC 4514 structural characters change key, since the library escapes the RDN value and returns the canonical bind DN that the plugin now keys the consumer lookup on.

Comment thread CHANGELOG.md Outdated
- fix: reframe the request body forwarded upstream in serverless plugins [#13798](https://github.com/apache/apisix/pull/13798)
- feat(limit-count): add Redis Sentinel backend, sliding window and delayed sync support [#13443](https://github.com/apache/apisix/pull/13443)
- feat(prometheus): support disabling labels via plugin metadata to reduce cardinality [#13202](https://github.com/apache/apisix/pull/13202)
- feat(prometheus): add built-in LLM histograms for TTFT and token distribution [#13487](https://github.com/apache/apisix/pull/13487)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be marked backward-incompatible under Change. PR #13487 changes the existing apisix_llm_latency metric schema by adding a type label, and streaming requests now emit both type="total" and type="ttft" samples. The PR explicitly tells operators to update existing queries with type="total"; dashboards, alerts, and recording rules need that migration.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, moved to Change in 9bce154. Confirmed against the 3.17.0 tag: llm_latency was defined there with no type label, and this PR prepends it and emits both type="total" and type="ttft" for streaming requests, so existing dashboards and alerts double-count until they select type="total". The entry now carries that migration.

Comment thread CHANGELOG.md Outdated
- feat(prometheus): support disabling labels via plugin metadata to reduce cardinality [#13202](https://github.com/apache/apisix/pull/13202)
- feat(prometheus): add built-in LLM histograms for TTFT and token distribution [#13487](https://github.com/apache/apisix/pull/13487)
- feat(ai-proxy): add built-in nginx variables for LLM observability [#13477](https://github.com/apache/apisix/pull/13477)
- feat(ai-plugins): add `fail_mode` for Consumer-bound protocol handling [#13489](https://github.com/apache/apisix/pull/13489)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be marked backward-incompatible under Change. fail_mode is additive, but its default skip intentionally changes existing behavior for unrecognized/non-AI traffic: ai-aliyun-content-moderation changes from 500 to pass-through, and ai-aws-content-moderation changes from moderating the raw body to pass-through. Operators that require fail-closed handling must set fail_mode: error.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, moved to Change in 9bce154. Confirmed the direction of the change: ai-aliyun-content-moderation previously returned 500 for a missing AI instance and an unsupported protocol, and 400 for an unsupported content-type; all three now go through binding.on_unsupported with the skip default and pass the request through unmoderated. The entry names fail_mode: error as the way to keep the previous fail-closed behaviour.

@shreemaan-abhishek

Copy link
Copy Markdown
Contributor Author

Thanks for the review @kayx23. Eight of the nine are addressed in 9bce154; the ngx_http_ffi_client one stays in Core for the runtime-pinning reason discussed in that thread, with sharper wording.

The Change section goes from 9 to 14 entries:

Recategorised in Plugins: #13699 refactor -> fix(ai-request-rewrite) and #13648 perf -> fix(loggly), both with the user-visible impact spelled out.

Section totals are now Change 14 / Core 52 / Plugins 99, 165 entries. check_changelog_prs and markdownlint both still pass locally.

The Chinese counterpart in #13843 has been updated in lockstep, so the two files stay line-for-line equivalent.

nic-6443
nic-6443 previously approved these changes Aug 20, 2026
Comment thread CHANGELOG.md Outdated
- :warning: fix(ldap-auth): `tls_verify: true` now performs real certificate verification, which the previously pinned `lua-resty-ldap` silently ignored; an LDAP server with a self-signed or hostname-mismatched certificate must present a trusted certificate, or verification has to be turned off explicitly [#13762](https://github.com/apache/apisix/pull/13762)
- :warning: fix(ldap-auth): key the consumer lookup on the escaped bind DN. A consumer whose `user_dn` was written in the previous unescaped form stops matching once the username contains RFC 4514 structural characters; rewrite such values in escaped form, e.g. `cn=comma\,user,ou=users,dc=example,dc=org` [#13805](https://github.com/apache/apisix/pull/13805)
- :warning: feat(prometheus): add built-in LLM histograms for TTFT and token distribution. `apisix_llm_latency` gains a `type` label and streaming requests now emit both `type="total"` and `type="ttft"`, so existing queries, dashboards and alerts must select `type="total"` [#13487](https://github.com/apache/apisix/pull/13487)
- :warning: feat(ai-plugins): add `fail_mode` for Consumer-bound protocol handling. Its `skip` default turns unrecognized or non-AI traffic from a hard error into pass-through in `ai-aliyun-content-moderation` and `ai-aws-content-moderation`; set `fail_mode: error` to keep the previous fail-closed behavior [#13489](https://github.com/apache/apisix/pull/13489)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One remaining correction: this still generalizes the previous hard-error/fail-closed behavior to AWS. PR #13489 states that Aliyun returned 500/400, while AWS moderated the raw body (and ai-prompt-guard already passed through). Please use wording such as: “The skip default changes unrecognized or non-AI traffic to pass-through: Aliyun no longer returns an error, and AWS no longer moderates the raw body. Set fail_mode: error where unrecognized traffic must be rejected.”

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, applied in a02cb7e. Verified the AWS pre-image: _M.rewrite read the body with core.request.get_body() and moderated it, with no content-type or protocol gate at all, so the gate added here is what turns non-JSON traffic into a skip. Calling that a "hard error" was wrong, and it hid the part that actually matters for upgraders: traffic that used to be scanned silently stops being scanned.

I extended your wording slightly to cover the third plugin. ai-prompt-guard was not purely pass-through before either: a body that failed JSON parsing returned 400, which now becomes pass-through at the skip default. Its other path, a body matching no AI protocol, was silently allowed before and still is, so nothing changes there.

The entry now reads:

Its skip default lets unrecognized or non-AI traffic through instead of failing: ai-aliyun-content-moderation and ai-prompt-guard no longer return an error, and ai-aws-content-moderation no longer moderates a non-JSON body as raw text. Set fail_mode: error where such traffic must be rejected.

The Chinese counterpart in #13843 carries the same change.

Signed-off-by: Abhishek Choudhary <shreemaan.abhishek@gmail.com>
@shreemaan-abhishek
shreemaan-abhishek merged commit fa69297 into master Aug 20, 2026
45 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

doc Documentation things size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants