From 58c2a779f5849b4709663858f24bed7f8b1b200f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 29 Jul 2026 23:38:52 +0200 Subject: [PATCH 1/4] Redact the pairing PIN from unauthorized lockdown clients --- src/mesh/PhoneAPI.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 572b7924240..f86d2577398 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -767,6 +767,12 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) LOG_DEBUG("Send config: bluetooth"); fromRadioScratch.config.which_payload_variant = meshtastic_Config_bluetooth_tag; fromRadioScratch.config.payload_variant.bluetooth = config.bluetooth; +#ifdef MESHTASTIC_PHONEAPI_ACCESS_CONTROL + if (!getAdminAuthorized()) { + // The pairing PIN is a shared secret; never expose it to an unauthenticated client. + fromRadioScratch.config.payload_variant.bluetooth.fixed_pin = 0; + } +#endif break; case meshtastic_Config_security_tag: LOG_DEBUG("Send config: security"); From cc2db6de3cdf44d4fac929773fc35701e4a2ef3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 29 Jul 2026 23:38:52 +0200 Subject: [PATCH 2/4] Fail the build when PacketAPI would bypass the lockdown gate --- src/mesh/api/PacketAPI.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/mesh/api/PacketAPI.cpp b/src/mesh/api/PacketAPI.cpp index 3f145bfe3f1..9408e926657 100644 --- a/src/mesh/api/PacketAPI.cpp +++ b/src/mesh/api/PacketAPI.cpp @@ -6,6 +6,12 @@ #include "RadioInterface.h" #include "modules/NodeInfoModule.h" +#ifdef MESHTASTIC_PHONEAPI_ACCESS_CONTROL +// receivePacket() dispatches ToRadio straight to MeshService, bypassing handleToRadioPacket and so +// the lockdown admin gate. Fail the build rather than silently ship an admin-auth bypass. +#error "USE_PACKET_API is incompatible with MESHTASTIC_PHONEAPI_ACCESS_CONTROL (PacketAPI bypasses the lockdown admin gate)" +#endif + PacketAPI *packetAPI = nullptr; PacketAPI *PacketAPI::create(PacketServer *_server) From 5b69a487eaa255eb82d349ebd1786e52ee7d6e67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 29 Jul 2026 23:38:52 +0200 Subject: [PATCH 3/4] Compact kept favorites when resetting the node database --- src/mesh/NodeDB.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index cdd61d15f5f..0cf9186b5da 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1607,15 +1607,19 @@ void NodeDB::resetNodes(bool keepFavorites) numMeshNodes = 1; if (keepFavorites) { LOG_INFO("Clearing node database - preserving favorites"); - for (size_t i = 0; i < meshNodes->size(); i++) { + // Compact favorites into contiguous low slots: zeroing in place leaves one above + // numMeshNodes, invisible to every `i < numMeshNodes` scan yet still serialized to flash. + for (size_t i = 1; i < meshNodes->size(); i++) { meshtastic_NodeInfoLite &node = meshNodes->at(i); - if (i > 0 && !nodeInfoLiteIsFavorite(&node)) { - eraseNodeSatellites(node.num); - node = meshtastic_NodeInfoLite(); - } else { + if (nodeInfoLiteIsFavorite(&node)) { + if (numMeshNodes != i) + meshNodes->at(numMeshNodes) = node; numMeshNodes += 1; + } else if (node.num) { + eraseNodeSatellites(node.num); } - }; + } + std::fill(nodeDatabase.nodes.begin() + numMeshNodes, nodeDatabase.nodes.end(), meshtastic_NodeInfoLite()); } else { LOG_INFO("Clearing node database - removing favorites"); for (size_t i = 1; i < meshNodes->size(); i++) { From 7411eb30c2845a9f9767242990fef39135641dfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 30 Jul 2026 08:29:29 +0200 Subject: [PATCH 4/4] Make the compaction loop reference const --- src/mesh/NodeDB.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 0cf9186b5da..3ba46ccf298 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1610,7 +1610,7 @@ void NodeDB::resetNodes(bool keepFavorites) // Compact favorites into contiguous low slots: zeroing in place leaves one above // numMeshNodes, invisible to every `i < numMeshNodes` scan yet still serialized to flash. for (size_t i = 1; i < meshNodes->size(); i++) { - meshtastic_NodeInfoLite &node = meshNodes->at(i); + const meshtastic_NodeInfoLite &node = meshNodes->at(i); if (nodeInfoLiteIsFavorite(&node)) { if (numMeshNodes != i) meshNodes->at(numMeshNodes) = node;