fix(security): a kept route no longer grants the contact remote CLI

CONTACT_FLAG_ROUTE_PINNED was bit 4 (0x10) of ContactInfo::flags, which
upstream 1.18 made isRemoteCLIAllowed(): keeping a route to a contact let it
run any CLI command on the device remotely (radio, name, PIN, reboot,
wifi.pwd), and a contact the app allowed CLI had its route held. The kept
route moves to bit 7 (0x80). Not in any release (the route editor came
after rc3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-10-15 15:42:14 +02:00
co-authored by Claude Opus 5.5
parent ad8e3f859f
commit 0346cebaf6
2 changed files with 6 additions and 5 deletions
+1 -1
View File
@@ -150,7 +150,7 @@ consumes keys only.
- Fonts ✅ Noto Sans generated by `ui-lvgl/fonts/generate.sh` (lv_font_conv): European Latin (Latin-1, Extended-A/B), Greek, Cyrillic + supplement, typographic punctuation, currency, LVGL symbols; 12/14/16/20 px, clock 40 px digits only. On-screen keyboard (`ui-lvgl/Keyboard.h`), phone-style: one layout per script (Latin QWERTY / Cyrillic ЙЦУКЕН / Greek) chosen by the same `keyboard_main_alphabet` / `keyboard_alt_alphabet` prefs as `ui-new` (globe key switches), hold a key for its variants; compose limit counted in UTF-8 bytes. Long-press variants and UTF-8 case mapping are shared with `ui-new` in `ui-core/KeyboardData.h`.
- Simulator ✅: `SIM_UI=lvgl variants/sim/build_wasm.sh` builds the same `ui-lvgl` code for the browser (`SimLcdDisplay` in `variants/sim/SimDisplayDriver.h` blits LVGL's RGB565 flush to a 320×240 canvas; the `SIM_PLATFORM` branch of `ui-lvgl/LvglPort.h` feeds the mouse in as touch). LVGL comes from the L2 env's PlatformIO libdeps; its objects are cached in `web/build/obj_lvgl/`. `web/lvgl.html` runs it next to a `ui-new` peer on a direct link (buttons: advert, peer DM, peer → Public, the board button) and exposes `window.sim` (`tap`, `hold`, `touch`, `peerDM`, `peerChannel`) for headless checks.
- Map (in progress): offline Web-Mercator raster tiles on the microSD (`/maps/{z}/{x}/{y}.png`, the Meshtastic MUI layout; optional per-column `.pak` from PR #3381's packer), SDMMC 1-bit mount in `LvglPort.h`. `ui-lvgl/map/TileProvider.h` is the swap point: the screen and `map/TileCache.h` (24 decoded RGB565 tiles in PSRAM, LRU) only call `renderTile(z, x, y, rgb565)`, so a vector renderer can replace the PNG provider later. `MapScreen.h`: pan by drag, +/-, re-centre/follow GPS, Nearby nodes with a position as markers (tap = node detail), one tile decoded per loop pass, never during a drag (the view fills in once the finger rests; an idle map decodes the ring around the view ahead, and a not-yet-decoded tile shows a cached coarser one meanwhile). `maps/attribution.txt` is behind a "©" on the map and in Settings > About. Two maps over the same tile view: Nearby's header opens the Nodes map (every node Nearby knows a position for), Home > Map the Navigation map (`NavMap.h`): saved waypoints, live shares, the recorded trail and the Locator target with a dashed line to it and a bar with distance / bearing / own course / ETA (`ui-core/EtaTracker.h`, shared with ui-new's NavView). Hold the map to drop a waypoint, the pin marks the GPS position; the bar opens the target list and the waypoint menu (go, rename, share as `[WAY]` into a conversation, delete). Node detail has Navigate (target followed by key); a person sharing on a channel is followed by sender name (Locator target kind 2). Settings > Navigation carries the `track_shared_loc` / `locator_enabled` switches ui-new has on its Live share / Locator screens. Tiles come from `tools/maps/fetch_tiles.py` (no default server: tile.openstreetmap.org blocks offline use) or on the device: the map's download button fetches the visible area (a few overview levels up to a chosen zoom) over WiFi (`map/TileDownloader.h`; skips tiles on the card, rejects non-PNG and placeholder answers; server from `maps/source.txt`, default OpenTopoMap). The HTTP GET runs on a worker task (`LvglPort.h`, keep-alive TLS); WiFi credentials (Settings > WiFi, with scan) live in NVS, and WiFi is on only during a download. The job (area + zoom range) stays in `maps/.job` until it completes, so a download cut short by power-off, lost WiFi or Stop is offered for Resume (tiles already on the card are skipped). Past the downloaded detail the map magnifies the nearest coarser tile it has (up to 2 levels -- more is just big pixels; zoom pill shows e.g. `z17 (map z15)`); known-missing tiles are remembered without a pixel buffer. Internal RAM is the constraint (Arduino-ESP32 2.0.17 / IDF 4.4: task stacks and, by default, mbedTLS buffers must be internal): with WiFi + Bluedroid up only ~13 KB was left, so the L2 LVGL env sets `MESH_IN_PSRAM` (`the_mesh`, ~120 KB, placement-new into PSRAM in `main.cpp`) and the fetch task points mbedTLS at PSRAM. The download popup shows IP, RSSI, free/largest internal heap and the last TLS/HTTP error. Arduino-ESP32 3.x (IDF 5: PSRAM stacks, NimBLE) is to be evaluated in the polishing phase. In the simulator the same code downloads through emscripten fetch.
- Route and trace ✅ (tested on the L2 to a direct neighbour): `ui-core/PathEditor.h` (`UiCore::route`) holds the repeaters between this node and a contact -- loaded from the contact's stored path, edited (add / move / remove), traced and saved (`MyMesh::setContactPath`; an empty list saves "direct"). A trace goes out and back over `MyMesh::sendTrace` (A B dest B A, or A B A with `toNode()` off: only a node that retransmits can answer, so a companion target needs repeat on); `PingEngine::startTrace` keeps the SNR of every entry (`PingResult::snrs_x4`, up to `TRACE_SNR_MAX`) and `PathEditor` turns them into links (`linkSnrX4(k)`: node k hearing k-1, and k-1 hearing k). After no reply, `probe()` traces to the 1st, 2nd, ... repeater and stops at the first that doesn't answer (`reached()`). Failures carry a reason (`failure()`: no key, busy, send, nothing to trace); `PingEngine` takes over a slot left past its timeout by a view that went away. Views: ui-lvgl's Route screen (node detail > Route and trace; `RouteScreen.h`) with a picker by distance and "Map" -- the nodes map where tapping repeaters in order builds the route (numbered badges, a line); ui-new's Path view (`RouteView` in `NearbyScreen.h`, the node's action menu > Path / trace) with Add handing the Nodes list over filtered to repeaters. A saved route can be kept (`CONTACT_FLAG_ROUTE_PINNED`, `ContactInfo::flags` bit 4; "Keep" in both views): `MyMesh::onContactPathRecv` puts the held path and its lastmod back after the base class took a heard one, without announcing it to the app (`_path_kept`); `tickDmResends` doesn't drop it before the last resend; an app update that leaves the route as it is keeps the flag, one that changes it ends it; Reset path (device or app) clears it. The ui-lvgl map's markers are one drawn layer (`mapview::drawMarks`, hit-tested by `hitMark`), not an object each.
- Route and trace ✅ (tested on the L2 to a direct neighbour): `ui-core/PathEditor.h` (`UiCore::route`) holds the repeaters between this node and a contact -- loaded from the contact's stored path, edited (add / move / remove), traced and saved (`MyMesh::setContactPath`; an empty list saves "direct"). A trace goes out and back over `MyMesh::sendTrace` (A B dest B A, or A B A with `toNode()` off: only a node that retransmits can answer, so a companion target needs repeat on); `PingEngine::startTrace` keeps the SNR of every entry (`PingResult::snrs_x4`, up to `TRACE_SNR_MAX`) and `PathEditor` turns them into links (`linkSnrX4(k)`: node k hearing k-1, and k-1 hearing k). After no reply, `probe()` traces to the 1st, 2nd, ... repeater and stops at the first that doesn't answer (`reached()`). Failures carry a reason (`failure()`: no key, busy, send, nothing to trace); `PingEngine` takes over a slot left past its timeout by a view that went away. Views: ui-lvgl's Route screen (node detail > Route and trace; `RouteScreen.h`) with a picker by distance and "Map" -- the nodes map where tapping repeaters in order builds the route (numbered badges, a line); ui-new's Path view (`RouteView` in `NearbyScreen.h`, the node's action menu > Path / trace) with Add handing the Nodes list over filtered to repeaters. A saved route can be kept (`CONTACT_FLAG_ROUTE_PINNED`, `ContactInfo::flags` bit 7 -- bit 4 is upstream's remote CLI; "Keep" in both views): `MyMesh::onContactPathRecv` puts the held path and its lastmod back after the base class took a heard one, without announcing it to the app (`_path_kept`); `tickDmResends` doesn't drop it before the last resend; an app update that leaves the route as it is keeps the flag, one that changes it ends it; Reset path (device or app) clears it. The ui-lvgl map's markers are one drawn layer (`mapview::drawMarks`, hit-tested by `hitMark`), not an object each.
- Packet flow ✅ (sim only): `ui-core/PacketFlow.h` (`UiCore::flow`) turns every packet heard (`MyMesh::logRx` -> `Listener::onPacketHeard`) into a flow -- origin (an advert's position / a message's source hash), the path's repeaters with a position (resolved back from this device, each hash nearest the point after it), this device -- and a packet sent (`logTx`) into a pulse; ring of `MAX_FLOWS`, nothing resolved while `NodePrefs::flow_show` is off. ui-lvgl draws them on the map's marker layer (`mapview::drawFlows`: a dot running the line, a fading trail, rings) and repaints only their area every 50 ms (`UITask::mapFlowTick`); ui-new draws the last minute's lines on the Nodes map (`NearbyScreen::renderMap`, toggle "Flow" in its options). Setting: Map options > On the map > Packet flow, CLI `map.flow`.
- PR #3381's LVGL UI is a reference for platform glue (flush, GT911, PSRAM pool) only; its screens are not adopted.
+5 -4
View File
@@ -5,11 +5,12 @@
#define OUT_PATH_UNKNOWN 0xFF
// ContactInfo::flags bit 4: the route in out_path was set by hand (the device's
// ContactInfo::flags bit 7: the route in out_path was set by hand (the device's
// Route editor) and is kept: a path heard from the contact, or a message that
// goes unanswered, doesn't replace it. Bit 0 is the favourite star, bits 1-3 the
// telemetry permissions. Reset path (or a new route saved) clears / sets it.
#define CONTACT_FLAG_ROUTE_PINNED 0x10
// goes unanswered, doesn't replace it. Reset path (or a new route saved) clears
// / sets it. Upstream's bits, below: 0 the favourite star, 1-3 the telemetry
// permissions, 4 remote CLI -- the top bit is the one furthest from theirs.
#define CONTACT_FLAG_ROUTE_PINNED 0x80
struct ContactInfo {
mesh::Identity id;