# Схема архитектуры и настройки Модуль стоит между системой-потребителем и внешней языковой моделью. Потребитель отдаёт текст один раз на вход и один раз на выход. В модель уходит уже подменённый текст, потребителю возвращается текст с восстановленными значениями. ```mermaid flowchart LR consumer["Система-потребитель"] api["POST /process и POST /proxy"] policy["Политика системы\nconfig/systems.json"] rules["Правила и словари"] ner["Вторая ступень\nNER, если включена"] filters["Отсев ложных\nсрабатываний"] masker["Маскирование\nMASK / STRICT / TOKEN / SYNTHETIC"] store["Хранилище соответствий\nпамять, AES-GCM"] llm["Внешняя LLM\nили заглушка"] consumer --> api --> policy --> rules --> ner --> filters --> masker --> store masker -->|"только /proxy"| llm llm -->|"демаскирование по токенам"| consumer store -->|"демаскирование по payload_id"| consumer ``` `POST /process` — контракт для системы-потребителя: маскирование и демаскирование. `POST /proxy` — демонстрация всей цепочки до модели и обратно; проверяющий контур может его не использовать. Настройки систем, перечень типов и вид маски задаются файлом `config/systems.json`. Код для смены политики пересобирать не нужно. Файл перечитывается при изменении и по `POST /admin/reload`. ## Что происходит с одним текстом 1. По заголовку `X-System-Id` выбирается политика. Неизвестное имя получает политику `default`. Выключенная система и неверный `X-System-Key` получают `403` до обработки текста. 2. Если `payload_id` уже есть в хранилище этой системы, направление определяется сравнением текста с сохранённой маской и с исходником. 3. Иначе текст проходит детекцию. Сначала правила: контрольные суммы (карта, ИНН, СНИЛС, ОГРН), однозначные форматы (email, телефон), затем шаблоны с якорным словом (документ, адрес, дата, ФИО). Цифровые значения с нестандартными разделителями дополнительно собираются в кластер и проверяются той же контрольной суммой. 4. Если включена вторая ступень, модель смотрит только непокрытые кандидаты: цепочки слов с заглавной буквы и, для юридической модели, цифровые кластеры. В поставке по умолчанию ступень выключена (`pdguard.ner.*-engine: off`). В `compose.yaml` она включается, если в каталог `models/` положены веса. 5. Ложные срабатывания снимаются до маски: имя в составе организации и на вывеске, общеизвестное имя без других персональных данных рядом, адрес отделения, типы-спутники без самостоятельного персонального данного. 6. Оставшиеся фрагменты заменяются по `maskMode` системы. Пара «исходный текст ↔ маска» пишется в хранилище этой системы. Исходный текст в хранилище шифруется AES-GCM, ключ — `pdguard.store.encryption-key`. Демаскирование не запускает детектор заново: по `payload_id` (и запасным отпечатком маски) достаётся сохранённый исходник. Поэтому звёздочки тоже обратимы, пока жива запись. ## Системы-потребители Файл `config/systems.json`, путь переопределяется свойством `pdguard.systems-file`. | Система | Включена | Демаскирование | Режим | Типы | |---------|----------|----------------|-------|------| | `default` | да | да | `MASK` | все (`*`) | | `crm` | да | нет | `TOKEN` | ФИО, телефон, email, город, улица, дом, квартира | | `analytics` | да | нет | `SYNTHETIC` | все (`*`) | | `strict` | да | да | `STRICT` | все (`*`) | | `legacy-billing` | нет | нет | `MASK` | все (`*`), запросы отклоняются | Запрос без заголовка идёт в `default`. Хранилище у каждой системы своё: одна система не читает соответствия другой. Поля политики: | Поле | Назначение | |------|------------| | `enabled` | `false` — модуль отвечает `403` и текст не обрабатывает | | `demask` | разрешено ли обратное преобразование | | `maskMode` | чем заменяется найденное значение | | `types` | какие типы маскировать; `"*"` — все, которые знает сборка | | `requireCompanion` | типы, которые маскируются только рядом с самостоятельным персональным данным | | `key` | общий секрет; если задан, заголовок `X-System-Key` обязан совпасть. Только ASCII | Пример добавления системы — дописать объект и перечитать файл: ```json "dms": { "enabled": true, "demask": true, "maskMode": "MASK", "types": ["FIO", "PHONE", "MEDICAL_POLICY", "BIRTH_DATE"], "key": "dms-secret" } ``` После `POST /admin/reload` запросы с `X-System-Id: dms` и `X-System-Key: dms-secret` маскируют только перечисленные типы, звёздочками, и умеют демаскировать. Новый тип персональных данных добавляется правилом в реестре (`DocumentRules`, `FinanceRules`, `DateRules`, `FioRules`, `ContactRules`, `AddressRules`) и именем в `PdTypes`. Политики, где указано `"*"`, подхватывают его без правки конфига. Политика с явным списком — только если имя типа туда добавить. ## Типы персональных данных Список отдаёт `GET /admin/types`. Группы: | Группа | Типы | |--------|------| | Человек | `FIO`, `CARDHOLDER` | | Документы | `PASSPORT`, `PASSPORT_ISSUER`, `PASSPORT_DATE`, `DEPT_CODE`, `FOREIGN_PASSPORT`, `DRIVER_LICENSE`, `MILITARY_ID`, `BIRTH_CERTIFICATE`, `MEDICAL_POLICY` | | Контакты | `PHONE`, `EMAIL` | | Адрес | `ADDRESS_COUNTRY`, `ADDRESS_POSTCODE`, `ADDRESS_CITY`, `ADDRESS_STREET`, `ADDRESS_HOUSE`, `ADDRESS_FLAT`. `ADDRESS_REGION` и `ADDRESS_DISTRICT` размечает только модель второй ступени | | Даты и гражданство | `BIRTH_DATE`, `BIRTH_PLACE`, `DATE`, `CITIZENSHIP` | | Платёжные данные | `CARD`, `CARD_EXPIRY`, `CVV`, `PIN` | | Реквизиты | `INN`, `SNILS`, `ACCOUNT_NUMBER`, `BIK`, `OGRN`, `OGRNIP`, `KPP` | | Прочее | `INCOME`, `BIOMETRIC` | Правила устойчивы к регистру, к дате числом и словами, к вставке слов между серией и номером документа, к уменьшительным формам имён. Карта, ИНН, СНИЛС, ОГРН и ОГРНИП без верной контрольной суммы не маскируются. ## Режимы маскирования Режим задаётся полем `maskMode` и действует на все типы, которые политика разрешила. | Режим | Пример для `Иванов Иван Иванович` и паспорта `4509 123456` | Когда уместен | |-------|--------------------------------------------------------------|---------------| | `MASK` | `И. И. И.`, паспорт `45** ****56` | человеку остаётся узнаваемый контур, середина закрыта. Края коротких серий (загранпаспорт, военный билет, свидетельство о рождении) не открываются. CVV и PIN закрываются целиком | | `STRICT` | сплошные звёздочки на всю длину, включая ФИО | ничего из исходных знаков не остаётся | | `TOKEN` | `[FIO_1]`, `[PASSPORT_1]` | однозначная обратимая подстановка, удобная и для демаскирования ответа модели | | `SYNTHETIC` | вымышленные ФИО и номер той же формы | модель видит правдоподобный текст. Для типов без своей подстановки остаётся токен | Одинаковое исходное значение внутри одного текста получает одну и ту же замену. В `POST /proxy` режим системы для отправки в модель заменяется на `TOKEN`: одинаковые звёздочки нельзя однозначно вернуть на место в ответе модели. ## Контекстное правило Типы из `requireCompanion` сами по себе персональными данными не считаются. Они маскируются, только если в том же тексте есть находка самостоятельного типа. В политиках `default` и `strict` спутники такие: `CVV`, `PIN`, `DATE`, `BIRTH_PLACE`, `ADDRESS_COUNTRY`, `ACCOUNT_NUMBER`, `BIK`, `OGRN`, `OGRNIP`, `KPP`, `INCOME`, `BIOMETRIC`. Два спутника друг друга не подтверждают. Дата рядом с ОГРН без имени и документа человека не маскируется. PIN рядом с номером карты — маскируется. Список спутников у каждой системы свой; пустой список означает, что маскируется всё найденное из `types`. Отдельно от этого списка снимаются ложные ФИО и адреса: «Александр Пушкин» без других персональных данных, «Институт Склифосовского», адрес отделения банка. Если рядом с общеизвестным именем есть другой тип персональных данных, имя остаётся замаскированным: однофамилец защиту не теряет. ## Состояние и наблюдаемость Хранилище соответствий по умолчанию — память процесса (`pdguard.store.backend=memory`): потолок `pdguard.store.max-chars` (512 МБ символов), срок жизни записи `pdguard.store.ttl-minutes` (30 минут). При `backend=redis` та же запись дублируется в Redis, чтобы демаскирование попало на другой узел. В текущем `compose.yaml` поднят один узел, Redis не используется. Одновременные запросы ограничивает адаптивный предел: он растёт, пока задержка укладывается в `pdguard.target-latency-ms` (200 мс), и сжимается, когда перестаёт. Лишние запросы получают `429`, очередь не копится. Метрики отдаёт Micrometer на `/actuator/prometheus`. Готовые Prometheus и Grafana лежат в `monitoring/` и поднимаются тем же `docker compose`.