fix(security): treat a new identity key as a message request, never as a known contact

A stranger could pick the display name of an existing contact ("Mom")
and appear as that contact. Messages from a key that is not in the
contact list now open a message request labelled "(unknown)": no
replies, read receipts, typing indicators, calls or name in
notifications until the user accepts it and names the contact
themselves. Names that look like an existing contact (case, diacritics,
Cyrillic/Greek homoglyphs, invisible characters) also show a warning in
the real contact's chat.

When the relay account of a known contact shows up with a different
key, the chat shows "safety number changed", verification is cleared
and the session that now belongs to the other key is dropped before
sending, so a reply never goes to the new key.
This commit is contained in:
Julius Joska
2026-10-23 10:50:42 +02:00
parent 2f6b542f24
commit 717aeed4ff
23 changed files with 999 additions and 67 deletions
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:sepot/core/l10n/contact_name.dart';
import 'package:sepot/core/constants/app_constants.dart';
import 'package:sepot/core/l10n/error_messages.dart';
import 'package:sepot/core/l10n/l10n.dart';
@@ -37,8 +38,8 @@ Future<void> forwardMessage(
for (final g in groups)
if (!g.left && g.id != fromId) _Target(g.id, g.name, isGroup: true),
for (final c in contacts)
if (!c.blocked && c.id != fromId)
_Target(c.id, c.displayName, verified: c.verified),
if (!c.blocked && c.accepted && c.id != fromId)
_Target(c.id, contactName(context.l10n, c), verified: c.verified),
]..sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
// Poznámky nahoře: přeposlat si něco „na později“ je nejčastější případ.
if (!ConversationsNotifier.isNotes(fromId)) {