Files
pd-guard/docs/02-jury-check.md
T

14 KiB
Raw Blame History

Инструкция для жюри по проверке

Модуль принимает текст, находит в нём персональные данные, подменяет их и по тому же идентификатору возвращает исходный текст. Ниже — как это воспроизвести и где смотреть журнал и метрики.

Сервис слушает http://localhost:8080.

Запуск

Нужны Java 21 и Maven 3.8+ (или обёртка ./mvnc). Для дашборда — Docker Compose.

Локально:

./mvnc package -DskipTests
java -jar target/pd-guard-spring-1.0.0.jar

Контейнер, Prometheus и Grafana (compose.yaml ссылается на уже собранный образ pd-guard-spring:jvm, сам его не строит):

docker build -f src/main/docker/Dockerfile -t pd-guard-spring:jvm .
docker compose up -d

Если локальной Java нет — тот же образ собирается полностью внутри Docker, mvn package идёт в отдельной стадии сборки (дольше первого запуска, зато не требует ничего на хосте кроме Docker):

docker build -f src/main/docker/Dockerfile.build -t pd-guard-spring:jvm .
docker compose up -d

Готовность: GET http://localhost:8080/health отвечает OK.

Действующие системы читаются из config/systems.json. Это не встроенный файл в jar: при запуске из каталога проекта используется именно он.

Как устроен запрос

POST /process

Тело:

{"payload": "текст", "payload_id": "устойчивый-идентификатор"}

Заголовки:

Заголовок Смысл
X-System-Id имя системы из config/systems.json. Нет заголовка или имя неизвестно — применяется политика default
X-System-Key нужен только если у системы в конфиге задано поле key

Направление выбирается по payload_id, а не по отдельному флагу:

  1. Идентификатор ещё не встречался — текст маскируется, пара «исходник ↔ маска» запоминается.
  2. Пришёл ранее выданный текст маски и у системы включено demask — возвращается исходный текст.
  3. Пришёл тот же исходный текст — возвращается та же маска, что и в первый раз.

Ответ: {"result": "..."}.

Коды: 200 успех, 400 нет payload или payload_id, 403 система выключена или неверный ключ, 429 перегрузка (заголовок Retry-After: 1). При внутреннем сбое сервис отвечает 200 и текстом [обработка недоступна], чтобы исходные персональные данные не ушли наружу.

Маскирование в браузере

Откройте http://localhost:8080/.

Четыре режима на странице — это четыре системы из конфига:

Кнопка Заголовок Что увидите
MASK default звёздочки с открытыми краями номера, ФИО инициалами
STRICT strict сплошные звёздочки, длина сохраняется
TOKEN crm токены вида [FIO_1], [PASSPORT_1]
SYNTHETIC analytics правдоподобная подмена (вымышленное ФИО, номер, адрес)

Вставьте текст, например:

Клиент Иванов Иван Иванович, паспорт 4509 123456, тел +7 916 123-45-67

Нажмите «Обработать». Результат появится в блоке под кнопкой.

Страница каждый раз создаёт новый payload_id. Она показывает только маскирование. Демаскирование проверяется запросом к API с тем же идентификатором.

Маскирование через API

Система default маскирует все известные типы и умеет демаскировать. Режим — звёздочки с открытыми краями номера (MASK).

curl -s -X POST http://localhost:8080/process \
  -H "Content-Type: application/json" \
  -H "X-System-Id: default" \
  -d "{\"payload\":\"Клиент Иванов Иван Иванович, паспорт 4509 123456, тел +7 916 123-45-67\",\"payload_id\":\"doc-1\"}"

Ожидаемый вид ответа: в result ФИО заменено на инициалы (И. И. И.), середина паспорта и телефона закрыта звёздочками, края видны.

Сплошные звёздочки — тот же запрос с заголовком X-System-Id: strict.

Токены вида [FIO_1], [PASSPORT_1], [PHONE_1] — тот же запрос с заголовком X-System-Id: crm (но у crm в политике нет PASSPORT, см. ниже).

Синтетика — X-System-Id: analytics. У этой системы обратное преобразование выключено.

Система crm маскирует только ФИО, телефон, email и части адреса (город, улица, дом, квартира). Паспорт в её политике не входит и в ответе останется открытым. Демаскирование у crm выключено.

Система legacy-billing выключена: тот же запрос с X-System-Id: legacy-billing даёт 403.

Демаскирование

Нужны три условия одновременно:

  • тот же payload_id, что при маскировании (doc-1 в примере выше);
  • в payload — точная строка из поля result предыдущего ответа;
  • система с "demask": true. Сейчас это default и strict. У crm и analytics демаскирование выключено намеренно.
curl -s -X POST http://localhost:8080/process \
  -H "Content-Type: application/json" \
  -H "X-System-Id: default" \
  -d "{\"payload\":\"<сюда строка result из маскирования>\",\"payload_id\":\"doc-1\"}"

В result вернётся исходная фраза с ФИО, паспортом и телефоном.

Соответствие живёт в памяти процесса 30 минут и пропадает после перезапуска. Повторный запрос после перезапуска будет обработан как новое маскирование.

Хранилище разделено по системам: маска, полученная от default, не раскрывается запросом от strict с тем же payload_id.

Цепочка до модели

POST /proxy показывает, что ушло бы во внешнюю модель и что вернулось бы потребителю. Адрес модели по умолчанию пуст, поэтому отвечает заглушка: она возвращает присланный (уже замаскированный) текст. В модель подставляются токены, даже если у системы выбран другой режим: по звёздочкам однозначное восстановление невозможно.

curl -s -X POST http://localhost:8080/proxy \
  -H "Content-Type: application/json" \
  -H "X-System-Id: default" \
  -d "{\"prompt\":\"Клиент Иванов Иван Иванович, карта 4111 1111 1111 1111. Кратко опишите профиль.\"}"

В ответе:

Поле Смысл
prompt_masked текст, который ушёл бы в модель
llm_response_masked ответ модели (или заглушки) ещё с токенами
response текст потребителю; при demask: true токены заменены обратно
replaced таблица «токен → исходное значение»
llm заглушка либо имя модели

В prompt_masked не должно остаться открытых ФИО и номера карты.

Где смотреть логи

Отдельного файла журнала нет. Строки пишет процесс в стандартный вывод.

Локальный запуск — окно, где выполнена команда java -jar.

Docker:

docker compose logs -f pd-guard

На каждое маскирование есть строка уровня INFO. В ней идентификатор, длина текста и счётчики по типам. Сами значения персональных данных в журнал не пишутся:

payload_id=doc-1 символов=72 найдено={FIO=1, PASSPORT=1, PHONE=1}

Рядом по смыслу, если они случаются:

  • Системе … обращение в модуль запрещено настройками — выключенная система;
  • Системе … отказано: неверный ключ;
  • демаскирование не нашло соответствие — идентификатор и отпечаток маски неизвестны, текст обработан как новый;
  • Настройки систем перечитаны из … — после правки config/systems.json.

Для прокси: proxy: система=… заменено=… модель=…. Число замен есть, значения — нет.

Где смотреть метрики

Сырые метрики сервиса, без Docker:

Адрес Что это
GET /actuator/prometheus все метрики в формате Prometheus
GET /actuator/metrics список имён Micrometer
GET /actuator/metrics/pdguard.process длительность обработки
GET /actuator/metrics/pdguard.pd.detected сколько фрагментов найдено, в разрезе типа и системы
GET /actuator/health проба Spring Boot

Имеет смысл смотреть после одного-двух вызовов /process:

curl -s http://localhost:8080/actuator/prometheus | findstr pdguard

На Linux и macOS вместо findstr — grep pdguard.

Основные ряды:

Метрика О чём
pdguard_process_seconds длительность, метки direction (mask / unmask) и system
pdguard_pd_detected_total найденные фрагменты, метки type и system
pdguard_requests_rejected_total отказы: overload, malformed, system_disabled, internal_error
pdguard_concurrency_limit текущий потолок одновременных запросов
pdguard_concurrency_in_flight сколько запросов в работе
pdguard_store_chars объём хранилища соответствий в символах
pdguard_tokens_processed_total оценка числа обработанных токенов
pdguard_demask_unresolved_total демаскирование без найденного соответствия
pdguard_ner_* вторая ступень (модель). В поставке без скачанных моделей ступень выключена, ряды почти не растут

Дашборд появляется вместе с docker compose:

  • Grafana: http://localhost:3000 (анонимный просмотр включён). Дашборд «Модуль безопасности персональных данных»: http://localhost:3000/d/pd-guard. Обновление раз в 5 секунд. Блоки: обращения и типы ПДн, задержка и доля ответов быстрее 0,5 с, отказы, вторая ступень, ресурсы узла.
  • Prometheus: http://localhost:9090. Цель сбора — pd-guard:8080, интервал 5 секунд.

Что поменять без перезапуска

Отредактируйте config/systems.json и вызовите:

curl -s -X POST http://localhost:8080/admin/reload

Файл также перечитывается сам, если изменилось время модификации (проверка не чаще раза в секунду). Текущие политики: GET /admin/config. Список типов, которые умеет распознавать сборка: GET /admin/types.

Проверка набора типов: в политике crm нет PASSPORT. Текст с паспортом и телефоном под X-System-Id: crm скроет телефон и оставит номер паспорта. Под default скроет оба.