API7 Gateway 3.10.7
Basic Auth — A basic auth password can no longer be empty.
Release Date: 2026-09-08
Breaking Changes
Plugins
-
Basic Auth (opens in Plugin Hub docs)
Upgrade note
A basic auth password can no longer be empty. The Control Plane now rejects a consumer or a credential whose password is an empty string with
HTTP 400andbasic-auth plugin: password: String length must be greater than or equal to 1. On 3.10.6 the same request returnedHTTP 200and was stored.A consumer with an empty password that is already stored turns into an error-level entry in the configuration compatibility report after the upgrade, and any further edit of it is rejected until the password is set.
The Data Plane now fails closed as well: a password that resolves to an empty string, including an
$env://or$secret://reference that resolves to"", is rejected withHTTP 401and a warning in the error log. On 3.10.6 such a consumer authenticated successfully with credentials of the username, a colon, and a space.Before upgrading, check for basic auth consumers and credentials whose password is empty or whose secret reference resolves to an empty value, and give each of them a real password.
Control Plane
-
Routes the gateway cannot tell apart are refused
Upgrade note
Creating or updating a route (
POST/PUT/PATCH /apisix/admin/routes), and updating a service (PUT/PATCH /apisix/admin/services/{id}), is now refused withHTTP 400when the result would leave the gateway group with two routes matching the same URL for the same HTTP methods at the same priority. Importing a service from an OpenAPI specification is refused on the same grounds, because it creates its routes through the same path — so a specification whose operations collapse to two indistinguishable routes no longer imports. The message names the route it collides with, for exampleroute "get-again" conflicts with route "get" (id: ...) of service "first" (id: ...): both match "httpbin.org/get" for all HTTP methods at priority 0.Only exact duplicates at the same priority are refused. Two routes matching the same URL at different priorities, with partially shared or disjoint HTTP methods, or with overlapping rather than identical paths are all still accepted, as are routes carrying
vars, which match on conditions a URL comparison cannot model. Inactive services are excluded, and re-activating one is checked, so an inactive service cannot be used to smuggle a duplicate in.Until now only the Dashboard's route conflict modal drew this line, so ADC,
a7and direct Admin API callers could install routes the gateway cannot distinguish, and nothing anywhere flagged it. Existing duplicates keep serving traffic after the upgrade, but the next edit of either route is rejected, and so is any update to a service that owns such a pair — a description or label change included — because every service update re-evaluates all of the routes it owns.Before upgrading, review the gateway group for duplicate routes and give one of each pair a different path, a different set of HTTP methods, or a different priority.
Upgrade Notes
Upgrade note — the grouped compatibility report and dismiss rules need an upgraded Data Plane
The compatibility report is now grouped by issue, and a dismiss rule matches on the machine-readable reason the Data Plane reports. Both depend on the Data Plane reporting compatibility problems as structured data, which it does from 3.10.7 on the 3.10 line and from 3.9.20 on the 3.9 line. API7 Enterprise is upgraded Control Plane first, so during the upgrade window a 3.10.7 Control Plane talks to gateway instances that do not yet report that way, and for those instances neither works.
Such a Data Plane reports each problem as a rendered English sentence carrying no machine-readable reason. The Control Plane drops such a record as the heartbeat arrives, so that instance's compatibility report is empty. Problems that genuinely exist on it — a resource the Data Plane rejected, a plugin it does not have — are not shown, and a dismiss rule has nothing to act on. This affects gateway instances earlier than 3.10.7 on the 3.10 line, earlier than 3.9.20 on the 3.9 line, and every instance on a line older than 3.9 — the supported range starts at 3.2, and none of those releases report structured issues either. An instance on 3.9.20 or later reports structured issues that this Control Plane renders normally. Plan for the report to be blank rather than trustworthy until such an instance is upgraded, and do not read a blank report as a clean one.
The instance itself is still flagged as needing an upgrade by the version rules, so it does not silently look up to date. This affects only what the compatibility report displays. It never affects traffic or the configuration a gateway instance runs, and the report behaves as documented as soon as the instance is upgraded.
Upgrade note — an empty ca_certs list is now rejected in a traffic-split plugin configuration
The Control Plane now rejects a traffic-split plugin configuration whose upstream sets tls.ca_certs to an empty list, answering HTTP 400. On 3.10.6 the same write was accepted.
This applies to the plugin configuration path only. An upstream configured the ordinary way is unaffected, because the Control Plane drops an empty ca_certs before the payload leaves it and never delivered that shape to a Data Plane.
Such a configuration did not work on 3.10.6 either: the Data Plane failed the upstream schema check and discarded the configuration, while the Control Plane reported success. What changes is that a silent failure is now a visible one.
If a traffic-split with an empty tls.ca_certs is already stored, give it a CA certificate or remove the tls block. Until then, any edit to that traffic-split is rejected.
Upgrade note — the Dashboard is served by the Control Plane process, and its asset paths changed
The Console is served from the dashboard process again, as a static single-page application built with Vite. Three consequences for a deployment that does not simply take the files shipped with the release.
The integrated image no longer contains Node.js, and it no longer contains a shell: its base moved to a static distroless image and its entrypoint is now the dashboard binary itself. The docker-compose.yaml in the offline package starts that binary directly and probes it with api7-ee-dashboard healthz. If you carry a customized command or healthcheck from 3.10.6 forward — the one that started node /app/server.js alongside the dashboard — the container will not start. Take the compose file from the new offline package and re-apply your own changes on top of it. Because there is no shell, docker exec ... sh into the dashboard container and any shell-based entrypoint override no longer work.
The browser assets moved from Next.js paths under /_next/static/ to content-hashed files under /assets/. A reverse proxy rule, a Content-Security-Policy, or a CDN cache key pinned to /_next/static no longer matches anything and must be repointed to /assets/.
The Developer Portal frontend image (api7-ee-developer-portal-fe) is now distroless and likewise has no shell, so the same applies to it; it already ran as a non-root user, and its startup behavior, including the database and portal connectivity checks, is unchanged.
Features
Plugins
- JWE Decrypt (opens in Plugin Hub docs)
- A new authentication plugin that decrypts a JSON Web Encryption token presented by the client, identifies the consumer from the token's key ID, and forwards the decrypted payload to the upstream in a configurable request header. A consumer's secret is 32 bytes, given either as plaintext or base64url-encoded, and the token must use the
dirkey management algorithm withA256GCMcontent encryption. A request whose token is missing, malformed, tampered with, or cannot be decrypted is rejected, and the handling of a missing token is configurable. The plugin decrypts tokens only; it has no token generation endpoint. It is configured from the Dashboard like any other authentication plugin.
- A new authentication plugin that decrypts a JSON Web Encryption token presented by the client, identifies the consumer from the token's key ID, and forwards the decrypted payload to the upstream in a configurable request header. A consumer's secret is 32 bytes, given either as plaintext or base64url-encoded, and the token must use the
- Chaitin WAF (opens in Plugin Hub docs)
- The response can now be reported to the SafeLine detection service alongside the request, so data leaked in a response body, an exploit's output, or an unexpected status code becomes visible to SafeLine. Three options are available both on the plugin and in its plugin metadata:
config.log_respenables response reporting,config.resp_body_sizecaps how much of the response body is reported in KB (0reports headers only), andconfig.extra_ignored_content_typesadds comma-separated response content types to skip on top of the built-in list. Reporting is off by default. The report is sent after the response has been delivered to the client and is advisory only: it never blocks or modifies a response.
- The response can now be reported to the SafeLine detection service alongside the request, so data leaked in a response body, an exploit's output, or an unexpected status code becomes visible to SafeLine. Three options are available both on the plugin and in its plugin metadata:
- AI Proxy Multi (opens in Plugin Hub docs)
- Instance health checks are now reported through the Data Plane control API.
GET /v1/healthchecklists one entry per instance that declareschecks, carrying the newpluginandmetafields (meta.instancenames the instance), and a new sub-resourceGET /v1/healthcheck/{src_type}/{src_id}/checkersreturns every checker a resource owns as an array. A route whose real upstreams were LLM instances previously reported nothing, andGET /v1/healthcheck/{src_type}/{src_id}answeredHTTP 404withno checker for routes[1].
- Instance health checks are now reported through the Data Plane control API.
Control Plane
- A diagnostic agent can now be deployed alongside a gateway and driven from the Dashboard to profile a running Data Plane — CPU profiles and memory snapshots of the gateway's own worker processes — without opening any inbound port into the network the gateway runs in. The agent dials out to the Control Plane over mTLS on the port the Data Plane already uses, and the Dashboard forwards requests to it over that connection. A new cluster-level Diagnostic Agents section under API Runtime lists the agents with their status, opens a diagnostic console for each agent that is online, and generates a ready-to-run Docker command or Kubernetes manifest for a new agent with its certificate, the host PID namespace and the capabilities a diagnostic run needs already filled in. Each agent is named when it is deployed and carries that name in the certificate it is issued, so the list identifies the machine rather than a bare ID.
- The gateway instance configuration compatibility report is now organized by issue rather than by resource.
GET /api/gateway_groups/{gateway_group_id}/instances/{gateway_instance_id}/compatibility_issuesreturns one entry per distinct problem — a machine-readablereason(resource_invalid,plugin_unavailable,plugin_config_invalid,plugin_unknown_fields), the plugin and field it concerns, and the resources carrying it — ordered errors first, then by how many resources are affected. The Data Plane reports each issue as structured data instead of one rendered English sentence per resource, so a single cause is no longer repeated across a thousand rows and an unrecognized-field warning is no longer buried under an aggregated error. The Gateway Instances page in the Dashboard shows the report in this grouped form. - An operator can now dismiss a plugin field the Data Plane does not recognize but safely ignores, so the compatibility report lists only what needs acting on. A dismissal names the plugin and the field and applies across every gateway group and instance, covering every element of an array — dismissing
nodes[*].weightcovers them all. Dismissals can be managed from the Gateway Instances page in the Dashboard or through the/api/compatibility_dismiss_rulesendpoints, are governed by their own permissions, and every change is audited. Only unrecognized-field warnings can be dismissed: dismissing one never changes what the Data Plane does with the configuration, and never turns an Incompatible instance Compatible. - Services can now be found by domain or path prefix. The
searchparameter on the service list endpoint matches, case-insensitively, any substring of a service'shostsorpath_prefixin addition to name, description, labels and ID; whitespace-separated terms keep their AND semantics. A user who knew only a service's domain or path prefix previously had no way to look it up. Services published before the upgrade are searchable this way without being republished.
Data Plane
- The stream proxy can now pass a TLS session through to the backend instead of terminating it at the gateway. A TCP listener declared with
tls_passthrough: trueunderapisix.stream_proxyin the gateway's configuration file reads the SNI from the client's ClientHello, selects the backend from it, and forwards the encrypted session untouched, so the TLS session terminates at the backend. Every connection arriving on such a listener is passed through, and the listener is declared in the gateway's own configuration — in thegateway_conf/config.yamlof a Docker Compose deployment, or in the gateway Helm chart values on Kubernetes. - A request answered with
101 Switching Protocolsis now classified asrequest_type=websocket, so WebSocket sessions land in their ownapisix_http_status,apisix_http_latencyandapisix_bandwidthseries and can be excluded from latency queries withrequest_type!="websocket". Ordinary requests keep their existing series, and a refused handshake staystraditional_http. Dashboards and alerting rules that aggregate these metrics without filtering onrequest_typewill see WebSocket traffic separated out of the series it used to be counted in.
Fixes
Plugins
- Basic Auth (opens in Plugin Hub docs)
- Fixed issue: A consumer whose password contained a colon could never authenticate. RFC 7617 defines everything after the first colon as the password, but the credentials were split on every colon, which truncated it.
- Fixed issue: A consumer whose password was empty — stored as an empty string, or resolved from an
$env://or$secret://reference that yielded one — authenticated any request presenting its username with an empty password. Such a consumer is now rejected withHTTP 401, and an empty password can no longer be configured. See Breaking Changes for what to check before upgrading.
- UA Restriction (opens in Plugin Hub docs)
- Fixed issue: A request carrying more than one
User-Agentheader was allowed when its user agents matched thedenylist, and rejected when they did not — the decision was inverted for every request with more than one such header. A denied client could therefore get through simply by sending the sameUser-Agenttwice. Such a request is now rejected withHTTP 403. A request with a singleUser-Agentheader, and theallowlistbranch, were never affected.
- Fixed issue: A request carrying more than one
- Chaitin WAF (opens in Plugin Hub docs)
- Fixed issue: Setting any option in a route- or service-level
configblock reset every option it did not set, discarding the values configured in the plugin metadata. A route that set onlyread_timeoutsilently revertedreq_body_size,connect_timeoutand the rest to the built-in defaults. A plugin-levelconfignow overrides the metadata field by field, as the documentation already described.
- Fixed issue: Setting any option in a route- or service-level
- AI Cache (opens in Plugin Hub docs)
- Fixed issue: Under the
passthroughprotocol, requests with the same body but a different method, path or query string shared one cache entry, so a request to/v1/images/generationson a wildcard route could be served the cached response of an earlier request to/v1/chat/completions. The cache key for that protocol now includes the client's method, path and query. Other protocols keep their existing keys, so entries cached before the upgrade stay valid.
- Fixed issue: Under the
- AI Proxy (opens in Plugin Hub docs) and AI Proxy Multi (opens in Plugin Hub docs)
- Fixed issue: With the Vertex AI provider, an embeddings request whose
modelcontained a/, a?or a space was sent to the wrong path or failed outright. A model namedmodels/text-embedding-004added a path segment to the prediction URL instead of naming the model, and a?or a space producedHTTP 500withinvalid characters found in pathin the error log — the request was never sent. The model is now escaped into a single path segment. - Fixed issue: When an LLM upstream broke the connection after part of a streaming response had already been delivered to the client, the gateway tried to turn the committed
HTTP 200into a500, which is not possible once the response has started. The client still received the truncated stream, but the error log filled withattempt to set status 500 via ngx.exit after sending out the response status 200, and AI Proxy Multi treated the failure as retryable and could send another billable request to a second instance after the client had already been served part of an answer. The stream now simply ends where it is — without a protocol terminator, so a client detects the truncation from the missing[DONE]— and the broken upstream connection is closed instead of being returned to the keepalive pool.
- Fixed issue: With the Vertex AI provider, an embeddings request whose
- AI AWS Content Moderation (opens in Plugin Hub docs) and AI Aliyun Content Moderation (opens in Plugin Hub docs)
- Fixed issue: A streaming response cut short by the gateway's own
max_response_bytesormax_stream_duration_mslimit was given a synthetic completion event, so the client received a truncated answer as though the model had finished it. The completion event is now appended only to a stream that really completed.
- Fixed issue: A streaming response cut short by the gateway's own
- Traffic Label (opens in Plugin Hub docs)
- Fixed issue: On a route carrying an
ipmatchrule, the compiled matcher was stored inside the plugin's own configuration, so from the first matching request onward the route could no longer be serialized to JSON.GET /v1/routeson the Data Plane control API failed, route dumps atinfolog level came out empty, and matching requests loggedfailed to encode: Cannot serialise table: excessively sparse array.
- Fixed issue: On a route carrying an
- AWS Lambda (opens in Plugin Hub docs), Azure Functions and OpenFunction
- Fixed issue: A
function_uriwith no path —https://xxx.lambda-url.us-east-1.on.awswith no trailing slash, for example — produced a request with an empty request target, which a strict function endpoint rejected withHTTP 400 Bad Request. The request is now sent to/. A function response with status400or above is also logged atwarnwith its status and body; previously the error log carried onlyexits with http status code 403, with no indication of why.
- Fixed issue: A
Data Plane
- Fixed issue: An HTTPS upstream configured with certificate verification and its own CA certificate failed every request with
HTTP 502andupstream SSL certificate verify error: (20:unable to get local issuer certificate)in the error log, even when the upstream certificate really was issued by that CA. The trusted store was never applied to an upstream that supplied a CA certificate without also supplying a client certificate. Such an upstream now connects. - Fixed issue: A
grpcsupstream ignored its certificate verification settings entirely, so an upstream certificate was accepted no matter which CA issued it. The settings were discarded by the internal redirect that dispatches gRPC traffic, and the upstream certificate was in any case checked against the wrong name. Verification and the configured CA certificates now take effect forgrpcsas they do forhttps, and the certificate is checked against the upstream host. Agrpcsupstream that has verification enabled and whose certificate does not chain to the configured CA, or whose subject alternative names do not cover the upstream host, now fails withHTTP 502where it previously connected; check those upstreams before upgrading. - Fixed issue: Two upstreams could share one parsed CA certificate store, so an upstream could be verified against another upstream's CA certificates. The store is now keyed on the CA certificates themselves.
- Fixed issue: A gateway worker could crash while closing a multiplexed upstream connection, which released the memory the connection's log still pointed at. This is reached in practice through the Dubbo Proxy plugin, which drives multiplexed upstream connections.
- Fixed issue: The gateway logged
failed to open file[/usr/local/apisix//conf/apisix.uid] for writingat error level on every start wheneverconf/apisix.uidwas not writable by the runtime user, which is how the offline package ships it. The instance ID was read correctly and the instance registered normally, so nothing was broken, but the lines read like a real fault during triage. The file is now written only when an ID actually has to be persisted. - Fixed issue: On the Data Plane control API,
GET /v1/healthcheckreturned an empty JSON object where an array was expected —"nodes": {}for an upstream configured withchecksbut not yet used, and{}for the whole result when nothing was being health checked. Both are now[].
Control Plane
- Fixed issue: A direct upgrade from 3.8.x on PostgreSQL crashed the Control Plane immediately after the database migration completed, with
ERROR: cached plan must not change result typeandpanic: init ETCD custom_plugins for gateway groups failed. A table read before the migration and again after it reused a statement prepared against the old schema. The connections the migration ran on are now discarded, so those statements go with them. Only PostgreSQL was affected, and only an upgrade from a version below 3.9.2. - Fixed issue: A gateway running a data-plane-only hotfix build was judged against the wrong version. A gateway reporting a version that differs from the Control Plane's only in its pre-release part —
3.10.7-patch.1against a 3.10.7 Control Plane — was shown as PartiallyCompatible with an upgrade hint, and an error-level entry in its compatibility report turned it Incompatible and raised the banner warning that gateway instances need an upgrade, while an identically configured gateway on the base release stayed Compatible. Such a build is now judged as the release it is built on, in both directions, so a hotfix Control Plane no longer flags a gateway on the base release either. - Fixed issue: A gateway instance was looked up without its gateway group and without ordering its runs, so an instance that had been restarted was answered for from a run that no longer exists, for up to seven days. The compatibility issues endpoint returned a report contradicting the summary shown beside it on the same screen, and never converged; deleting a gateway instance could clear the offline guard on the strength of a stale run and remove a gateway that was still connected, still answering
HTTP 200; and health check status reported by the Data Plane could be filed under the wrong gateway group when the same instance ID existed in two groups. - Fixed issue: When Prometheus answered the Control Plane with something other than JSON — an authenticating proxy returning an HTML
401page, for example — the Dashboard's monitoring pages and the Developer Portal's metrics endpoint failed withHTTP 500andinvalid character '<' looking for beginning of value, which said nothing about the real cause. Such a reply is now reported asHTTP 502with a message naming the upstream status, for exampleprometheus returned 401 Unauthorized, and the first 256 bytes of the body go to the Control Plane log instead of to the client. Prometheus's own JSON error responses still pass through unchanged.
Console (Dashboard)
- Fixed issue: The
Results: X–Y of Nlabel under a paginated table or card list showed the wrong range on every page after the first whenever the page size was not the default 10, because the range was computed from a fixed page size rather than the one in use. The rows displayed were always correct; only the label was wrong.
Developer Portal
- Fixed issue: For an external API product, Download OpenAPI Document always saved the file as
api-1.jsonorapi-1.yamlinstead of a name derived from the API. Products backed by a gateway service were already correct. - Fixed issue: Pasting into the input fields of the interactive API documentation viewer, including the Basic Auth credential fields, did not work or mishandled line breaks, so a pasted credential could be dropped or malformed before the request was sent.