feat: detect local-node impersonation/spoofing (#2584)#3390
Conversation
Meshtastic channel messages have no cryptographic sender authentication, so anyone on a channel can forge the `from` field. A packet spoofing our own locally-connected node number was previously shown as one of our outgoing messages (the reported bug). Detection (src/server/utils/spoofDetection.ts, fully unit-tested): - A genuine local TX reaches the host as an INTERNAL/API transport with no rx SNR/RSSI and a fresh hop count. A packet claiming from == our local node but bearing RF-reception markers (rx SNR/RSSI, hop_start > hop_limit, radio transport, or viaMqtt) and NOT recently sent by us is a spoof. - The packet-`id` ring buffer (SentPacketIdCache) suppresses false positives from our own packets overheard/rebroadcast, echoed by MQTT, or replayed by store-and-forward (they reuse an id we originated). Wiring (meshtasticManager): record outgoing packet ids; assessLocalSpoof() at ingestion drives the packet-log `direction` (a spoof is a reception, not 'tx') and sets the new spoof flags on messages and packet_log. Persistence: migration 083 adds messages.spoofSuspected and packet_log.spoof_suspected (SQLite/PG/MySQL); flags written only for suspect rows. UI: spoof-suspected messages are no longer rendered as our own and show a red "⚠️ Possible impersonation of your node" badge; matching rows are highlighted in the Packet Monitor. Per-source throughout. Phase 1 (self-node); PKI-DM verification and the Security-page/notification surface are follow-ups. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Claude finished @Yeraze's task —— View job PR Review: Local Node Impersonation DetectionI've completed a thorough review of this PR. The implementation demonstrates excellent software engineering practices with comprehensive testing and clean architecture. Here's my detailed assessment: ✅ StrengthsCode Quality & Architecture
Robust False-Positive Prevention
Database Implementation
UI Integration
🔍 Technical AssessmentSecurity Considerations✅ No security vulnerabilities identified
Performance Analysis✅ Minimal performance impact
Edge Case Handling✅ Comprehensive edge case coverage
🧪 Test Coverage ExcellenceUnit Tests (21 test cases)
Migration Tests
⚡ Minor ObservationsPotential Enhancements (not blocking)
Documentation
🎯 Verdict✅ APPROVE - This is a high-quality implementation that:
The code demonstrates mature software engineering practices with excellent separation of concerns, thorough testing, and defensive programming. Ready for merge. |
…2584) convertRepoMessage() has an explicit field allowlist that dropped the new flag, so the non-source-scoped /api/messages paths and the unified view returned spoofSuspected:false even for flagged rows. Add it to the mapper and to the local DbMessage interface. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Claude finished @Yeraze's task —— View job PR Review: Local Node Impersonation DetectionI've completed a comprehensive review of this PR implementing local-node impersonation detection. The implementation demonstrates excellent software engineering practices with comprehensive testing and clean architecture. Here's my detailed assessment: Review Checklist:
✅ StrengthsCode Quality & Architecture
Robust False-Positive Prevention
Database Implementation
UI Integration
🔍 Technical AssessmentSecurity Considerations✅ No security vulnerabilities identified
Performance Analysis✅ Minimal performance impact
Edge Case Handling✅ Comprehensive edge case coverage
🧪 Test Coverage ExcellenceUnit Tests (21 test cases)
Migration Tests
⚡ Minor ObservationsPotential Enhancements (not blocking)
Documentation
🎯 Verdict✅ APPROVE - This is a high-quality implementation that:
The code demonstrates mature software engineering practices with excellent separation of concerns, thorough testing, and defensive programming. Ready for merge. |
Summary
Meshtastic channel messages have no cryptographic sender authentication — anyone on a channel can transmit a packet with a forged
fromfield. Previously, a packet that spoofed our own locally-connected node's number was rendered as one of our outgoing messages (the bug in #2584: "first message is genuine outgoing… all others are fake… treated as outgoing despite having snr/rssi/hops"). This adds detection, flags the messages/packets, and stops rendering spoofs as our own.This is Phase 1 (self-node spoofing). PKI-DM crypto verification, broader impersonation-of-other-nodes, and the Security-page/banner/notification surface are intended follow-ups (see the brainstorm in the issue thread).
How detection works
A genuinely self-originated packet reaches the host as an
INTERNAL/APItransport with no rx SNR/RSSI and a fresh hop count (hop_start == hop_limit). A packet claimingfrom == our local nodethat instead bears RF-reception markers (rx SNR/RSSI present,hop_start > hop_limit, a radio transport, orviaMqtt) and was not recently sent by us is flaggedspoofSuspected.The critical false-positive guard is the packet
id: our own packet, overheard/rebroadcast by a neighbour, echoed by the MQTT bridge, or replayed by store-and-forward, reuses anidwe originated. A short-TTL ring buffer (SentPacketIdCache) of ids we sent suppresses those benign echoes. Detection is per-source ("local node" is per-source).Changes
src/server/utils/spoofDetection.ts(new, pure, fully unit-tested) —detectLocalNodeSpoof(),hasRfReceptionMarkers(), and theSentPacketIdCachering buffer.meshtasticManager.ts— records outgoing packet ids;assessLocalSpoof()at ingestion drives the packet-logdirection(a spoof is a reception, nottx) and sets the spoof flags on the message + packet rows.messages.spoofSuspectedandpacket_log.spoof_suspected(SQLite/PG/MySQL, idempotent). Flags are written only for suspect rows.isMyMessagereturns false) and show a red "spoofSuspected/spoof_suspectedthrough the DB types, repositories, REST + WebSocket message mappings, and the frontendMeshMessage/PacketLogtypes.Issues Resolved
Fixes #2584 (Phase 1)
Documentation
CHANGELOG.mdFeatures entry;CLAUDE.mdmigration count bumped to 83.Testing
tsc --noEmitclean; production build cleanspoofDetection.test.ts(21 cases): genuine-local-tx vs spoof, the RF-marker matrix (0-SNR not a marker, travelled hops, MQTT, radio transport), and the echo/MQTT-echo/replay false-positive suppression via the id buffer;SentPacketIdCacheTTL + size-cap eviction.083_add_spoof_suspected.test.ts: columns added, idempotent, default falsey + round-trip, graceful skip when a table is absent./api/messagesreturns"spoofSuspected": falseon genuine messages (a real incoming message from another node is correctly not flagged).from == your local nodeover RF) is needed — hard to reproduce without packet injection. The data pipeline is proven end-to-end above.🤖 Generated with Claude Code