Security Hardening
- #17200 Plugin install allowlist entries (
emqx ctl plugins allow <name-vsn>) now expire 5 minutes after they are issued, and may be pinned to a SHA-256 hash of the package.
emqx ctl plugins allow <name-vsn> sha256:<HEX> accepts a 64-character lowercase hex digest; uploads whose contents do not hash to that value are rejected with 403 Forbidden. The previous behavior of accepting any payload named <name-vsn>.tar.gz is preserved when the optional sha256: argument is omitted.
-
#17188 Removed the EMQX release version (
rel_vsn) from the unauthenticatedGET /status?format=jsonresponse to avoid disclosing the broker version to unauthenticated callers. The version remains available via the authenticated node-info APIs. -
#17200 Fixed a path-traversal vulnerability in the plugin install endpoint. A tarball whose entry name contained
..segments could cause file writes outside the plugin install directory. The install path now refuses to extract any tarball whose entries would escape the install directory. -
#17202 A successful plugin install via
POST /api/v5/plugins/install(and the dashboard upload that wraps it) now immediately revokes the cluster-wideemqx ctl plugins allow <name-vsn>entry that authorized the upload, so the same grant cannot be reused for a subsequent (potentially different) tarball. The 5-minute TTL still applies; this change closes the window earlier on the common path. -
#17314 Sanitize PROXY-Protocol v2 SSL Common Name / Subject before they enter client identity. When a listener is configured with
proxy_protocol = true, the broker now rejects connections whose PROXY-Protocol SSL TLV bytes contain ASCII control characters (the same byte class already rejected on MQTT-ingested clientid/username/password). This blocks attacker-controlled bytes from being smuggled into outbound HTTP authentication, authorization, or rule-engine header values via${cert_common_name}and${cert_subject}templates.
As an additional defense layer, the HTTP authentication and authorization clients now refuse to send a request when a rendered header name or value contains a CR, LF, or NUL byte.
-
#17322 Extend the byte-class check applied to MQTT clientid / username / password to other fields that feed
ClientInfoand HTTP request templating: -
peersni(TLS Server Name Indication; also accepted from the PROXY-Protocol v2authorityTLV) is now validated at the connection ingestion boundary. Control characters cause the connection to be rejected and a warning logged. - Client attribute values produced by
mqtt.client_attrs_initVariform expressions are dropped (with a warning) when they contain control characters, so templates such as${client_attrs.tns}cannot carry injected bytes downstream. -
HTTP action / bridge connector header rendering now drops any header whose rendered name or value contains NUL, CR, or LF.
-
#17581 Fixed the JT/T 808 gateway to use the phone number accepted during authentication as the connection identity, rejecting mismatched registration-code authentication attempts and subsequent uplink frames with a different phone number.
-
#17276 Hardened the official EMQX docker image to clear image-scanner findings:
-
Applied Debian security upgrades during the runtime image build, so the image picks up the latest patched
libssl3t64. - Removed the unused
libgnutls30t64package. EMQX talks TLS via OpenSSL through Erlang/OTP and never links GnuTLS, so it was only present as a transitive dependency ofcurland showed up in scanner reports. - Replaced the Debian
curlpackage — which would have transitively re-introducedlibgnutls30t64vialibrtmp1— with a statically-linkedcurlbinary from https://github.com/stunnel/static-curl (OpenSSL, HTTP/2, HTTP/3; no RTMP, no GnuTLS). Container healthchecks that callcurlcontinue to work unchanged.
Bug Fixes
Core MQTT Functionalities
- #17573 Reduced MQTT v5 user-property parsing cost from quadratic to linear.
Previously a CONNECT, PUBLISH or SUBSCRIBE packet carrying many user-properties caused super-linear scheduler time on the owning connection process, because each parsed property was appended to the end of the accumulated list. Parsing now scales linearly with the number of entries while preserving their wire order.
Rule Engine
- #17210 Added the
connected_atfield to the$events/client/connackRule Event, which was stated in the documentation but missing from the actual data.
Data Integration
-
#17085 Fixed an issue with MQTT Sources in which, if its Connector used
clean_start = falseand reconnected to a broker with a session containing messages, those messages would not trigger rule actions. -
#17109 Fix query execution for PostgreSQL connectors in disable prepared statements mode. Previously, concurrent queries could interleave and produce errors.
-
#17112 Fixed RocketMQ connector isolation: a misconfigured or unreachable RocketMQ connector no longer destabilises other RocketMQ connectors on the same node. Previously, one connector with an unreachable broker could stall the shared client supervisor for up to 60 seconds, causing sibling connectors to flap with
resource_health_check_timed_outand for dashboard operations on them to hang.
The default TCP/TLS connect timeout is also lowered from 60 seconds to 10 seconds so a misconfigured server surfaces as failed quickly instead of appearing stuck.
- #17179 Fixed an issue where, under heavy load, a timed out call to a MongoDB process would be interpreted as an unrecoverable error and wouldn't be retried. Now, the message will be retried on such events.
On such events, logs like the following would be printed:
{"stacktrace":["{emqx_mongodb,on_query,3,[{file,\"emqx_mongodb.erl\"},{line,236}]}","{emqx_resource_buffer_worker,apply_query_fun,9,[{file,\"emqx_resource_buffer_worker.erl\"},{line,1514}]}","{emqx_resource_buffer_worker,call_query2,8,[{file,\"emqx_resource_buffer_worker.erl\"},{line,1355}]}","{emqx_resource_buffer_worker,do_flush,2,[{file,\"emqx_resource_buffer_worker.erl\"},{line,768}]}","{gen_statem,loop_state_callback,11,[{file,\"gen_statem.erl\"},{line,3735}]}","{proc_lib,init_p_do_apply,3,[{file,\"proc_lib.erl\"},{line,329}]}"],"request":"...","name":"call_query","id":"action:mongodb:xxx:connector:mongodb:xxx","error":"{error,{case_clause,{error,{timeout,{gen_server,call,[<0.215642180.3>,{checkout,#Ref<0.2539918795.141557761.165483>,true},5000]}}}}}"}...
- #17301 Upgraded Kafka client libraries:
brod4.5.2 → 4.5.4 andwolff4.1.7 → 4.1.10.
This brings the following user-visible fixes to Kafka producer and consumer integrations:
- Fixed a connection race condition during SASL re-authentication that could drop queued produce requests and cause
syncproduce calls to time out. -
Improved leader-connection reconnection so that stale dead connections are no longer returned right after an idle-timeout disconnect.
-
#17346 Upgraded the RocketMQ client dependency to
v0.7.2to fix memory growth in async producer requests. -
#17414 Fixed an issue where the health check of an Azure Blob Storage Connector could timeout, or generate large bandwidth costs, if the storage account contained too many containers. Companion fix to [#16935].
-
#17567 Upgraded Kafka client library
brodfrom 4.5.4 to 4.5.5.
Fixed Kafka consumer group join failures against Kafka 2.2.0, where the broker returns member_id_required and brod previously discarded the assigned member ID instead of using it for the retry.
-
#17597 Fixed a connection failure to MongoDB 8.0+ when authentication is required. The driver previously queried
buildInfobefore authentication to pick the auth mechanism; MongoDB 8.0 restricted that command to authenticated callers. The driver now skips the probe and uses SCRAM-SHA-1 directly, which all supported MongoDB versions accept. -
#17624 Fixed an issue with GCP PubSub Consumer Source where, if a source was initially created with a service account lacking necessary permissions to create subscriptions for the configured topic, the Source would fail to become
connectedeven after granting the permissions to the service account.
Gateway
- #17426 Fixed the JT/T 808 gateway schema validation to allow empty or omitted
registryandauthenticationURLs whenallow_anonymousis set totrue. Previously, thenot_emptyvalidator was applied to both fields regardless of theallow_anonymoussetting, causing a 400 error when submitting an empty string for these URLs even though they are not used in anonymous mode.
Access Control
- #17169 Restrict API keys from exporting or importing dashboard accounts and API keys via the data backup endpoints.
POST /data/export called with an API key now silently omits the dashboard_users and api_keys mnesia table sets from the resulting archive. POST /data/import called with an API key now returns 403 FORBIDDEN when the uploaded backup contains either of those table sets.
Dashboard bearer-token (login) callers are unaffected and continue to be able to back up and restore the full database, including dashboard users and API keys.
This closes a privilege-escalation gap where an API key holder could read or write dashboard login credentials and API key records — material that the existing /users and /api_key endpoints already deny to API keys — by going through the data backup endpoints instead.
-
#17451 #17553 Restricted backup file downloads so only dashboard administrators can download archives containing dashboard accounts or API key records, while API key callers can still download archives without those sensitive records.
-
#17539 Upgraded the
esamldependency tov1.1.5to disable XML entity expansion when parsing SAML responses and metadata, preventing crafted SAML XML from expanding external or custom entities during SAML SSO processing. -
#17645 Fixed an HTTP/1.1 protocol-conformance issue in the JWKS retrieval client used by JWT authentication. Earlier versions sent an empty
TE:header value due to a long-standing default in Erlang/OTP'sinetsHTTP client (fixed upstream in inets 9.4.2 / OTP 28.1). Some identity providers (notably PingFederate) reject such requests with503or a TCP reset. EMQX now sends an explicit validTE: trailersheader on JWKS fetches.
Clustering
-
#17220 Avoid
bin/emqxandbin/emqx_ctlinvocations from triggeringnodeup/nodedownevents on the running broker, which previously surfaced as misleadingcm_registry_node_downwarnings in the broker log. The temporary helper nodes started by these scripts now register as hidden Erlang nodes, as intended. -
#17306 Fixed cluster configuration import failing with a "required_field: node.cookie" schema check error when the exported
cluster.hoconcontained a partialnodesection. Read-only roots (node,rpc) are not part of the data import anyway, so they are now dropped from the imported config before the pre-flight schema check, letting the running node's own values be used for the validation. -
#17424 Fixed a global session registry leak that could leave duplicate or stale entries for the same client ID.
Discard and takeover-kick RPC handlers now also remove the registry row when the target process is no longer alive, and the registration throttle on the connect path now recognizes tombstone rows (no local channel state) and reaps them instead of blocking new connections for the same client ID indefinitely.
Observability
- #17255 Improved memory-usage reporting inside containers. The broker now picks the most constraining memory reading among cgroup v2, cgroup v1, and the host's
/proc/meminfo(smallest non-zero total wins, larger usage ratio breaks ties). Previously the reading could be misleading in two ways: on containers with a tight cgroup limit, the host view could indicate >70% while the cgroup limit was <10% (or the reverse); and on hosts where a cgroup is mounted with no memory limit set, the cgroup reading could collapse the reported usage ratio to ~0%. Overload-protection thresholds and theMemory usedmetric now reflect the limit that actually constrains the process.