API7 Gateway 3.9.18
Alibaba Cloud Logging (SLS) — The plugin now verifies the TLS certificate of the log server.
Release Date: 2026-08-12
Breaking Changes
Plugins
-
Upgrade note
The plugin now verifies the TLS certificate of the log server. Earlier versions completed the TLS handshake without validating the certificate at all, so a log server presenting a self-signed certificate, a certificate from a private CA, or an expired certificate was accepted silently, and log records containing the configured access key were sent to it.
Verification is controlled by the new
ssl_verifyoption, which defaults totrue. After the upgrade, a log server whose certificate the gateway cannot validate causes the batch processor to drop the log entries and reportfailed to perform TLS handshake to TCP serverin the error log. If your log server presents a certificate that is not signed by a publicly trusted CA, add the issuing CA to the Data Plane's trusted certificate store before upgrading, or setssl_verifytofalseon the plugin to keep the previous behavior.
Upgrade Notes
Upgrade note — the Loki Logger headers field is encrypted at rest
When Data Plane data encryption is enabled (apisix.data_encryption.enable), the Control Plane now encrypts the Loki Logger (opens in Plugin Hub docs) headers field at rest. Because API7 EE upgrades the Control Plane before the Data Plane, during the upgrade window a 3.9.18 Control Plane encrypts this field while an older 3.9.17 Data Plane cannot decrypt it, which can cause the plugin to fail until the Data Plane is also upgraded.
If you use Loki Logger with headers, upgrade the Data Plane to 3.9.18 promptly after the Control Plane, and avoid editing that plugin until both sides are on 3.9.18.
Upgrade note — Dashboard users are signed out once after the upgrade
The key used to sign Dashboard session cookies was a compile-time constant, the same in every deployment. It is now a random 32-byte key generated on first start and stored per deployment, so a session cookie can no longer be signed with a value that is identical everywhere.
Sessions issued before the upgrade were signed with the old constant and are no longer accepted. Every signed-in user has to sign in again once after the Control Plane is upgraded. Nothing needs to be done before upgrading, and no configuration change is required.
Upgrade note — Docker Compose deployments switch to the radixtree_host_uri router
The Docker Compose package now sets apisix.router.http to radixtree_host_uri, which is the default the Helm chart already uses. Previously the package did not set the key and fell back to radixtree_uri, so the same configuration could resolve routes differently depending on how the gateway was deployed.
Configurations that rely on plain host and path matching are unaffected. The difference appears when routes that specify hosts and routes that do not specify hosts share overlapping paths: radixtree_host_uri consults the per-host routers first and falls back to the host-less routes only when none of them matched, whereas radixtree_uri keeps every route in a single tree ordered by path specificity and priority. To keep the previous engine, set apisix.router.http to radixtree_uri in gateway_conf/config.yaml.
Features
Plugins
- OpenID Connect (opens in Plugin Hub docs)
- Added support for Pushed Authorization Requests (PAR, RFC 9126). When
par.enabledis set, the gateway sends the full authorization request to the identity provider's PAR endpoint over a back channel and redirects the browser with only arequest_uri, so authorization parameters never travel through the user agent and cannot be tampered with there. The endpoint and its client authentication method are configured withpar.endpointandpar.endpoint_auth_method. - Added support for DPoP sender-constrained tokens (RFC 9449). When
dpop.enabledis set, the gateway signs a DPoP proof withdpop.private_keyfor the token request and advertises the matchingdpop.public_jwk, binding the issued tokens to that key so a stolen token cannot be replayed from another client. With PAR enabled as well, the key thumbprint is also sent asdpop_jkton the pushed request, as the specification requires.
- Added support for Pushed Authorization Requests (PAR, RFC 9126). When
- AI AWS Content Moderation (opens in Plugin Hub docs)
- Added
check_responseto moderate the LLM's reply in addition to the request. A non-streaming reply is moderated before it reaches the client. For a streaming reply,stream_check_modeselects how it is handled:final_packet, the default, moderates the assembled response and annotates the last chunk with its risk level, whilerealtimemoderates batches of at moststream_check_cache_sizecharacters as the response is relayed and replaces the rest of the stream as soon as a batch is flagged, so a stream that turns harmful mid-response is cut off rather than delivered in full. A blocked reply is returned in the provider's own response format with the configureddeny_codeanddeny_message. - Added
request_check_rolesandrequest_check_modeto choose which message roles are moderated and whether only the last message or every message of the selected roles is checked, matching the options already available in AI Aliyun Content Moderation (opens in Plugin Hub docs).
- Added
- AI Proxy Multi (opens in Plugin Hub docs)
- Added the
semanticbalancer algorithm, which routes each request to the instance whose configuredexamplesare semantically closest to the prompt, instead of by weight or hash. The embedding provider is configured undersemantic_opts.embeddings, a global or per-instancethresholdsets how close a match has to be, and requests that match nothing well enough go tosemantic_opts.fallback. If the embedding service cannot be reached, the request is sent to the fallback instance rather than failed. Withsemantic_opts.debuggingenabled, the per-instance scores and the chosen instance are returned in response headers.
- Added the
Control Plane
- A custom plugin can now be uploaded as a complete package containing an entry file and its dependency files, instead of a single Lua file. The archive holds
$NAME.luaat the top level, its dependency modules under$NAME/, andmetadata.json; the Dashboard reads the metadata to fill in the plugin's catalog, description, documentation link, and author. The package is distributed to the Data Plane as one unit, so a plugin is never published with only some of its files present, and updating it replaces every file at once without restarting the gateway. - The Docker Compose package now ships
portal.sh, which brings up the Developer Portal against a running deployment. It creates the portal instance and its token, writes the frontend configuration and database, and starts the frontend behind a Compose profile, so trying the portal out no longer means following the manual deployment guide by hand. Developer accounts and sessions surviveportal.sh stopand a laterstart.
Data Plane
- The plugin set selected by global rules is now computed once per request and reused across phases, instead of being recomputed in each of them. On a deployment that enables the Prometheus (opens in Plugin Hub docs) plugin through a global rule, this measured 12% higher throughput on a single worker.
Console (Dashboard)
- The language switcher no longer shows country flags, which do not map to languages, and marks the language that is currently active.
Fixes
Plugins
- AI Aliyun Content Moderation (opens in Plugin Hub docs)
- Fixed issue: With
systemselected inrequest_check_roles, content placed in adeveloperrole message was not sent for moderation.developeris the role OpenAI uses in place ofsystemon newer models and on the Responses API, so a client could put instructions there and have them proxied to the LLM unchecked. Both roles are now collected together, and the existingsystementry covers them both. - Fixed issue: In
realtimestream check mode with a protocol converter in use — for example an Anthropic client in front of an OpenAI-compatible upstream — the same response text was submitted to the moderation service once per converted chunk rather than once per upstream chunk. Moderation results were unaffected, butstream_check_cache_sizewas reached far sooner than configured and the number of moderation calls scaled with the converter's output, adding latency and cost.
- Fixed issue: With
- AI AWS Content Moderation (opens in Plugin Hub docs)
- Fixed issue: Content longer than Amazon Comprehend's per-segment and per-request limits was rejected or silently truncated, so long prompts and replies were only partially moderated. Content is now split to stay within the documented limits and submitted across as many batches as needed, and the HTTP client is reused across the calls of one request instead of opening a new connection for each.
- Loki Logger (opens in Plugin Hub docs)
- Fixed issue: The
headersfield, which commonly carries the authentication token for the Loki endpoint, was stored in plaintext. It is now encrypted at rest when Data Plane data encryption is enabled (see Upgrade Notes).
- Fixed issue: The
- Limit Conn (opens in Plugin Hub docs)
- Fixed issue: When the plugin was configured on a service, each route under that service counted its concurrent connections separately, so the effective limit was multiplied by the number of routes. The counter is now keyed by the resource the plugin is configured on, so a service-level limit applies across all of its routes.
- Prometheus (opens in Plugin Hub docs)
- Fixed issue: Expired entries in the shared dictionary that backs the metrics were not reclaimed, so its usage only ever grew and it eventually stayed full, at which point new metric series were dropped. Expired entries are now reclaimed.
- OpenID Connect (opens in Plugin Hub docs)
- Fixed issue: An
Authorizationheader with leading or trailing whitespace was rejected inbearer_onlymode, because the whitespace was carried into the extracted token. The header is now trimmed before the token is read.
- Fixed issue: An
Data Plane
- Fixed issue: The
hostsconfigured on a service were matched case-sensitively against the request'sHostheader when the gateway used theradixtree_host_urirouter, which is the default in Kubernetes deployments. Because the header is normalized to lowercase before matching, a service whosehostscontained any uppercase letter could never match, and every route under it returnedHTTP 404for every request. Service hosts are now normalized, matching how route hosts have always behaved. Docker Compose deployments were not affected before this release, because they used theradixtree_urirouter (see Upgrade Notes). - Fixed issue: Control characters in a rewritten upstream URI — for example a carriage return and line feed carried in from a percent-encoded request path — were written into the upstream request line verbatim, which allowed a client to truncate the request line and inject arbitrary headers into the request the gateway sent upstream. Control characters are now percent-encoded.
- Fixed issue: Certificates and private keys for stream (TCP/UDP) TLS listeners were not resolved when they were stored as
$ENV://or$secret://references, so the TLS handshake failed with a PEM parsing error. Secret references are now resolved in the stream subsystem as they are for HTTP listeners. - Fixed issue: When the watch on the configuration store timed out with no events, the Data Plane advanced its revision as though it had consumed everything up to that point, so configuration changes that arrived around the timeout could be skipped. The revision is no longer advanced on a timeout, and the recovery reload that follows a broken watch is now cheaper.
- Fixed issue: When a full configuration reload delivered an item the Data Plane could not accept, the previously working value for that item was discarded rather than kept, so a single invalid resource could take working configuration out of service after a reconnect. The previous value is now retained.
Control Plane
- Fixed issue: The
order_byanddirectionlist parameters were interpolated into the generated SQL without validation, so a value that was not a column name changed the query rather than being rejected. Values are now validated wherever they are used, and the six list endpoints that had not declared the parameters — the four AI Gateway lists, certificate SNI usages, and contact point usages — now document the columns they accept and reject anything else withHTTP 400. Sorting by a valid column is unchanged; a request that passes an unsupported value to one of those endpoints now receives an error instead of an arbitrarily ordered result. - Fixed issue: The Lua parser that reads a custom plugin's schema at upload time evaluated the uploaded file with the full standard library available, so a file could run commands on the Control Plane host while its schema was being read. The parser now runs with only the libraries a schema definition needs, and a file that exceeds a three-second budget is rejected. Ordinary plugin files, whose schemas are declarative tables, are unaffected.
- Fixed issue: The Helm installation script generated for a gateway group wrote the Data Plane's private key, certificate, and CA to fixed paths under
/tmpwith the operator's default file mode, and left them there. The script now creates them withumask 077, uses temporary file names, and deletes them when it exits. - Fixed issue: Metrics pushed by a Data Plane were stored with the
gateway_group_idlabel taken from the payload rather than from the credential the connection was authenticated with, so a Data Plane could write series into another gateway group's metrics. The label is now taken from the authenticated identity. - Fixed issue: The etcd-compatible endpoint that Data Planes read their configuration from forwarded keys outside the caller's gateway group namespace, so a Data Plane could read, write, and delete the configuration of other gateway groups. Keys outside the caller's namespace are now rejected.
- Fixed issue: Service registry health check and probe results reported by a Data Plane were applied by registry ID alone, so a Data Plane could overwrite the discovered services and connection status of a registry belonging to another gateway group. Updates are now scoped to the reporting Data Plane's gateway group.
- Fixed issue: The
security.ssrf_protectionsetting had no effect on the Developer Portal process, so approval webhooks and dynamic client registration could still reach internal addresses when it was enabled. The Developer Portal now applies the same policy as the Control Plane. - Fixed issue: The Developer Portal API usage endpoint filtered statistics by the developer identifier supplied by the portal, which is only unique within one portal, so a developer could see the API product names and hourly call counts of a developer with the same identifier in another portal. Statistics are now restricted to the applications the caller owns.
- Fixed issue: Importing a license could leave the Dashboard rejecting every request with
license clock signature is invalidfor as long as the cache entry lived, because the license and the clock signed for it were cached separately and could be refreshed independently. They are now read and cached as one entry, cross-checked against each other, and written in a single transaction on import. - Fixed issue: After a license that raises the licensed core allowance was imported, a Control Plane replica that had already blocked a write kept rejecting writes for up to the license cache lifetime. The license restriction is now evaluated against the current state on every request instead of being cached.
- Fixed issue: On SQL Server, a query interrupted mid-flight was indistinguishable from an empty result, so a delete could report success without recording the deletion, and a lookup could come back empty for configuration that existed. Interrupted queries now surface the error. Separately, the isolation level set for compaction leaked onto pooled connections, so later queries ran serializably and could queue behind writers long enough to time out, or deadlock with concurrent writes. Compaction now runs on a dedicated connection whose isolation level is reset before it returns to the pool. PostgreSQL and MySQL deployments were not affected.
Console (Dashboard)
- Fixed issue: The monitoring page reported a success ratio of
0%when there was no traffic in the selected window, or when the reported failure count exceeded the request count, which read as though every request had failed. Both cases now render an em dash, because there is no ratio to report. The request and failure counts themselves are unchanged. - Fixed issue: The status badge on an offline gateway instance rendered broken text in both languages, such as
Offline - In 6 DaysUntil Deletion. - Fixed issue: The Debug Session sampling rule presets used
http_uri,http_method, andhttp_status. On the Data Plane, a variable with anhttp_prefix resolves to a request header, so those presets never matched and a debug session created from one captured nothing. The presets now useuri,method, andstatus. - Fixed issue: Several in-product help links pointed at the frozen documentation archive instead of the current documentation set.