fix(llm): close §19 redaction regex gaps for bare phone / CAPS name / bare SNILS (#1207) #1243

Merged
bot-backend merged 1 commit from fix/llm-redaction-pii-gaps into main 2026-06-13 05:04:47 +00:00
Collaborator

Summary

Closes #1207 — P2 security: §19 redaction regex имеет три дыры, через которые PII утекает в OpenAI при llm_enabled=True.

Verified воспроизведение

Прямо в redaction.py regex'ы:

  • _PHONE_RE = (?:\+7|\b8)[\s\-(]*\d{3}... — требует +7/\b8 + разделитель. Самая частая copy-paste «79221234567» (11 цифр с 7 без + и без \b) НЕ матчит.
  • _FULLNAME_RE = \b[А-ЯЁ][а-яё]+\s+[А-ЯЁ][а-яё]+\s+[А-ЯЁ][а-яё]+\b — требует строчные после первой буквы. Стандарт ЕГРН/паспортных выписок «ИВАНОВ ИВАН ИВАНОВИЧ» НЕ матчит.
  • _SNILS_RE = \b\d{3}-\d{3}-\d{3}[\s-]\d{2}\b — требует разделители. Голый «12345678901» НЕ покрывается (ИНН требует ровно 10/12, тут 11 — пропускает).

Для chat-пути scrub — единственный барьер (SafePayload.text без allowlist'а). При llm_enabled=True PII покидает РФ → §19 / 152-ФЗ.

Что в PR

  • _PHONE_BARE_RE = (?<!\d)[78]\d{10}(?!\d) — голые 11 цифр с 7/8.
  • _SNILS_BARE_RE = (?<!\d)\d{11}(?!\d) — любые голые 11 цифр.
  • _FULLNAME_RE расширен: каждое слово — [А-ЯЁ][а-яё]+ | [А-ЯЁ]{2,} (Titlecase или CAPS).
  • Конфликт phone-bare vs SNILS-bare разрешён порядком в _PII_PATTERNS: phone раньше SNILS (7/8-префикс семантически точнее СНИЛС'а).
  • ИНН (ровно 10/12 цифр) с SNILS-bare не пересекается.

Test plan

  • pytest tests/services/llm/test_redaction.py -v24 passed (16 старых + 8 новых: bare-phone-7/8, name-CAPS, name-mixed, snils-bare, phone-wins-over-snils, INN-not-snils).
  • pytest tests/services/llm/55 passed, никакой регрессии в LLM-suite.
  • ruff check → All checks passed.

Closes #1207

## Summary Closes #1207 — P2 security: §19 redaction regex имеет три дыры, через которые PII утекает в OpenAI при `llm_enabled=True`. ## Verified воспроизведение Прямо в `redaction.py` regex'ы: - `_PHONE_RE = (?:\+7|\b8)[\s\-(]*\d{3}...` — требует `+7`/`\b8` + разделитель. Самая частая copy-paste «**79221234567**» (11 цифр с 7 без + и без \b) НЕ матчит. - `_FULLNAME_RE = \b[А-ЯЁ][а-яё]+\s+[А-ЯЁ][а-яё]+\s+[А-ЯЁ][а-яё]+\b` — требует строчные после первой буквы. Стандарт ЕГРН/паспортных выписок «**ИВАНОВ ИВАН ИВАНОВИЧ**» НЕ матчит. - `_SNILS_RE = \b\d{3}-\d{3}-\d{3}[\s-]\d{2}\b` — требует разделители. Голый «**12345678901**» НЕ покрывается (ИНН требует ровно 10/12, тут 11 — пропускает). Для chat-пути scrub — **единственный** барьер (`SafePayload.text` без allowlist'а). При `llm_enabled=True` PII покидает РФ → §19 / 152-ФЗ. ## Что в PR - `_PHONE_BARE_RE` = `(?<!\d)[78]\d{10}(?!\d)` — голые 11 цифр с 7/8. - `_SNILS_BARE_RE` = `(?<!\d)\d{11}(?!\d)` — любые голые 11 цифр. - `_FULLNAME_RE` расширен: каждое слово — `[А-ЯЁ][а-яё]+ | [А-ЯЁ]{2,}` (Titlecase или CAPS). - Конфликт phone-bare vs SNILS-bare разрешён **порядком** в `_PII_PATTERNS`: phone раньше SNILS (7/8-префикс семантически точнее СНИЛС'а). - ИНН (ровно 10/12 цифр) с SNILS-bare не пересекается. ## Test plan - [x] `pytest tests/services/llm/test_redaction.py -v` → **24 passed** (16 старых + 8 новых: bare-phone-7/8, name-CAPS, name-mixed, snils-bare, phone-wins-over-snils, INN-not-snils). - [x] `pytest tests/services/llm/` → **55 passed**, никакой регрессии в LLM-suite. - [x] `ruff check` → All checks passed. Closes #1207
bot-backend added 1 commit 2026-06-13 04:57:22 +00:00
fix(llm): close §19 redaction regex gaps for bare phone / CAPS name / bare SNILS (#1207)
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-frontend (push) Has been skipped
CI / frontend-tests (push) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
Deploy / build-worker (push) Successful in 2m54s
CI / changes (push) Successful in 8s
CI / changes (pull_request) Successful in 6s
CI / backend-tests (push) Successful in 6m22s
CI / backend-tests (pull_request) Successful in 6m25s
Deploy / build-backend (push) Successful in 1m46s
Deploy / deploy (push) Successful in 1m10s
f12c03588d
`_PHONE_RE` требовал префикс +7/8 + хотя бы один разделитель → copy-paste форма
«79221234567»/«89221234567» (самая частая) уходила в OpenAI как есть. `_FULLNAME_RE`
требовал строчные после первой буквы → стандарт ЕГРН/паспортных выписок «ИВАНОВ
ИВАН ИВАНОВИЧ» не матчил. СНИЛС без разделителей не покрывался вообще (`_SNILS_RE`
ждёт формат «NNN-NNN-NNN NN»). Для chat-пути scrub — единственный барьер перед
OpenAI (SafePayload.text без allowlist'а), при llm_enabled=True PII покидает РФ
(нарушение §19/152-ФЗ).

Patch:
- _PHONE_BARE_RE: (?<!\d)[78]\d{10}(?!\d) — 11-значные с префиксом 7/8.
- _SNILS_BARE_RE: (?<!\d)\d{11}(?!\d) — любые 11-значные без разделителей.
- _FULLNAME_RE: альтернатива в каждом слове — Titlecase | CAPS-2+.
- Конфликт phone-bare vs SNILS-bare разрешён порядком в _PII_PATTERNS: phone
  раньше SNILS (7/8-префикс семантически точнее).
- INN (ровно 10/12) с SNILS-bare не пересекается по длине.

8 новых юнит-тестов покрывают все три дыры + конфликт-резолюцию. 24/24 redaction
тестов + 55/55 LLM-suite зелёные.

Closes #1207
bot-backend merged commit f12c03588d into main 2026-06-13 05:04:47 +00:00
bot-backend deleted branch fix/llm-redaction-pii-gaps 2026-06-13 05:04:47 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#1243
No description provided.