From e52e9550e6596fed9128ccbe085670ebf4af72a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=D0=B5=D0=BD=D0=BA?= =?UTF-8?q?=D0=BE=20=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0=20=D0=92=D0=BB?= =?UTF-8?q?=D0=B0=D0=B4=D0=B8=D0=BC=D0=B8=D1=80=D0=BE=D0=B2=D0=B8=D1=87?= Date: Mon, 21 Sep 2026 21:25:55 +0300 Subject: [PATCH] feat: extend requireCompanion to BIRTH_PLACE and ADDRESS_COUNTRY MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same reasoning as the existing CVV/PIN/DATE entries: a bare mention ("Экскурсия в Нижний Новгород", "цены выросли в Казахстане") isn't personal data on its own, only alongside some other PD that ties it to a specific person. The mechanism was already generic — this only adds two more types to the default set. Co-Authored-By: Claude Sonnet 5 --- config/systems.json | 2 +- .../java/ru/pdguard/config/SystemPolicy.java | 7 +++-- .../java/ru/pdguard/ContextDetectionTest.java | 29 +++++++++++++++++-- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/config/systems.json b/config/systems.json index ed10b52..e06dfdb 100644 --- a/config/systems.json +++ b/config/systems.json @@ -4,7 +4,7 @@ "demask": true, "maskMode": "MASK", "types": ["*"], - "requireCompanion": ["CVV", "PIN", "DATE"] + "requireCompanion": ["CVV", "PIN", "DATE", "BIRTH_PLACE", "ADDRESS_COUNTRY"] }, "crm": { "enabled": true, diff --git a/src/main/java/ru/pdguard/config/SystemPolicy.java b/src/main/java/ru/pdguard/config/SystemPolicy.java index 28ce823..da6662c 100644 --- a/src/main/java/ru/pdguard/config/SystemPolicy.java +++ b/src/main/java/ru/pdguard/config/SystemPolicy.java @@ -13,7 +13,9 @@ import java.util.Set; * @param types типы ПД к маскированию; {@code "*"} — все известные * @param requireCompanion типы, которые маскируются только вместе с ПД другого типа: * пин-код сам по себе безвреден, пин-код рядом с номером - * карты — уже нет; то же для даты без якорного слова + * карты — уже нет; то же для даты без якорного слова, места + * рождения («Нижний Новгород» в рассказе о городе — не адрес + * клиента) и страны («цены выросли в Казахстане» — не гражданство) */ public record SystemPolicy(boolean enabled, boolean demask, MaskMode maskMode, Set types, Set requireCompanion) { @@ -22,7 +24,8 @@ public record SystemPolicy(boolean enabled, boolean demask, MaskMode maskMode, /** Политика по умолчанию: маскируем всё, что умеем, обратное преобразование включено. */ public static final SystemPolicy DEFAULT = new SystemPolicy( - true, true, MaskMode.MASK, Set.of(ALL), Set.of("CVV", "PIN", "DATE")); + true, true, MaskMode.MASK, Set.of(ALL), + Set.of("CVV", "PIN", "DATE", "BIRTH_PLACE", "ADDRESS_COUNTRY")); public SystemPolicy { types = Set.copyOf(types); diff --git a/src/test/java/ru/pdguard/ContextDetectionTest.java b/src/test/java/ru/pdguard/ContextDetectionTest.java index 5b9464f..1f1ed02 100644 --- a/src/test/java/ru/pdguard/ContextDetectionTest.java +++ b/src/test/java/ru/pdguard/ContextDetectionTest.java @@ -76,8 +76,33 @@ class ContextDetectionTest { @Test void masksBirthPlace() { - assertHidden("Место рождения: город Тверь, проживает в Москве", "город Тверь"); - assertHidden("Родился в Нижнем Новгороде", "Нижнем Новгороде"); + // Место рождения — тип из requireCompanion: без другого ПД рядом не маскируется + // («Нижний Новгород» в рассказе о городе не должен теряться), поэтому в тесте + // на распознавание якоря рядом добавлен телефон. + assertHidden("Место рождения: город Тверь, проживает в Москве, тел. +7 916 123-45-67", "город Тверь"); + assertHidden("Родился в Нижнем Новгороде, тел. +7 916 123-45-67", "Нижнем Новгороде"); + } + + @Test + void doesNotMaskBirthPlaceWithoutAnyOtherPersonalData() { + String text = "Экскурсия в Нижний Новгород перенесена на май"; + assertEquals(text, mask(text), "место рождения без другого ПД рядом не маскируется"); + } + + @Test + void masksBirthPlaceWhenOtherPersonalDataIsAlsoPresent() { + assertHidden("Место рождения: город Тверь, ИНН 770301234550", "город Тверь"); + } + + @Test + void doesNotMaskCountryWithoutAnyOtherPersonalData() { + String text = "Цены на нефть выросли в Казахстане в этом квартале"; + assertEquals(text, mask(text), "страна без другого ПД рядом не маскируется"); + } + + @Test + void masksCountryWhenOtherPersonalDataIsAlsoPresent() { + assertHidden("Страна проживания Казахстан, ИНН 770301234550", "Казахстан"); } @Test