What went wrong
Moquette is a Java MQTT broker. MQTT clients publish and subscribe through named topics, while access-control lists decide which topics each client may read or write. In Moquette, a pattern rule could contain %c for the client ID or %u for the username. A rule such as sensor/%c/# was meant to give every device access to its own branch of the topic tree.
The broker replaced those placeholders with the connecting client's values and then interpreted the result as an MQTT topic filter. That ordering mattered because + and # are not ordinary characters in a filter: they are single-level and multi-level wildcards. Identity data was therefore allowed to become part of the authorization language.
For example, a client ID of + changed sensor/%c/# into sensor/+/#. Instead of naming one client's branch, the resulting filter matched every branch at that level. The same expansion was used for both read and write decisions.
Argo surfaced the unsafe substitution during a source review. I traced it through the authorization path and reproduced the boundary failure in Moquette's real matching logic before reporting it privately. The published advisory credits @gigioneggiando, and the eventual public repair records my authored patch and the maintainer's commit.
Why it mattered
The issue affected deployments that used pattern-based ACL rules containing the client ID or username, especially rules intended to separate tenants or devices. It did not make every Moquette installation vulnerable: the relevant pattern configuration had to exist, and an untrusted client had to be able to connect with an identity containing an MQTT wildcard.
Under those conditions, a client confined to its own topic branch could read messages from another branch or publish messages into it. That is a direct loss of confidentiality and integrity across the boundary the ACL was meant to enforce.
The upstream advisory rates the bundled vulnerability record Critical at CVSS 9.6, with network reachability, low privileges, no user interaction, and high confidentiality and integrity impact. This article covers only the pattern-ACL injection identified as Critical in that record; the same advisory documents several separate denial-of-service and correctness issues.
Versions through 0.18.0 were affected. The advisory lists 0.18.1 and 0.19.0 as patched, and the public 0.18.1 security release strongly recommends upgrading.
How it was fixed
The repair makes the boundary explicit before substitution. Moquette now checks whether the client ID or username contains + or #. If either identity contains an MQTT topic wildcard, the broker skips pattern-based authorization and logs a warning. In other words, it fails closed instead of trying to interpret ambiguous policy text.
The public commit also adds regression tests for wildcard client IDs and usernames across read and write checks. They verify that identities containing + or # cannot use the pattern rule to reach another client's topics, and that the previously crashing malformed-filter case is rejected safely.
The patch was released in Moquette 0.18.1. Existing operators do not need to redesign ordinary tenant patterns, but they do need a fixed version for the identity-to-policy boundary to be enforced by the broker.
After the patch
A template that produces a policy is a small interpreter. Values inserted into it cannot be treated as harmless strings when the destination gives some characters structural meaning.
The review question travels beyond MQTT: can a username, tenant ID, path segment or resource name introduce operators into the language that will consume it? SQL placeholders, regular expressions, route templates and IAM resource patterns all expose versions of the same mistake. Validation is not only about whether a value is well formed in isolation. It must also ask what that value becomes after substitution.