This release includes 3 security fixes for security teams reviewing exposed deployments.
Topics
+12 more
Affected surfaces
Summary
AI summarySecurity hardening across plugins, authentication headers, PROXY‑Protocol sanitization, Docker image fixes; bugfixes in MQTT parsing, rule engine, data integration connectors (Kafka, RocketMQ, MongoDB, GCP PubSub), gateway schema validation, access control export/import restrictions, clustering leaks and observability memory reporting.
Full changelog
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 with403 Forbidden. The previous behavior of accepting any payload named<name-vsn>.tar.gzis preserved when the optionalsha256: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 withproxy_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 feedClientInfoand 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 patchedlibssl3t64. - 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-linked
curlbinary from https://github.com/stunnel/static-curl (OpenSSL,
HTTP/2, HTTP/3; no RTMP, no GnuTLS). Container healthchecks that call
curlcontinue to work unchanged.
- Applied Debian security upgrades during the runtime image build, so the
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.
- Fixed a connection race condition during SASL re-authentication that could drop queued produce requests and cause
-
#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_requiredand 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/exportcalled with an API key now silently omits thedashboard_usersandapi_keysmnesia table sets from the resulting archive.POST /data/importcalled with an API key now returns403 FORBIDDENwhen 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
/usersand/api_keyendpoints 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.
Breaking Changes
- Plugin install allowlist entries now expire after 5 minutes and may be pinned to a SHA‑256 hash; unpinning reverts to previous non‑hash behavior.
- Removed `rel_vsn` from the unauthenticated `/status?format=json` response.
Security Fixes
- Fixed path‑traversal vulnerability in plugin install endpoint (tarball entries with `..` segments).
- Hardened Docker image by applying Debian security upgrades and removing unused `libgnutls30t64` package.
- Disabled XML entity expansion in SAML processing via `esaml` v1.1.5 upgrade.
Weekly OSS security release digest.
The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.
No spam, unsubscribe anytime.
Share this release
About EMQX
Scalable MQTT broker. Connect 100M+ IoT devices in one single cluster, move and process real-time IoT data with 1M msg/s throughput at 1ms latency.
Related context
Related tools
Beta — feedback welcome: [email protected]