Мой блог
Менеджер задач Dela: сквозное шифрование, self-hosted сервер и код от Claude Code
Разработчик представил проект Dela — менеджер задач со сквозным шифрованием, созданный для развёртывания на собственном сервере. Ключевая концепция сервиса заключается в полном шифровании информации непосредственно на пользовательских устройствах: сервер выступает исключительно в роли релея и хранилища шифротекстов, не имея доступа к содержимому записей. Синхронизация правок между клиентами происходит по отдельным полям с применением меток гибридных логических часов, а вся кодовая база проекта была сгенерирована нейросетевым инструментом Claude Code.
Поводом для создания собственной системы стали ограничения бесплатного тарифа Todoist по числу проектов и нежелание оформлять платную подписку, а также потребность в строгой приватности, исключающей чтение списков дел третьими лицами или администратором хостинга. В результате получился кроссплатформенный менеджер задач Dela с открытым исходным кодом под лицензией AGPL-3.0, доступный в браузере, а также на платформах Windows и Android.
Архитектура безопасности: что доступно серверу
Поскольку серверная часть Dela часто размещается на одном VPS с другими веб-сервисами и ботами, потенциальная компрометация соседних контейнеров или получение root-прав злоумышленником не должны приводить к утечке задач. По этой причине сквозное шифрование встроено в архитектуру на постоянной основе и не подлежит отключению в настройках.
Сервер обрабатывает минимально необходимый объём метаданных. Ему известны:
- адреса электронной почты учетных записей, идентификаторы подключенных устройств и списки участников совместных проектов;
- количество поступивших операций и их временные метки;
- объём каждой операции с выравниванием (pad) с точностью до 64 байт.
В то же время от сервера полностью скрыты названия задач, описания, комментарии, вложения, проекты, разделы, метки, дедлайны, правила повторения, приоритеты, точное время напоминаний, а также конкретные таблицы, записи и поля, модифицируемые операцией.
Криптографические ключи формируются на устройстве пользователя из мастер-пароля через алгоритм Argon2id. Из 64 байт результата 32 байта становятся секретом входа (сервер хранит только его хеш), а остальные 32 байта формируют ключ обёртки (KEK), расшифровывающий мастер-ключ. Мастер-ключ шифрует ключи личных пространств и секретный ключ X25519. Сам исходный пароль никогда не покидает клиент. При его смене перешифровывается исключительно мастер-ключ, тогда как тело задач остаётся нетронутым. Для предотвращения перебора и проверки наличия аккаунта в базе сервер возвращает соль для Argon2id: если запрошенного адреса не существует, генерируется детерминированная поддельная соль через HMAC от внутреннего секрета сервера.
Попольная синхронизация и разрешение конфликтов
Базовой единицей модификации данных выступает операция записи нового значения в конкретное поле отдельной строки таблицы. Каждое действие сопровождается меткой гибридных логических часов (HLC), состоящей из физического времени в миллисекундах, счётчика операций внутри одной миллисекунды и уникального идентификатора устройства. Метки имеют строго фиксированную ширину и сопоставляются как строки: при конфликте побеждает операция с большей меткой.
Благодаря разделению по ячейкам изменение разных параметров одной и той же задачи на двух устройствах в офлайн-режиме не приводит к потере данных. Если на смартфоне без сети был изменён срок выполнения, а на ПК в это же время отредактировано название, после подключения к интернету применятся обе правки. Если же оба клиента меняли один и тот же атрибут, сохранится вариант с более поздней меткой HLC. Для защиты от рассинхронизации системного времени сервер отклоняет операции, опережающие его часы более чем на 60 секунд, транслируя клиенту корректное время для применения поправки.
В модели данных также заложены специальные правила для стабильной работы без связи:
- метки задачи представлены отдельными бинарными полями формата
label:<id>, благодаря чему теги, добавленные независимо на разных клиентах, корректно суммируются; - идентификатор системного списка «Входящие» совпадает с идентификатором пространства, что предотвращает дублирование папки при её создании в офлайне;
- позиционирование задач внутри списков опирается на дробные ключи, поэтому перестановка меняет значение только одного поля;
- циклические перемещения подзадач друг в друга разрешаются на этапе чтения путём игнорирования наиболее старой операции перемещения.
Формат конвертов и серверный журнал
На сервер операции передаются внутри защищённых конвертов. В качестве идентификатора ячейки выступает слот — хеш BLAKE2b-128 от пути к ячейке с использованием ключа пространства. Сервер понимает, что два конверта направлены в одну и ту же ячейку, но не может узнать её адрес. В открытом виде передаётся только HLC-метка, необходимая для определения актуальности пакета.
Шифрование полезной нагрузки осуществляется связкой XChaCha20-Poly1305 с выравниванием длины до 64 байт. Слот и метка HLC входят в блок аутентифицированных дополнительных данных (AAD): любая попытка подменить время или привязать данные к другому слоту приведёт к ошибке расшифровки на клиенте. Сервер сохраняет исключительно последний конверт для каждого слота и выдаёт изменения клиентам страницами по возрастанию порядкового номера. Детерминированное тестирование ядра релея включает симуляцию на десятках сценариев с намеренным сдвигом часов на клиентах до 30 секунд и потерей сетевых пакетов.
Совместная работа и ротация ключей
Каждый общий проект функционирует внутри отдельного криптографического пространства. Приглашая участника, владелец получает его открытый ключ по почте, шифрует ключ проекта через crypto_box и передаёт отпечаток для ручной сверки по независимому каналу связи. Это предотвращает атаку типа «человек посередине», при которой скомпрометированный сервер мог бы сгенерировать собственную пару ключей.
В случае удаления пользователя из проекта Dela запускает процедуру ротации ключа. Владелец проекта генерирует новый ключ, заново перешифровывает все существующие данные и передаёт обновлённый ключ оставшимся участникам. Исключённый пользователь сохраняет доступ к локально сохранённой копии старых записей, но лишается возможности расшифровывать любые новые правки. Процесс ротации выполняется пакетами с верификацией курсора изменений, предотвращая гонки при одновременной записи.
Интеллектуальный ввод и синтаксис Todoist
Модуль быстрого создания задач выполняет синтаксический разбор вводимого текста на лету на русском и английском языках. Система автоматически определяет даты, время, цикличность, целевой проект, метки и приоритет, подсвечивая распознанные токены в интерфейсе ввода. Нажатие на подсвеченный фрагмент возвращает слово в текст названия, если парсер ошибся с контекстом.
Правила повторения сохраняются в стандарте RRULE. Дополнительно поддерживаются относительные повторы с отсчётом интервала от фактической даты выполнения задачи. Поисковые фильтры полностью совместимы с синтаксисом запросов Todoist и поддерживают русскоязычные ключевые слова, а перенос существующей базы задач реализован через прямое обращение к API Todoist прямо из браузера пользователя без участия сервера Dela.
Клиентские приложения для Android и Windows
Десктопный клиент для Windows построен на базе Tauri 2, а мобильное приложение для Android использует фреймворк Capacitor 8. Оба решения используют единый веб-интерфейс на React, дополненный нативными компонентами для интеграции с операционной системой:
- на ПК реализованы работа в системном трее и глобальная комбинация клавиш Ctrl+Shift+Space для вызова окна ввода;
- на Android добавлены виджет рабочего стола, тайл быстрых настроек, голосовой ввод и автономные локальные напоминания;
- фоновая доставка уведомлений с других устройств работает через децентрализованный протокол UnifiedPush (включая ntfy) без обязательной привязки к Google Play Services.
Безопасность локальных данных обеспечивается PIN-кодом с хешированием Argon2id, поддержкой биометрии и прогрессивными задержками ввода. После десяти неверных попыток ввода PIN-кода локальное хранилище полностью очищается, требуя повторной авторизации по основному паролю.
Разработка через Claude Code и исправление ошибок
Создание кодовой базы проекта происходило при помощи Claude Code. Разработка велась поэтапно на основе заранее согласованной архитектурной спецификации, разделённой на четыре фазы с чёткими критериями приёмки.
Для отладки мобильного приложения нейросеть взаимодействовала с физическим смартфоном разработчика через USB-кабель и команды adb shell input, анализируя состояние через логи Chrome DevTools и системные дампы Android. В процессе тестирования ассистент выявил и устранил несколько специфических проблем платформы:
- Падение плагинов Capacitor: функция
registerPluginвозвращала прокси-объект, у которого асинхронный контекст вызывал методthen(), что приводило к ошибке из-за отсутствия реализации промиса на стороне Android; - Зависание виджета: лаунчер Android блокировал отрисовку стандартного класса разметки
<View>в качестве разделителя, что потребовало его замены наFrameLayout; - Сброс напоминаний: из-за погрешности неточных будильников внутренний таймер приложения обгонял системный вызов и отменял расписание; логику скорректировали, оставив удаление только неактуальных будущих событий и добавив запрос разрешения на точное время.
Текущие ограничения и развёртывание через Docker
В текущей версии Dela веб-клиент держит расшифрованные данные в IndexedDB, а мобильный виджет помещает отображаемые задачи в закрытую системную директорию без дополнительного шифрования. В случае гипотетического взлома хостинга сервер не сможет прочесть зашифрованные задачи, но потенциально способен подменить JS-скрипты веб-интерфейса, поэтому использование нативных клиентов для Windows и Android обеспечивает более высокий уровень изоляции. Приложение для iOS в настоящий момент отсутствует.

Развёртывание серверной части требует установленного Docker и настроенного доменного имени. Встроенный веб-сервер Caddy автоматически выпускает TLS-сертификаты. Контейнер функционирует с правами доступа к файловой системе только для чтения, не требует привилегий суперпользователя и автоматически создаёт резервные копии базы данных SQLite раз в сутки и перед выполнением миграций.
