v0.9.2 Mobile Resilience & Ergonomics 9 мин 1 октября 2026, 15:00 — 17:30

Акт XIII: Мобильная зрелость и криптографическая гигиена — как мы победили сбой синхронизации ключей и очистили интерфейс Android

Разбор инцидента BUG-051: почему синхронизация хранилища спотыкалась на пустых приватных ключах, как постоянный сокетный пинг провоцировал Fail2ban, почему мы убрали настройки ИИ с мобильных экранов и как переработали сенсорную эргономику терминала.

BUG-051 #android #mobile #sync #crypto #security #fail2ban #ergonomics #ux #v0.9.2
Контекст акта: После успешного внедрения сквозной E2EE-синхронизации и разметки терминала мобильный клиент для Android столкнулся с суровой реальностью полевых испытаний. Попытка подключиться к синхронизированным серверам внезапно оборачивалась ошибкой авторизации, назойливый фоновый пинг вызывал подозрения у серверных систем защиты от подбора портов, а экран настроек был перегружен десктопными блоками искусственного интеллекта. Мы взялись за наведение порядка: методично разобрали криптографический конвейер, вернули мобильному клиенту легкость и адаптировали интерфейс под реальные пальцы и экраны.

Запись 59. Мобильная зрелость и криптографическая гигиена: как мы устранили сбой синхронизации ключей и очистили интерфейс Android-клиента

⏱️ Время: 1 октября 2026, 15:00 — 17:30 (~2.5 часа)

1. Контекст: проверка боем на мобильном устройстве

Честно признаюсь: я не держу в памяти тонкости криптографических примитивов и байтовых буферов в Dart. Моя задача как архитектора — видеть всю картину целиком: как данные рождаются на десктопе, как упаковываются в зашифрованный конверт, как передаются на телефон и что происходит, когда администратор нажимает кнопку подключения на ходу, стоя где-нибудь в метро или на объекте.

Синхронизация между устройствами на десктопе выглядела эталонно: мастер-пароль, сквозное шифрование AES-GCM, сокеты, чистая передача. Но стоило накатить свежую сборку на реальный Android-смартфон и подтянуть базу хостов, как начались странности.

Серверы, прекрасно открывавшиеся на ноутбуке, на телефоне наотрез отказывались пускать внутрь: терминал закрывался до авторизации, выбрасывая сухой SSHAuthAbortError. Хуже того: при подключении к серверам с жесткими правилами безопасности мобильный клиент внезапно попадал под временный бан сетевых экранов. А открыв настройки приложения на пятидюймовом экране, пользователь натыкался на огромный блок настройки API-ключей Gemini и параметров ИИ — функционала, который создавался для десктопа и на мобильном устройстве был попросту не нужен.

Стало ясно: мобильному клиенту требуется основательная ревизия и очистка от всего лишнего.

2. Подводные камни: пустые массивы, агрессивный пинг и дубликаты отпечатков

Мы начали системный разбор каждого звена цепочки и нащупали сразу четыре скрытых архитектурных дефекта:

  1. Криптографическая ловушка в SyncManager:
    Когда хост использует аутентификацию по паролю, поле encryptedPrivateKey в базе данных имеет нулевую длину (Uint8List(0)). Во время синхронизации цикл экспорта пытался расшифровать приватный ключ через AES-GCM. Криптографический модуль честно возмущался: расшифровка нуля байт невозможна, выбрасывался ArgumentError. Из-за общего блока try-catch исключение прерывало всю итерацию: обработка закрытого ключа падала, а следовавший за ней экспорт серверного пароля (encryptedPassphrase) молча пропускался! В итоге зашифрованный пароль улетал на телефон «как есть» — зашифрованным локальным мастер-паролем десктопа. Естественно, мобильное хранилище расшифровать его не могло и возвращало password = null.

  2. Молчаливый отказ контроллера:
    Когда мобильный SessionConnectController получал password == null из-за сбоя дешифрования, он не сообщал об ошибке пользователю, а наивно пытался установить SSH-сессию вовсе без учетных данных. Удаленный сервер мгновенно сбрасывал соединение, оставляя оператора в полном недоумении.

  3. Сетевая агрессия сокетного пинга:
    Фоновый таймер PingMonitorNotifier каждые 15 секунд открывал сырой TCP-сокет на 22-й порт каждого добавленного сервера и сбрасывал его для замера задержки. Если на сервере установлен Fail2ban или настроен лимит незавершенных SSH-сессий, такое поведение воспринимается как попытка сканирования портов. Мобильный клиент сам провоцировал собственную блокировку.

  4. Риск падения на дубликатах known_hosts:
    В методе findKnownHost запрос завершался вызовом getSingleOrNull(). Если в процессе импорта или синхронизации в таблице появлялись две записи для одного хоста, вызов гарантированно падал с критической ошибкой StateError: Too many elements.

3. Логика решений с ИИ: системное исправление и фокус на эргономике

Мы сформулировали четкие технические требования и провели последовательную переработку:

  1. Изоляция криптографических потоков:
    В SyncManager обработка приватных ключей и паролей была строго разделена. Дешифрование приватного ключа теперь выполняется только при явном условии k.encryptedPrivateKey.isNotEmpty. Пустые массивы больше не вызывают сбоев. Временный буфер пароля rawPassBytes очищается по правилам строгой гигиены памяти — через fillRange(0, length, 0) в блоке finally.
  2. Безопасный поиск хост-ключей:
    В репозитории known_host_repository.dart добавлен оператор ..limit(1), гарантирующий безопасную выборку без риска StateError.
  3. Честная диагностика авторизации:
    В SessionConnectController внедрена проверка: если при наличии сохраненных учетных данных их расшифровка вернула пустоту, подключение немедленно прерывается с понятным диагнозом в UI, подсказывающим перепроверить пароль или повторить синхронизацию.
  4. Бережное отношение к мобильной сети:
    Агрессивный 15-секундный таймер опроса на Android полностью выключен. Приложение выполняет единственный мягкий замер задержки при старте, а для актуализации данных добавлен нативный жест Pull-to-refresh (RefreshIndicator) в каталоге хостов.
  5. Очистка от десктопного балласта:
    Из мобильного профиля настроек целиком убрана карточка конфигурации ИИ и Gemini. Мобильный клиент должен оставаться компактным, быстрым и отзывчивым инструментом администратора.
  6. Сенсорная адаптивность:
    • В каталоге хостов появилась удобная горизонтальная лента чипов-фильтров (All, PROD, STAGE, DEV и папки).
    • Диалог добавления сервера разворачивается на всю ширину экрана с компактными полями для удобного ввода с сенсорной клавиатуры.
    • Панель быстрого ввода терминала пополнилась частыми Unix-символами (:, _, $, &, PgUp, PgDn, ENTER), а стрелки навигации сгруппированы рядом с Ctrl и Alt.
    • В горизонтальном режиме терминала высота шапки сжата до 36dp, отдавая драгоценные пиксели под консольные строки.

4. Итог

  • Зарегистрирован и полностью закрыт инцидент BUG-051.
  • Серверы после E2EE-синхронизации открываются на Android стабильно и предсказуемо.
  • Мобильный интерфейс избавлен от неиспользуемых настроек и оптимизирован для сенсорного ввода одной рукой.
  • Собрана и протестирована тестовая сборка app-release.apk (68.8 МБ).
  • Анализатор кода сообщает об абсолютной чистоте (0 предупреждений), все тесты монорепозитория (63 storage_vault, 150 terminal_ui, 61 shellit) пройдены на 100%.
📖 Типографика статьи выровнена по алгоритму Кнута — Пласса с помощью mcp-typography-server на базе Justif.
Knuth-Plass • TeX Style