From dbdf2d732f298ca97d8bdb131e879a122f31126c Mon Sep 17 00:00:00 2001 From: agessaman Date: Tue, 4 Aug 2026 14:04:39 -0700 Subject: [PATCH] fix(mqtt): scan every slot for a stale JWT after a clock correction The post-NTP-correction refresh looped to _max_active_slots, which is a count of activation positions and never an index bound. The indices holding those positions are not contiguous: a slot passed over by isSlotReady() -- or, since the demand-driven work, by a failed setup -- leaves a higher index activated. On a two-position board that meant slots 1 and 2 could be live while only indices 0 and 1 were scanned, so slot 3 kept a JWT issued against the pre-correction clock until its own expiry or a reconnect regenerated it. Now bounded by RUNTIME_MQTT_SLOTS. The existing guard already skips disabled, non-JWT, and clientless slots, so widening the range cannot touch a slot that was never set up. Pre-existing (the loop predates the demand-driven work) and kept as its own commit so it can be picked separately. Audited the other _max_active_slots uses: all are count comparisons or log arguments, so this was the only misuse. --- src/helpers/bridges/MQTTBridge.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/helpers/bridges/MQTTBridge.cpp b/src/helpers/bridges/MQTTBridge.cpp index 89373341..735999e9 100644 --- a/src/helpers/bridges/MQTTBridge.cpp +++ b/src/helpers/bridges/MQTTBridge.cpp @@ -3925,7 +3925,13 @@ bool MQTTBridge::syncTimeWithNTP(bool force, bool primary_only) { // and re-setup all JWT-authenticated slots so they get fresh tokens. if (_slots_setup_done && was_ntp_synced) { unsigned long current_time = (unsigned long)time(nullptr); - for (int i = 0; i < _max_active_slots; i++) { + // Every slot, not _max_active_slots: that is a count of positions, never an + // index bound. Which indices hold those positions is not contiguous — a slot can + // fail isSlotReady() or its setup and be passed over, leaving a higher index + // activated — so bounding by the cap silently skipped an activated slot and left + // it holding a JWT issued against the pre-correction clock. The guard below + // already excludes disabled, non-JWT, and clientless slots. + for (int i = 0; i < RUNTIME_MQTT_SLOTS; i++) { bool slot_jwt = (_slots[i].preset && _slots[i].preset->auth_type == MQTT_AUTH_JWT) || (!_slots[i].preset && _slots[i].audience[0] != '\0'); if (_slots[i].enabled && slot_jwt && _slots[i].client) {