feat: warn user on inbound LXMF messages with unverified signatures#876
feat: warn user on inbound LXMF messages with unverified signatures#876torlando-tech wants to merge 1 commit into
Conversation
Greptile SummaryThis PR closes a consumer-side LXMF signature-forgery gap by threading a
Confidence Score: 4/5Safe to merge for Kotlin-backend users; the Python-backend path has a false-positive warning bug that should be fixed before shipping to users running the Python bridge. The Kotlin backend wiring, Room migration, and UI rendering are all correct. On the Python backend path,
Important Files Changed
Sequence DiagramsequenceDiagram
participant LXMFkt as LXMF-kt Router
participant NativeBackend as NativeRnsBackendImpl
participant PyBridge as PythonEventBridge
participant Collector as MessageCollector
participant Repo as ConversationRepository
participant Room as Room DB (messages)
participant UI as MessagingScreen / MessageDetailScreen
LXMFkt->>NativeBackend: registerDeliveryCallback(LXMessage)
Note over NativeBackend: signatureValidated = message.signatureValidated
NativeBackend->>Collector: handleIncomingMessage(message)
Collector->>Repo: saveMessage(DataMessage(signatureVerified))
PyBridge->>Collector: "_messages.tryEmit(ReceivedMessage(signatureVerified=dictBool(...)))"
Note over PyBridge: dictBool defaults to false if key absent
Repo->>Room: INSERT MessageEntity(signatureVerified INTEGER)
Room-->>UI: MessageEntity.toMessage().signatureVerified
UI->>UI: "if signatureVerified == false → UnverifiedSenderChip"
UI->>UI: signatureVerified?.let → Signature info card
Prompt To Fix All With AIFix the following 1 code review issue. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 1
rns-backend-py/src/main/kotlin/network/columba/app/rns/backend/py/PythonEventBridge.kt:251
**`dictBool` defaults to `false`, not `null`, when the key is absent**
`PyObject.dictBool` (defined in `PythonExt.kt`) returns `Boolean` (non-nullable) with a fallback of `false` when the key is missing from the payload — unlike every other nullable dict helper in that file. If `event_bridge.py` does not yet include `signature_validated` (older bridge version, partial rollout, or any edge case), `signatureVerified` is set to `false` instead of `null`. The UI treats `false` as a confirmed unverified sender and renders "⚠ Unverified sender — may be forged" on every such message, producing a security false alarm for every message delivered through an un-updated Python bridge. `null` is the correct sentinel for "state unknown" — the UI already handles it as "no warning". Use `dictGet("signature_validated")?.toJava(Boolean::class.javaObjectType)` directly, or introduce a `dictBoolOrNull` helper parallel to `dictDouble`.
Reviews (4): Last reviewed commit: "feat: warn user on inbound LXMF messages..." | Re-trigger Greptile |
ca38e48 to
680ee98
Compare
LXMF wire encryption protects confidentiality, not authenticity: anyone who knows a recipient's destination hash can encrypt a forged packet claiming any source identity. The signature is what proves authenticity. This surfaces the per-message signature-verification state through the receive path so the UI warns on senders whose signature could not be verified (the forgery class), mirroring Sideband's warn-and-ingest pattern rather than silently dropping (which would lose legitimate first-contact messages). Both backends are covered: - Kotlin (NativeRnsBackendImpl): reads LXMessage.signatureValidated. - Python (PythonEventBridge): reads signature_validated from the event_bridge.py delivery payload (already emitted upstream). Data path: ReceivedMessage.signatureVerified (@parcelize — crosses the :reticulum -> UI IPC seam) -> MessageEntity (Room) -> Message -> MessageUi -> UnverifiedSenderChip rendered above any bubble shape, plus a Signature card in the message-detail screen. The flag is a definite true/false for received messages and null for sent/legacy rows (treated as "no warning"). Room schema 2 -> 3: additive MIGRATION_2_3 adds the nullable signatureVerified column (legacy rows backfill null), wired into both ColumbaDatabase builders. Backend asymmetry worth noting: python LXMF delivers both SOURCE_UNKNOWN and SIGNATURE_INVALID to the app (the python flavor warns on both), whereas LXMF-kt drops SIGNATURE_INVALID at the router (the kotlin flavor warns on SOURCE_UNKNOWN only). Neither ever presents a forgeable message as trusted. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
680ee98 to
63b0359
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Summary
Warns the user on inbound LXMF messages whose sender signature could not be verified — the signature-forgery class where anyone who knows a recipient's destination hash can encrypt a packet claiming any source identity. Mirrors Sideband's app-layer warn-and-ingest pattern (not silent-drop), so legitimate first-contact messages aren't lost.
How it works
The LXMF layer already determines, per delivered message, whether the signature verified against a known sender identity. This PR threads that state to the UI on both backends:
NativeRnsBackendImpl): readsLXMessage.signatureValidated.PythonEventBridge): readssignature_validated, whichevent_bridge.pyalready emits on the delivery payload.Data path:
signatureVerifiedsemantics:true— signature checked against a known source identity, valid.false— signature could not be verified; UI warns.null— sent messages (signed locally) and legacy rows; treated as "no warning".Backend asymmetry (intentional, documented)
Python LXMF's
lxmf_deliverydelivers bothSOURCE_UNKNOWNandSIGNATURE_INVALIDto the app, so the Python flavor warns on both. LXMF-kt's router dropsSIGNATURE_INVALIDbefore the app sees it, so the Kotlin flavor warns onSOURCE_UNKNOWNonly. Neither ever presents a forgeable message as trusted. (Aligning the two — e.g. distinguishing the reason in the UI, or revisiting the LXMF-kt drop — is a possible follow-up, tracked separately.)Room migration
Schema
2 → 3. AdditiveMIGRATION_2_3adds the nullablemessages.signatureVerified INTEGERcolumn; existing rows backfill toNULL("no warning" — showing every legacy message as unverified would be inaccurate). Wired into bothColumbaDatabasebuilders (DatabaseModule+ the:reticulum-processServiceDatabaseProvider). Covered by a migration round-trip test.UI
UnverifiedSenderChip(newui/components/composable, alongside the existing chips/banners) rendered above the bubble whensignatureVerified == false, usingcolorScheme.errorContainerto match existing warning affordances.MessageDetailScreengains a "Signature" info card (Verified / Unverified + threat-model explanation).Wire compatibility
Unchanged. No change to LXMessage decoding, router behavior, or on-the-wire bytes. Cross-impl interop with python LXMF and iOS LXMF is unaffected — the only changes are the new local DB column and the in-app warning.
Test plan
ktlintCheck detekt cpdCheckgreenrns-api,rns-ipc,rns-backend-kt,rns-backend-py,dataincl. newMigration2To3Test) green🤖 Generated with Claude Code