Право на забвение в почте: как физически стереть все письма субъекта по запросу об удалении ПДн
Клик по «Удалить» в вебмейле трогает одну копию письма из восьми. Разбираем, где на диске реально лежат данные субъекта — maildir, индексы Dovecot, FTS, логи Postfix, карантин, бэкапы — и как найти, удалить и доказать удаление по 152-ФЗ и GDPR ст. 17.
EvilMail Team4 августа 2026 г.12 мин чтения
Что на самом деле значит «удалить письмо»
Менеджер получает запрос об удалении персональных данных, открывает вебмейл, находит письмо, жмёт «Удалить» и отчитывается: исполнено. На диске в этот момент не изменилось почти ничего.
Одно входящее письмо к моменту запроса существует не в одном экземпляре, а в шести-восьми независимых копиях. Оригинал лежит в maildir получателя. Копия — в папке Sent отправителя, если он тоже ваш пользователь. Заголовки и куски тела закешированы в dovecot.index.cache. Полный текст письма проиндексирован в FTS-бэкенде (Xapian или Solr) и живёт там отдельным блобом. Адреса и Message-ID записаны в mail.log. Если письмо ещё не доставлено — оно в активной очереди Postfix. Спам-движок мог отложить копию в карантин. И, наконец, всё перечисленное вчера ночью уехало в бэкап, а оттуда — в офсайтовое хранилище.
Клик по «Удалить» трогает ровно одну из этих поверхностей — ящик получателя, да и то лишь перекладывает письмо в Корзину, а не стирает. Удаление ПДн — это операция по всей поверхности хранения, а не действие в одном ящике. Ниже — как эта поверхность выглядит и как её реально зачистить.
Удаление ПДн из почты: право на забвение, doveadm, бэкапы | evilmail — EvilMail Blog
Юридическая рамка за 60 секунд
Два режима, одна инженерная задача.
GDPR, статья 17 — «право на стирание». Субъект вправе требовать удаления своих данных, оператор обязан ответить в течение одного месяца (ст. 12(3)), с продлением до трёх при обоснованной сложности. 152-ФЗ, ст. 21 — оператор обязан прекратить неправомерную обработку и уничтожить ПДн в установленный срок, а требование Роскомнадзора исполняется в предписанный период.
Что удалять обязательно — переписку, контакты, вложения, метаданные, где субъект идентифицируем. Что можно и нужно удержать на законном основании: бухгалтерские и налоговые записи в пределах сроков хранения, данные под активным правовым требованием (спор, претензия), антифрод-логи в рамках объявленного retention. Право на стирание не абсолютно — оно уступает другому законному основанию обработки.
Ваши союзники здесь — принципы минимизации (ст. 5(1)(c)) и ограничения хранения (ст. 5(1)(e)). Чем меньше вы храните и чем короче, тем меньше поверхность стирания. Практический вывод: сначала фиксируем scope — что удаляем и что законно удерживаем, с обоснованием, — и только потом трогаем диск.
Инвентаризация: где живёт почта субъекта
Карта поверхностей с реальными путями (на стеке evilmail — Dovecot + Postfix + rspamd):
Maildir:/var/mail/vhosts/{domain}/{user}/Maildir/{cur,new,tmp} — сами письма, по файлу на сообщение.
Индексы Dovecot:dovecot.index, dovecot.index.cache, dovecot.list.index в каталоге ящика. В .cache лежат закешированные заголовки и куски тела.
FTS-бэкенд: Xapian в /var/lib/dovecot/ либо коллекция dovecot в Solr. Полнотекстовый индекс держит тело письма отдельно от maildir.
Логи MTA:/var/log/mail.log*, плюс journald.
Очередь Postfix:/var/spool/postfix/{active,deferred,hold} — если письмо ещё в пути.
Две поверхности забывают почти всегда. Первая — почта субъекта в чужих ящиках: он писал вашим пользователям, значит его адрес и имя лежат в их INBOX и в их Sent. Вторая — внешние интеграции: CRM, тикет-система, рассылочный движок, отдельный поисковый индекс. Удаление из maildir без зачистки этих двух — это неисполненный запрос.
Поиск: находим все следы
Сначала соберите полный набор идентификаторов. Один субъект — это основной адрес, все алиасы, старые адреса, отображаемое имя в кириллице И латинице (Иван Петров / Ivan Petrov), плюс Message-ID цепочки, если речь о конкретной переписке. Искать надо по From, по To/Cc, в теле и в заголовках (Return-Path, Reply-To).
bash
# найти во всех папках одного ящика (or здесь — инфиксный оператор Dovecot)
doveadm search -u [email protected] mailbox '*' \
from [email protected] or to [email protected]
# обойти всех виртуальных пользователей сервера
doveadm user '*' | while read u; do
hits=$(doveadm search -u "$u" mailbox '*' \
from [email protected] or to [email protected] | wc -l)
[ "$hits" -gt 0 ] && echo "== $u : $hits"
done
# сырой поиск по дереву maildir (тело + заголовки), два алфавита
grep -rliE 'subject@example\.com|Ivan Petrov|Иван Петров' /var/mail/vhosts/
# логи и очередь
grep -i '[email protected]' /var/log/mail.log*
mailq | grep -i [email protected]
grep -rl по maildir ловит то, что doveadm search может пропустить из-за расхождения индекса с диском — например, письма, попавшие мимо LDA. Прогоняйте оба.
Удаление в живой системе
Сначала разберитесь, какое у вас хранилище (mail_location) — от этого зависит вся процедура. На evilmail это maildir:~/Maildir, и здесь важная деталь: на Maildir `doveadm expunge` сразу удаляет файл сообщения (unlink) — каждое письмо это отдельный файл, отдельная команда purge не нужна и на Maildir она ничего не делает. doveadm purge осмыслен только на mdbox/sdbox: там expunge лишь помечает сообщение (refcount), а физически данные вырезает purge, схлопывая dbox-файлы. Не перепутайте: на Maildir ждать «purge» после expunge — значит ничего не сделать.
Но и на Maildir есть нюанс: unlink убирает имя файла, а блоки с телом письма остаются на диске до перезаписи. Гарантию физического стирания даёт только шифрование тома (LUKS/dm-crypt) или очистка на уровне ФС — на уровне почтового сервера этого не решить.
bash
# удалить из INBOX получателя и из Sent отправителя (Maildir: файл уходит сразу)
doveadm expunge -u [email protected] mailbox INBOX from [email protected]
doveadm expunge -u [email protected] mailbox Sent to [email protected]
# ТОЛЬКО для mdbox/sdbox: физически вырезать данные из dbox-файлов
# doveadm purge -u [email protected]
# пересборка FTS, иначе тело письма остаётся в поисковом индексе
doveadm fts rescan -u [email protected]
doveadm fts optimize -u [email protected]
Три подводных камня. Первый — FTS: без rescan/optimize тело удалённого письма продолжает всплывать в поиске и, что важнее, физически лежит в индексе Xapian. Второй — `.cache`: закешированные заголовки инвалидируются при экспандже, но чтобы гарантированно пересобрать индекс каталога, прогоните doveadm index -u [email protected] '*'. Третий — hardlink и дедупликация: при включённом single-instance storage (sis, например для вложений) или на бэкапах с дедупом одна inode держит данные живыми, даже когда «последняя» видимая копия удалена. Проверьте счётчик ссылок перед тем, как считать письмо стёртым:
Не забудьте карантин (rspamc / каталог amavis) и очередь — застрявшее письмо удаляется через postsuper -d <queue_id>.
Самое сложное: бэкапы и логи
Здесь начинается отдельная война, и здесь ломается большинство «исполненных» запросов.
Правильно устроенные бэкапы — append-only и immutable: borg с append-only, restic с WORM-хранилищем, S3 с Object Lock. Это защита от шифровальщика, и это же делает точечное вырезание одного письма невозможным без цепочки restore → scrub → re-backup, которая рвёт целостность и историю снапшотов. Резать снапшот скальпелем — плохая идея.
Два легитимных пути.
Путь 1 — документированное исключение (обычно правильный). Данные субъекта в бэкапах помечаются к удалению при следующей ротации и НЕ восстанавливаются в прод ни при каком restore. В ответе субъекту прямо указывается срок хранения бэкапов, по истечении которого копия исчезнет физически. GDPR такой подход допускает: живая обработка прекращена немедленно, бэкап гасится по объявленному retention.
bash
# не вырезаем точечно — управляем retention всего репозитория
restic snapshots
restic forget --keep-within 30d --prune
Путь 2 — дождаться ротации при коротком retention: если цикл бэкапов 7–14 дней, часто проще дождаться естественного истечения снапшотов, чем городить restore-scrub. Главное — зафиксировать дату, к которой копия гарантированно исчезнет.
Логи — та же логика. PII в mail.log и journald гасится ротацией и вакуумом, а не редактированием файлов:
bash
journalctl --vacuum-time=30d
# в logrotate — сжатие + короткий maxage; при необходимости — анонимизация адресов/IP
Доказательство и аудит
Исполнение без доказательства = неисполнение. Регулятор и субъект вправе спросить «покажите». Фиксируйте:
Журнал операции: кто, когда, какие команды выполнил, сколько сообщений затронуто. Вывод doveadm expunge даёт число.
Контрольный повторный поиск после чистки, возвращающий ноль совпадений в живой системе — тот же doveadm search и grep, что на этапе инвентаризации.
Зафиксированный retention бэкапов с датой, к которой копия исчезнет.
Письменный ответ субъекту с датой исполнения и явным упоминанием законно удержанных данных.
Сам аудит-журнал храните как минимально необходимый набор — факт операции и идентификаторы, без содержимого писем. Аудит удаления ПДн не должен становиться новой копией ПДн.
Чеклист исполнения запроса
1.Подтвердить личность заявителя — иначе вы удаляете чужие данные по чужой просьбе.
2.Собрать все идентификаторы: основной адрес, алиасы, старые адреса, имя в двух алфавитах.
3.Определить scope: что удаляем и что законно удерживаем (бухгалтерия, спор, антифрод) — с обоснованием.
4.doveadm search + grep -rl по всем ящикам, папкам, логам и очереди.
5.doveadm expunge в INBOX/Sent/shared (на Maildir файл уходит сразу; на mdbox — затем purge).
6.doveadm fts rescan + optimize — вычистить тело из поискового индекса.
7.Карантин rspamd/amavis, застрявшая очередь (postsuper -d).
8.Внешние интеграции: CRM, тикеты, рассылки, отдельный поиск.
10.Бэкапы: пометить к удалению по ротации, зафиксировать retention.
11.Контрольный поиск = 0 совпадений в живой системе.
12.Аудит-запись + письменный ответ в срок.
Privacy by design: почему TTL решает половину проблемы
Каждый пункт выше — это работа, потому что данные лежат долго и в множестве копий. Убери одно из двух — и запрос на стирание схлопывается.
Именно на этом построена временная почта evilmail: ящики и сообщения имеют TTL и истекают автоматически, долгого хранения нет по умолчанию, минимизация встроена на входе. Когда данные субъекта живут часы, а не годы, поверхность стирания падает на порядок: нет разросшихся Sent в чужих ящиках, нет писем в квартальных бэкапах, FTS-индекс короткий и самоочищается.
Лучший запрос на удаление — тот, который нечего исполнять, потому что данных уже нет.