fix(llm): close §19 redaction regex gaps for bare phone / CAPS name / bare SNILS (#1207) #1243
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
вторичка
ИРД
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#1243
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/llm-redaction-pii-gaps"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Closes #1207 — P2 security: §19 redaction regex имеет три дыры, через которые PII утекает в OpenAI при
llm_enabled=True.Verified воспроизведение
Прямо в
redaction.pyregex'ы:_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=TruePII покидает РФ → §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)._PII_PATTERNS: phone раньше SNILS (7/8-префикс семантически точнее СНИЛС'а).Test plan
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).pytest tests/services/llm/→ 55 passed, никакой регрессии в LLM-suite.ruff check→ All checks passed.Closes #1207
`_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