GasApp

Политика хранения данных GasApp

Пункт M8 «Написать Data Retention Policy и Privacy Policy» (раздел 22), MASVS PRIVACY-2.

Редакция: 2026-10-07. Применимо к: серверу GasApp и клиентам Android/iOS.

Каждая строка ниже соответствует константе или запросу в коде, и рядом указано, где именно. Политика хранения, которую нельзя сверить с реализацией, — это обещание, а не политика.

Принцип

🔐 Сервер спроектирован так, чтобы нечего было выдавать. Это сильнее любого срока хранения: срок можно изменить приказом, а отсутствующие данные выдать нельзя.

Из этого следует главное свойство: сервер не знает, кто кому пишет при включённом sealed sender, не хранит IP-адресов и не ведёт журнала активности (раздел 15).

Что хранится на сервере

ДанныеСрокГде в коде
Аккаунт: имя пользователя, identity-ключДо удаления аккаунта — владельцем или после 12 месяцев без входаmigrations/0001_initial.sql, accounts
Открытая часть ключа восстановления аккаунта и момент последнего возвращения аккаунтаДо удаления аккаунта; нужны, чтобы вернуть аккаунт с тем же именем на новом устройстве и не принять повтор запросаaccounts.recovery_key, recovery_pq_key, recovered_at, миграция 0013
Журнал ключей: хеш имени, identity-ключ, хеш пост-квантовой половины и версия — по эпохам8 суток истории (последнее значение — пока аккаунт существует); нужен, чтобы собеседники проверяли ключ без личной сверки, а владелец — своё имяkt_leaves, kt_epochs, миграция 0014. Нового знания нет: имя и ключ хранятся в accounts и так
Месяц последней активности аккаунтаОбновляется при входе, не чаще раза в месяц; нужен только правилу «год без входа»accounts.active_month, миграция 0011
Устройство: идентификатор, привязка к аккаунтуДо удаления аккаунтаdevices
Подписанные предключиДо замены новыми (ротация — 7 суток). Время публикации — с точностью до часаsigned_prekeys
Одноразовые предключиДо расходованияone_time_prekeys, DELETE при выдаче bundle
Недоставленные конверты30 сутокENVELOPE_TTL, gasapp-server-db/src/lib.rs:37
Вложения30 сутокATTACHMENT_TTL, там же:50
Токен push-уведомленийДо смены или удаления аккаунтаpush_tokens
Хеш токена уведомлений: по нему устройство в фоне узнаёт, есть ли для него что-то в очереди. Времени запросов нетДо выключения уведомлений либо удаления устройстваnotify_tokens, миграция 0028
Ключ доступа sealed sender (SHA-256 от него)До заменыsealed_sender_access
Жалобы: кто пожаловался — на кого, доказанные сертификатом отправителяДо удаления любого из аккаунтов или до решения оператора (удаление аккаунта, отклонение жалоб). Время — с точностью до часаsealed_sender_reports
Журнал действий оператора: действие, причина, число жалобщиков — без имени и идентификатора аккаунтаБессрочно: из него составляется отчёт о прозрачности. Время — с точностью до часаadmin_actions, миграция 0015
Хеш адреса почты (HMAC-SHA-256 на ключе узла GASAPP_EMAIL_KEY). Сам адрес не хранитсяДо удаления аккаунтаaccounts.email_hash, миграция 0017
Код подтверждения почты: хеш адреса, хеш кода, счётчик неверных вводов и счётчик писем за часКод действует 15 минут; строка стирается при регистрации либо через час после истечения кодаemail_codes, миграция 0017; email::purge_expired_codes
Премиум: пометка на аккаунтеДо снятия оператором или удаления аккаунтаaccounts.premium, миграция 0018
Канал: владелец, открытый ключ подписи автора, зашифрованные название и описание, номер последней записи. Время создания — с точностью до часаДо удаления канала автором или удаления его аккаунтаchannels, миграция 0018
Записи канала: шифротекст (размер — с точностью до бакета дополнения), час публикации, номер изменения, счётчик просмотровДо удаления записи автором либо канала. Удалённая запись заменяется подписанной автором отметкой об удалении — она остаётся, чтобы читатели узнали об удаленииchannel_posts, миграции 0018, 0019
Файлы канала (фото, документы, аватар): шифроблобы и их размер, час загрузкиДо удаления записи, к которой файл приложен, либо каналаchannel_media, миграция 0019
Публичное имя канала и ссылка-приглашение к нему. ⚠️ В ссылке ключ чтения: публичный канал сервер прочитать можетПока канал публичный; стирается, когда автор делает канал закрытым, и вместе с каналомchannels.handle, channels.public_link, миграция 0019
Подписчики канала: какие аккаунты подписаны на какие каналы, номер записи, до которой каждый дочитал, реакции (одна из восьми эмодзи на запись) и их счётчикиДо отписки (приложение отписывает аккаунт, когда канал убирают с телефона), удаления канала либо аккаунтаchannel_subscribers, channel_reactions, channel_reaction_counts, миграция 0019
Администраторы канала: какие аккаунты, кроме владельца, пишут в канал, и публичный ключ подписи каждого. Время назначения — с точностью до часаДо снятия администратора владельцем, удаления канала либо аккаунта администратораchannel_admins, миграция 0020
Закрытый канал: признак «закрытый»; хеши меток ссылок-приглашений (сами ссылки сервер не хранит) и час создания каждойДо отзыва ссылки (строка остаётся с отметкой «отозвана»), удаления каналаchannels.closed, channel_invites, миграция 0021
Заявки в закрытый канал: какой аккаунт просится в какой канал, одноразовый открытый ключ с подписью аккаунта, час подачиДо ответа на заявку (одобрения либо отказа), удаления канала либо аккаунтаchannel_join_requests, миграция 0021
Ответы на одобренные заявки: ключ чтения канала шифротекстом под ключ заявителяДо удаления подписчика из канала, удаления канала либо аккаунтаchannel_grants, миграция 0021
Удалённые из закрытого канала: какой аккаунт удалён из какого канала, час удаленияДо снятия запрета тем, кто ведёт канал, удаления канала либо аккаунтаchannel_bans, миграция 0021
Смена ключа закрытого канала: номер действующей эпохи ключа; звенья цепочки (прежний ключ шифротекстом под новый) и час каждой сменыДо удаления каналаchannels.key_epoch, channel_epochs, миграция 0023
Ключи доставки участников закрытого канала: открытый ключ за подписью аккаунта — под него участнику шифруют ключ новой эпохиДо удаления участника из канала, удаления канала либо аккаунтаchannel_member_keys, миграция 0023
Удостоверение участника закрытого канала: подпись того, кто ведёт канал, под ключом доставки участника и номером эпохи. Сервер его хранит, но ни проверить смысл, ни подделать не можетВместе с ключом доставкиchannel_member_keys.approval, миграция 0027
Жалобы на каналы: какой аккаунт пожаловался на какой канал, причина, номер записи (если указан) и — только по выбору жалобщика — ссылка канала с ключом чтенияДо решения оператора (удаление канала либо отклонение жалоб), удаления канала либо аккаунта жалобщикаchannel_reports, миграция 0024
Боты: какой аккаунт-бот принадлежит какому аккаунту-владельцу, час создания. Адреса почты у бота нетДо удаления бота владельцем либо удаления владельца (боты удаляются вместе с ним)bots, миграция 0022
Токены ботов: хеш одноразового токена, владелец, имя будущего ботаЧас, либо до регистрации бота по токенуbot_tokens, миграция 0022
Счёт токенов бота за текущие сутки: день и число — без времени каждого токенаДо следующих суток, в которые аккаунт берёт токенaccounts.bot_tokens_day, миграция 0026

Конверт удаляется сразу после того, как получатель подтвердил приём (DELETE FROM envelopes …, repo.rs:507), а не по истечении срока. Тридцать суток — предел для тех, кто не вышел на связь.

⚠️ Вложения — исключение, и это записано в коде. Сервер не знает, кому адресовано вложение: ссылка на него лежит внутри зашифрованного конверта. Поэтому удалить вложения вместе с аккаунтом невозможно, и срок хранения — единственное, что их вообще стирает.

⚠️ Адрес почты — вне этой таблицы, и это надо понимать. Сервер получает адрес в запросе кода и в запросе регистрации, считает хеш и адрес забывает: в базу и в журнал сервера он не пишется. Но письмо с кодом отправляет почтовая служба (GASAPP_EMAIL_CMD), и у неё остаются адрес, время и текст письма — на её сроках, не на наших. Оператор обязан выбрать службу, срокам которой доверяет, и назвать её пользователям.

Чего на сервере нет

🔐 Перечислено не для красоты: отсутствие этих данных — и есть содержание политики.

Резервные копии сервера

server/deploy/backup.sh снимает копию базы с исключением данных конвертов и вложений. 🔐 Иначе они переживали бы свой тридцатидневный срок в копиях — то есть срок хранения существовал бы только в основной базе. Копий четырнадцать, ежедневных: вложение, попавшее в последнюю копию перед истечением, жило бы до 30 + 14 суток. ⚠️ До 2026-09 вложения в копии попадали; проверка дампа (COPY public.<таблица>) теперь охватывает обе таблицы.

Ключ шифрования копий ротируется (rotate-backup-key.sh); старый ключ не удаляется сразу, иначе ротация превращалась бы в потерю всех прежних копий.

Что хранится на устройстве

ДанныеСрок
Переписка, контакты, ключи сессийДо удаления пользователем или до срабатывания таймера исчезающих сообщений
Исчезающие сообщенияПо таймеру чата; чистка выполняется при каждом открытии хранилища (Core.open → purgeExpired). В резервную копию не попадают. ⚠️ До 2026-09 одно истёкшее сообщение с вложением останавливало чистку целиком, а приложение не сообщало об ошибке
Резервная копия (у того, где вы её храните)До удаления вами. Исчезающих сообщений в ней нет. Удалённое сообщение остаётся в копиях, сделанных до удаления; при восстановлении всей цепочки оно убирается снова, из старой или неполной цепочки — возвращается
Файлы File VaultДо удаления пользователем: «Хранилище» → файл → «Удалить из хранилища». Стирается и запись в базе, и зашифрованный блоб на диске. ⚠️ До 2026-09 этого пункта не было в интерфейсе — строка обещала удаление, которого пользователь сделать не мог; файл жил до удаления аккаунта
Счётчик неверных попыток PINДо верного ввода PIN
Имя и адрес почты незавершённой регистрацииС шага PIN до создания аккаунта; стирается при успешной регистрации либо вместе с базой, если регистрацию отменили
Каналы: ссылка-доступ (секрет чтения и ключ автора), название, расшифрованные записи; у своих каналов — приватный ключ подписиДо удаления канала с устройства («Убрать канал» / «Удалить канал») или удаления аккаунта. 🔐 В резервную копию входят ключи каналов (секрет чтения, свой ключ подписи, имена администраторов) — иначе потеря телефона означала бы потерю канала; записи не входят и забираются с сервера заново
Буфер обмена (скопированное из приложения)60 секунд либо до запирания приложения

🔐 Всё это лежит в базе, зашифрованной SQLCipher; ключ выводится из PIN и секрета, не покидающего устройство (раздел 7.3).

Что значит «удалено» на устройстве. Удалённое и исчезнувшее сообщение перестаёт читаться из файла базы — в том числе тем, кто узнал ключ. Освобождённые страницы затираются (secure_delete), а журнал WAL после каждого удаления переносится в основной файл и обнуляется (checkpoint_truncate).

⚠️ До 2026-09 журнал не переносился: удалённый текст оставался в основном файле до автоматической контрольной точки — в тихой переписке это дни и недели. Проверяется тестами a_purged_message_does_not_linger_in_the_main_file и a_deleted_message_does_not_linger_in_the_main_file: образ основного файла, снятый при открытой базе, открывается тем же ключом.

⚠️ Физические ячейки флеш-памяти приложению недоступны: перезапись страницы файла их не затирает, и восстановление с самого носителя этой гарантией не покрывается.

Удаление аккаунта

DELETE /v1/accounts/{device_id} за подписью владельца. В приложении — «Приватность» → «Удалить аккаунт».

Удаляется сразу, а не помечается к удалению: отложенное стирание означало бы период, в который данные уже обещаны удалёнными, но ещё лежат на диске.

ЧтоСудьба
Запись аккаунта, имя пользователя, identity-ключУдаляется
Все устройства аккаунтаУдаляются
Подписанные и одноразовые предключиУдаляются
Недоставленные конверты этих устройствУдаляются
Токены pushУдаляются
Ключи доступа sealed sender и жалобыУдаляются — и поданные этим аккаунтом, и поданные на него. ⚠️ До 2026-09 жалобы на удалённый аккаунт оставались: ссылки на аккаунт у них не было (миграция 0006)
ВложенияНе связаны с аккаунтом: загрузка анонимна (миграция 0009). Истекают через 30 суток. ⚠️ До 2026-09-24 удалялись вместе с аккаунтом
Открытый сокет уведомленийЗакрывается
Хеш адреса почты и пометка о премиумеУдаляются вместе с записью аккаунта; адрес снова свободен
Каналы аккаунта и их записиУдаляются (каскад от channels.owner_account). У читателей остаётся то, что они уже загрузили
Журнал почтовой службы о письме с кодом⚠️ Не затрагивается: он не на сервере GasApp

Каскады объявлены в схеме (0001_initial.sql и далее), а не собраны перечислением в коде: перечисление разошлось бы со схемой на следующей добавленной таблице, и остаток пережил бы удаление молча.

Ответ называет числа — сколько устройств, конвертов и вложений удалено. «Готово» без чисел неотличимо от «готово» при запросе, который ничего не сделал.

⚠️ Чего удаление не уносит.

Локальные данные приложение стирает само, после подтверждения с сервера: база, соль, зашифрованный аппаратный секрет, ключ-обёртка в Android Keystore, история попыток PIN. Порядок обязателен — запрос подписывается ключом устройства, и стерев его первым, при обрыве связи человек остался бы с живым аккаунтом и без средства его удалить.

Запросы правоохранительных органов

Сервер может выдать только то, что у него есть. Применительно к конкретному имени пользователя это:

Сервер не может выдать: содержимое переписки, список собеседников, историю доставки, IP-адреса, местоположение.

⚠️ Это свойство архитектуры, а не политики: перечисленного нет в базе.

Изменения политики

Изменение сроков хранения — изменение констант в коде, а константы закреплены тестом retention_matches_the_plan (gasapp-server-db/src/lib.rs:193). Поменять срок, не тронув политику, можно; поменять его незаметно — нет: тест придётся править в том же коммите.