@@ -124,6 +124,9 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length)
124124 if (!pAck)
125125 return ;
126126 pAck->transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MQTT;
127+ // sendLocal consumes packets sent to a live interface, but returns SHOULD_RELEASE
128+ // when it handled a local delivery synchronously. Match MeshService::sendToMesh's
129+ // ownership contract so the MQTT acknowledgement cannot leak on the local path.
127130 if (router->sendLocal (pAck) == ERRNO_SHOULD_RELEASE )
128131 packetPool.release (pAck);
129132 } else {
@@ -158,6 +161,11 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length)
158161
159162 if (shouldDropMqttDownlink (*p))
160163 return ;
164+ // A decoded MQTT packet has already crossed the broker boundary, so authenticate it in place
165+ // before handing it to the router. The routing-auth cache intentionally keeps an authenticated
166+ // copy separate for encrypted packets, but a cache hit alone does not update this packet's
167+ // xeddsa_signed marker (which is part of the downstream message metadata).
168+ bool decodedAuthChecked = false ;
161169
162170 if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
163171 if (moduleConfig.mqtt .encryption_enabled ) {
@@ -175,11 +183,14 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length)
175183 // protection for known signers. Without this, a peer on a plaintext broker could
176184 // impersonate a signing node with unsigned broadcasts. Hold cryptLock like the RF path
177185 // (perhapsDecode) does - checkXeddsaReceivePolicy -> xeddsa_verify mutates shared
178- // CryptoEngine cache state, and MQTT ingress can run on a different task.
179- if (passesRoutingAuthGate (p.get ()) != RoutingAuthVerdict::ACCEPT ) {
186+ // CryptoEngine cache state, and MQTT ingress can run on a different task. The in-place
187+ // call preserves the verified xeddsa_signed marker for downstream routing/UI consumers.
188+ concurrency::LockGuard g (cryptLock);
189+ if (!checkXeddsaReceivePolicy (p.get ())) {
180190 LOG_INFO (" Ignore decoded message failing XEdDSA policy" );
181191 return ;
182192 }
193+ decodedAuthChecked = true ;
183194#endif
184195 }
185196
@@ -191,7 +202,7 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length)
191202 // likely they discovered each other via a channel we have downlink enabled for
192203 if (isToUs (p.get ()) || (nodeInfoLiteHasUser (tx) && nodeInfoLiteHasUser (rx)))
193204 router->enqueueReceivedMessage (p.release ());
194- } else if (router && passesRoutingAuthGate (p.get ()) == RoutingAuthVerdict::ACCEPT )
205+ } else if (router && (decodedAuthChecked || passesRoutingAuthGate (p.get ()) == RoutingAuthVerdict::ACCEPT ) )
195206 router->enqueueReceivedMessage (p.release ());
196207}
197208
0 commit comments