> ## Documentation Index
> Fetch the complete documentation index at: https://veriqa.app/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Безопасность и обработка данных

> Что Veriqa собирает, где хранит, сколько держит, что шифрует и что уходит за периметр.

Эта страница — адрес для вопросов, которые security review задаёт первыми.

<Note>
  Это фактическое описание софта, а не юридическая консультация и не сертификат соответствия. Какие
  из этих фактов значимы для вашего регулятора и что с ними делать — решение ваше и вашего юриста.
</Note>

## Кто чем владеет

Veriqa поставляется как код, который вы запускаете у себя. В self-hosted-модели это и есть весь
ответ на вопрос «куда уходят данные»:

* **Контроллер — вы.** База, ключевой материал и логи находятся на вашей инфраструктуре, под вашими
  строками подключения. Ничего не разворачивается за вас.
* **Veriqa (вендор) не контроллер и не процессор данных ваших пользователей.** Вендору Veriqa не
  передаётся ничего: продукт не звонит домой, не шлёт телеметрию и не имеет вендорского эндпоинта в принципе.
  Единственные исходящие адреса в коде — API мессенджеров, которые настроили вы, и SMTP-сервер, на
  который вы его направили (см. [Что уходит за периметр](#что-уходит-за-периметр)).
* **Провайдеры мессенджеров — ваши контрагенты, а не вендора Veriqa.** Telegram, MAX и WhatsApp Cloud API видят
  сообщения, которые вы отправляете через собственные боты. Отношения с каждым из них у вас прямые.

## Инвентарь персональных данных

Всё, чего касается поставляемый продукт, — и ничего сверх того.

| Данные | Откуда берутся | Где живут | Сколько |
| - | - | - | - |
| Идентификатор пользователя в канале (id Telegram/MAX/WhatsApp, адрес email) | из канала, при подтверждении | хранилище транзакций; claim `sub` выпущенного токена | транзакция: минуты. Токен: ваши сроки жизни токенов |
| Отображаемое имя, имя и фамилия, username | профиль в канале | хранилище транзакций; claim'ы `name`, `given_name`, `family_name`, `preferred_username` | так же |
| Адрес email, `email_verified` | из канала (либо введён пользователем в канале Email) | хранилище транзакций; claim `email` | так же |
| Номер телефона | из канала, где он его отдаёт | хранилище транзакций; claim `phone_number` | так же |
| Изображение аватара | профиль в канале; у Telegram адаптер скачивает файл (не больше `AvatarDataUri.MaxImageBytes`, 64 КиБ — изображение должно уложиться в снапшот транзакции 128 КБ; фото крупнее не передаётся, а некорректное или слишком большое значение от любого адаптера ядро отбрасывает, и вход проходит без аватара) и хранит его как URI `data:`, MAX может отдать `https` URL | хранилище транзакций (снапшот идентичности канала); claim `picture` — только клиентам, запросившим scope `avatar`, в payload их access и refresh токенов в базе OpenIddict | транзакция: минуты. Записи токенов: до чистки (см. ниже) |
| Локаль | профиль в канале | хранилище транзакций; claim `locale` | так же |
| IP-адрес инициатора | запрос `/connect/authorize` | снапшот контекста инициатора внутри транзакции | транзакция: минуты. Не показывается и не логируется |
| Браузер, ОС, тип устройства | разобранный `User-Agent` того же запроса | снапшот контекста инициатора | так же. Показывается в сообщении подтверждения |
| Сырая строка `User-Agent` | тот же заголовок | снапшот контекста инициатора — **только если вы это включили** | так же |
| Страна и город | offline-GeoIP по тому же IP | снапшот контекста инициатора | так же. Показывается в сообщении как «Город, Страна» |
| Записи аудита | события транзакций и каналов | выбранный вами audit-приёмник | по умолчанию 90 дней; только если аудит включён |
| Ожидания RP (email или телефон, который заявляет приложение) | ваше приложение при создании транзакции | хранилище транзакций, **в зашифрованном виде** | транзакция: минуты |
| Параметры подтверждаемой операции | ваше приложение | записи аудита — **только если вы это включили** | срок хранения аудита |
| Claims, собранные Veriqa во время входа (телефон, email, поля формы), и отказы пользователя от `Optional` claims | пользователь, на шаге добора | хранилище собранных claims, по тенанту и `sub` — **только если вы его включили** | до удаления по `sub` или по заданному вами сроку |

**Чего нет в списке:** тела сообщений из канала (адаптер читает то, что нужно для опознания
отправителя и подтверждения, и не сохраняет остального) и любой долгоживущий профиль пользователя сверх собранных claims выше. Справочника пользователей
продукт **не поставляет** — см.
[Идентичности каналов](#идентичности-каналов-по-умолчанию-не-хранится-ничего).

## Умолчания сбора

Это включено «из коробки». Ни один пункт не требует вашего действия, чтобы начать; каждый требует
действия, чтобы прекратить.

| Настройка | Поставляется как | Что означает |
| - | - | - |
| `Veriqa:InitiatorContext:Enabled` | `true` | Снапшот контекста инициатора вообще собирается |
| `Veriqa:InitiatorContext:CollectIpAddress` | `true` | IP инициатора пишется в снапшот |
| `Veriqa:InitiatorContext:CollectUserAgent` | `true` | `User-Agent` разбирается на браузер / ОС / тип устройства |
| `Veriqa:InitiatorContext:CollectGeoLocation` | `true` | Страна и город определяются, **когда доступна база GeoIP** |
| `Veriqa:InitiatorContext:StoreRawUserAgent` | `false` | Сырой заголовок *не* хранится; только разобранные поля |

Две оговорки, которые важнее самих флагов.

**Геолокация включена по умолчанию, но по умолчанию бездействует.** Для поиска нужны сателлитный
пакет `Veriqa.Core.AuthServer.MaxMind` *и* локальная база `.mmdb`, а базовые пакеты не несут ни
того, ни другого. Без них провайдер объявляет себя недоступным, и гео-поля остаются пустыми — то
есть штатная установка местоположения не собирает, хотя флаг говорит `true`. Сбор начинается ровно с
добавлением базы.

**IP собирается, но никогда не показывается и не логируется.** Он попадает в снапшот внутри
транзакции и дальше не идёт: сообщение подтверждения называет только приложение, браузер, ОС и
регион, а диагностическая запись о недоверенной конфигурации проксирования фиксирует код результата,
а не адрес.

Выключается всё это одной секцией:

```json appsettings.json theme={null}
{
  "Veriqa": {
    "InitiatorContext": {
      "Enabled": false
    }
  }
}
```

При `Enabled: false` снапшот не строится, а сообщение подтверждения просто не несёт строки
контекста. Можно и сузить, сохранив контекст: убрать `region` из `DisplayFields` или поставить
`CollectIpAddress: false` (тогда отпадёт и геолокация — искать будет не по чему). Подробности и
сужение на уровне приложения — в [Контексте инициатора](/docs/ru/concepts/initiator-context).

## Где это хранится

| Хранилище | Что держит | Чем настраивается |
| - | - | - |
| Хранилище транзакций | всю живую транзакцию: OIDC-контекст запроса, снапшот идентичности канала, разрешённую идентичность, контекст инициатора | `ConfigureTransactionEngine` — InMemory, EF Core или Redis ([Хранилища](/docs/ru/guides/storage)) |
| База OpenIddict | OIDC-клиенты, авторизации и токены; subject токена — `{канал}:{channel_user_id}`. Access и refresh токены — reference-токены: у клиента идентификатор, а полный токен со всеми claims (для клиентов с `avatar` — и с `picture`) лежит в payload записи токена | `Veriqa:OpenIddict:Database` |
| Audit-приёмник | записи аудита, если аудит включён | `AddVeriqaAuditTrail` — in-memory, EF Core или ваша реализация |
| Хранилища каналов | action-токены Email (id транзакции + нормализованный email), push-корреляции Email (только id транзакции), координаты промпта, id обработанных писем | по умолчанию в процессе; Redis — для нескольких реплик ([Хранилища](/docs/ru/guides/storage)) |
| Хранилище собранных claims | claims, собранные Veriqa, и отказы от `Optional` claims, по тенанту и `sub`, если вы его включили | `Veriqa:CollectedClaims:Store:Enabled` или `UseEfCoreCollectedClaimsStore` ([Хранилища](/docs/ru/guides/storage#хранилище-собранных-claims)) |
| Кольцо ключей DataProtection | ключи, которыми защищены ожидания RP | `Veriqa:DataProtection:RedisConnectionString` или `:KeysDirectory` |

### Идентичности каналов: по умолчанию не хранится ничего

Порт, который сохранял бы идентичность канала — id пользователя, телефон, email, отображаемое имя,
upsert'ом, без TTL и без удаления, — это `IChannelIdentityRepository`, и **Veriqa не поставляет его
реализации и не регистрирует ни одной.** Вход работает и без неё: разрешённая идентичность едет
вместе с транзакцией и исчезает вместе с ней. Хранить эти данные бессрочно — действие, которое вы
совершаете осознанно, зарегистрировав свою реализацию; см.
[Хранилища](/docs/ru/guides/storage#канальные-идентичности-в-поставке-не-хранятся).

## Сроки хранения

| Что | Поставляется как | Откуда число |
| - | - | - |
| Живая транзакция | 300 с на подтверждение | `Veriqa:TransactionEngine:TransactionTtlSeconds` (60–1800) |
| Мёртвая транзакция (завершённая, неуспешная, истёкшая) | читается ещё 600 с, затем удаляется | `CompletedRetentionSeconds` (60–3600); проход — раз в `CleanupIntervalSeconds` (60 с) |
| Записи аудита | 90 дней | `Veriqa:Logging:RetentionDays`; проход — раз в час, батчами по 500 |
| Action-токены Email, push-корреляции, координаты промпта | до собственного `ExpiresAt` | сама сущность |
| Записи об обработанных письмах (дедупликация входящей почты) | 24 часа | `ProcessedMessageRetention` |
| Access / refresh токены, коды авторизации | действуют 1 ч / 14 д / 5 мин; запись удаляется, когда она уже недействительна и старше 24 ч | `Veriqa:OpenIddict:Server:*LifetimeSeconds`; удаление — `TokenPruneThresholdSeconds`, проход раз в `TokenPruneIntervalSeconds` (1 ч) |
| Идентичности каналов, если порт зарегистрирован | **бессрочно** | у контракта нет ни TTL, ни удаления — границу ставите вы |
| Собранные claims, если хранилище включено | **бессрочно**, пока вы не задали срок | `Veriqa:ClaimCompletion:CollectedClaims:RetentionDays`, отсчёт от последнего входа, прочитавшего записи; удаление по `sub` — [Хранилища](/docs/ru/guides/storage#удаление-по-sub) |

Итог: **хранилище транзакций опустошает себя примерно за пятнадцать минут** после входа, и
единственное хранилище Veriqa, где что-то живёт днями, — журнал аудита. Всё, у чего срок жизни
бессрочный, добавили вы: репозиторий идентичностей канала, хранилище собранных claims или токены со
сроком, который выбрали вы.

<Warning>
  **Базу OpenIddict чистит сам auth-сервер.** Фоновая задача раз в `TokenPruneIntervalSeconds`
  (1 ч) удаляет записи токенов и авторизаций, которые уже недействительны (истекли или отозваны) и
  созданы раньше, чем `Veriqa:OpenIddict:Server:TokenPruneThresholdSeconds` назад (по умолчанию 24 ч), — при любом провайдере
  хранилища. Действующие записи не удаляются. Пока запись не вычищена, её payload — для клиентов с
  `avatar` вместе с изображением аватара — лежит в базе, поэтому меньший порог сокращает срок хранения
  этих данных.
</Warning>

### Аудит выключен, пока вы не попросите дважды

Два независимых переключателя, и нужны оба:

1. Сателлит opt-in — хост, который не вызвал `AddVeriqaAuditTrail`, журнала не имеет вовсе.
2. `Veriqa:Logging:Mode` поставляется как `System`, а записи пишутся **только** в режиме `Audit`.
   `Disabled` и `System` для приёмника — одно и то же: не писать.

Ретенция, когда записи появились, третьим переключателем не является: проход регистрируется
автоматически для любого встроенного приёмника и удаляет всё старше `RetentionDays`.
`ConfigureRetention` лишь настраивает частоту прохода и размер батча — он не «включает» ретенцию, и
его отсутствие не оставляет записи без срока. Ретенция в ноль и меньше отвергается на старте, а не
читается как «не хранить ничего».

Единственный случай, когда не подметает ничто, — **ваш собственный приёмник, поданный через
`UseSink<TSink>()`.** Записи тогда живут в вашем хранилище, встроенный проход не регистрируется, и
хост говорит об этом на старте. Границу такому хранилищу ставите вы.

```json appsettings.json theme={null}
{
  "Veriqa": {
    "Logging": {
      "Mode": "Audit",
      "RetentionDays": 90
    }
  }
}
```

### Что на самом деле лежит в записи аудита

Закрытый набор атрибутов, зафиксированный схемой, — по колонке на атрибут, без произвольного
payload: момент, актор, код действия, объект (id транзакции), исход и код причины, плюс тип
транзакции, тип канала, correlation id, тенант, `client_id`, локаль и таймзона интерфейса,
собственные безопасные детали канала и объявленный для него уровень входящей верификации.

**Актор замаскирован.** Для действия, которое пользователь совершил в канале, это
`{канал}:{маскированный id}` — читаемыми остаются не более двух последних символов идентификатора.
Немаскированное значение не покидает хранилища транзакций.

**Ни IP, ни User-Agent, ни геолокация, ни отображаемое имя, ни email** в запись аудита не попадают.
Контекст инициатора живёт в транзакции и умирает вместе с ней.

Единственное исключение, которое можно включить, — параметры подтверждаемой операции: заявленный вид
действия и значения его слотов. Это предметные данные вашего приложения и в них может быть что
угодно, поэтому по умолчанию их нет, и в запись они копируются только по вашему требованию
([Харденинг](/docs/ru/guides/hardening#логирование-и-аудит)).

## Шифрование

| Что | Шифрует ли Veriqa? |
| - | - |
| Ожидания RP (email/телефон, который заявляет ваше приложение) | **Да** — ASP.NET Core Data Protection, изолированная purpose-строка; значения читаются и после ротации, пока прежний ключ в кольце |
| Всё остальное в хранилище транзакций — снапшоты идентичности, контекст инициатора, OIDC-контекст запроса | **Нет.** Обычные колонки / значения Redis |
| Записи аудита | **Нет** |
| Токены OpenIddict | Access и refresh токены доходят до клиента непрозрачными идентификаторами; токен в payload записи базы OpenIddict шифрует сам OpenIddict вашим сертификатом шифрования. `id_token` подписан, а не зашифрован |
| Файлы базы, дамп Redis, бэкапы | **Нет** — это ваш слой хранения: прозрачное шифрование, шифрование томов и защищённое подключение к БД настраивает ваш оператор |

Прикладного шифрования хранимых персональных данных сверх этого одного защищённого поля нет, как нет
и собственного управления ключами. Если ваша модель угроз требует шифрования ПД at-rest, это
требование закрывается на слое хранения, которым управляете вы — шифрованные тома, TDE, защищённое
соединение с базой, — а не настройкой в Veriqa.

Секреты — `client_secret`, токены ботов, учётные данные SMTP — живут в переменных окружения или
secret-store, но не в конфигах в репозитории. Пароли в лог не попадают: там, где пароль нужно
сравнить, сравнение идёт по SHA-256-хешу.

## Ключи

Три вида ключевого материала, три разных дома.

**Сертификаты подписи и шифрования токенов.** В `Development` используются dev-сертификаты
OpenIddict. Вне `Development` загружаются ваши X.509-сертификаты — путём или base64
(`Veriqa:OpenIddict:Server:SigningCertificate*` / `EncryptionCertificate*`), и без них хост не
стартует.

<Warning>
  **Замена сертификата инвалидирует сессии.** Конфигурация принимает ровно один сертификат подписи и
  один сертификат шифрования, поэтому в JWKS один ключ подписи и старый с новым нигде не
  пересекаются. Токены, подписанные прежним ключом, перестают проходить проверку в момент замены.
  Планируйте смену ключа как регламентное окно, а не как фоновую ротацию — то же предупреждение
  есть в [харденинге](/docs/ru/guides/hardening#issuer-и-сертификаты).
</Warning>

**Кольцо ключей DataProtection.** Приоритет — Redis
(`Veriqa:DataProtection:RedisConnectionString`), затем каталог
(`Veriqa:DataProtection:KeysDirectory`); **если не задано ни то, ни другое, ключи живут в памяти и
теряются при перезапуске.** Для разработки это нормально, везде остальное — нет: после рестарта
защищённые ожидания живых транзакций уже не прочитать. Data Protection ротирует свои ключи сам, и
значения, записанные прежним ключом, читаются, пока тот в кольце, — так что кольцо на устойчивом
хранилище делает ротацию незаметной.

**Секреты каналов и приложений** — токены ботов, webhook secret token, `client_secret`, учётные
данные SMTP. Их ротация — операция на стороне канала и клиента; в Veriqa они не кешируются дольше
перечитывания конфигурации.

## Что уходит за периметр

Только то, для чего вы настроили канал. На одно подтверждение исходящее сообщение несёт:

* **имя приложения**, начавшего вход;
* **браузер**, **ОС** и **регион** инициатора — там, где они известны и включены к показу;
* **ссылку или кнопки** подтверждения и срок действия запроса.

Оно не несёт ни IP-адреса, ни сырого `User-Agent`, ни профильных данных пользователя, ни того, что
ваше приложение заявило о транзакции.

Куда именно — зависит от канала: `api.telegram.org` для Telegram, `platform-api.max.ru` для MAX,
`graph.facebook.com` для WhatsApp Cloud API и ваш собственный SMTP-сервер для Email. В обратную
сторону канал возвращает профиль отправителя — поля из таблицы инвентаря выше. Мессенджер видит
содержимое сообщения и получателя, потому что это и означает «доставить сообщение»; канал Email
видно ровно столько, сколько видит ваш SMTP-релей.

**Внешняя шина событий.** Если подключить публикатор RabbitMQ, события транзакций покидают процесс,
неся только безопасную атрибуцию — в том числе маскированную идентичность канала. Параметры
подтверждаемой операции явно исключены из сериализации и на шину не попадают ни при какой настройке
аудита.

## Логи

Логи приложения — не журнал аудита и не задуманы как доказательство. Для этой страницы важно, чего в
них быть не должно и что с этим делает код: идентификаторы пишутся **16-символьным необратимым
отпечатком** (старшие байты SHA-256), а не собой, — так оператор по-прежнему сшивает шаги одного
входа, а лог при этом не держит идентичности. Токены, секреты и IP инициатора не логируются вовсе.

Это поставляемое поведение собственных лог-записей Veriqa. Ваш хост, ваш обратный прокси и
request-логирование платформы — вне его: access-лог перед auth-сервером по умолчанию пишет IP
клиентов, и границы *ему* ставятся тем же шагом развёртывания, что настраивает
`ForwardedHeaders:KnownProxies`.

## Что остаётся решить вам

Ни у одного пункта нет правильного ответа, который продукт мог бы выбрать за вас.

* Держать ли контекст инициатора вообще и добавлять ли базу GeoIP.
* Включать ли аудит, с каким сроком хранения и должны ли записи нести параметры подтверждаемых
  операций.
* Хранить ли идентичности каналов — и если да, как они удаляются.
* Включать ли хранилище собранных claims и с каким сроком хранения.
* Сколько истёкшие и отозванные записи токенов живут в базе OpenIddict до чистки.
* Где живёт кольцо ключей DataProtection.
* Шифрование at-rest, бэкапы и их сроки — на слое хранения.
* Как исполняется запрос пользователя на удаление по всем вашим хранилищам. Свои строки транзакций
  Veriqa удаляет по собственному расписанию, а записи аудита — по ретенции; хранилище собранных
  claims вы чистите по `sub` (эндпоинтом или вызовом хоста); до всего, что добавили вы,
  дотягиваетесь вы.

## Дальше

<Card title="Харденинг перед продом" icon="shield-check" href="/docs/ru/guides/hardening">
  Чек-лист перед выходом в прод.
</Card>

<Card title="Хранилища" icon="database" href="/docs/ru/guides/storage">
  Какое хранилище что держит и как каждое подключается.
</Card>

<Card title="Известные ограничения" icon="triangle-exclamation" href="/docs/ru/reference/limitations">
  Чего продукт не делает.
</Card>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.