Docs

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

  • Alibaba Cloud Logging (SLS)

    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_verify option, which defaults to true. After the upgrade, a log server whose certificate the gateway cannot validate causes the batch processor to drop the log entries and report failed to perform TLS handshake to TCP server in 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 set ssl_verify to false on 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.enabled is 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 a request_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 with par.endpoint and par.endpoint_auth_method.
    • Added support for DPoP sender-constrained tokens (RFC 9449). When dpop.enabled is set, the gateway signs a DPoP proof with dpop.private_key for the token request and advertises the matching dpop.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 as dpop_jkt on the pushed request, as the specification requires.
  • AI AWS Content Moderation (opens in Plugin Hub docs)
    • Added check_response to 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_mode selects how it is handled: final_packet, the default, moderates the assembled response and annotates the last chunk with its risk level, while realtime moderates batches of at most stream_check_cache_size characters 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 configured deny_code and deny_message.
    • Added request_check_roles and request_check_mode to 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).
  • AI Proxy Multi (opens in Plugin Hub docs)
    • Added the semantic balancer algorithm, which routes each request to the instance whose configured examples are semantically closest to the prompt, instead of by weight or hash. The embedding provider is configured under semantic_opts.embeddings, a global or per-instance threshold sets how close a match has to be, and requests that match nothing well enough go to semantic_opts.fallback. If the embedding service cannot be reached, the request is sent to the fallback instance rather than failed. With semantic_opts.debugging enabled, the per-instance scores and the chosen instance are returned in response headers.

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.lua at the top level, its dependency modules under $NAME/, and metadata.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 survive portal.sh stop and a later start.

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 system selected in request_check_roles, content placed in a developer role message was not sent for moderation. developer is the role OpenAI uses in place of system on 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 existing system entry covers them both.
    • Fixed issue: In realtime stream 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, but stream_check_cache_size was reached far sooner than configured and the number of moderation calls scaled with the converter's output, adding latency and cost.
  • 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 headers field, 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).
  • 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 Authorization header with leading or trailing whitespace was rejected in bearer_only mode, because the whitespace was carried into the extracted token. The header is now trimmed before the token is read.

Data Plane

  • Fixed issue: The hosts configured on a service were matched case-sensitively against the request's Host header when the gateway used the radixtree_host_uri router, which is the default in Kubernetes deployments. Because the header is normalized to lowercase before matching, a service whose hosts contained any uppercase letter could never match, and every route under it returned HTTP 404 for 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 the radixtree_uri router (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_by and direction list 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 with HTTP 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 /tmp with the operator's default file mode, and left them there. The script now creates them with umask 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_id label 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_protection setting 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 invalid for 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, and http_status. On the Data Plane, a variable with an http_ prefix resolves to a request header, so those presets never matched and a debug session created from one captured nothing. The presets now use uri, method, and status.
  • Fixed issue: Several in-product help links pointed at the frozen documentation archive instead of the current documentation set.