Эта статья описывает технический приём. Она не заменяет консультацию юриста по обработке персональных данных.
Перед отправкой текста в языковую модель персональные данные можно заменить метками вида [ТЕЛЕФОН_1], а после ответа вернуть их обратно. Ниже рабочий код для n8n и разбор того, что этот приём закрывает, а что нет.
Что понадобится
- n8n на своём сервере.
- Узел Code для регулярных выражений.
- Для имён и адресов отдельный сервис на Python рядом с n8n: Natasha или Presidio.
Шаг 1. Решить, что маскировать
Модели обычно не нужны конкретные телефоны и номера документов. Чтобы определить тему обращения или оценить звонок, достаточно знать, что в тексте был телефон. Маскируйте всё, что не влияет на результат:
- телефоны и email;
- ИНН физических лиц и ИП, СНИЛС, номера паспортов;
- номера банковских карт и счетов;
- имена, фамилии, адреса.
Номера заказов и артикулы обычно оставляют, если модель должна на них ссылаться.
Шаг 2. Замаскировать структурированные данные
Телефоны, email и номера документов хорошо находятся регулярными выражениями. Чтобы не принять за ИНН или СНИЛС случайный номер заказа, код проверяет контрольные цифры, а номера карт проверяет по алгоритму Луна.
Узел Code в режиме Run Once for Each Item, исходный текст в поле text:
const digits = (s) => s.replace(/\D/g, '');
// Алгоритм Луна для номеров карт
function luhnOk(num) {
let sum = 0;
for (let i = 0; i < num.length; i++) {
let d = Number(num[num.length - 1 - i]);
if (i % 2 === 1) {
d *= 2;
if (d > 9) d -= 9;
}
sum += d;
}
return sum % 10 === 0;
}
// Контрольные цифры ИНН: 10 цифр у организаций, 12 у физлиц и ИП
function innOk(inn) {
const d = inn.split('').map(Number);
const ctrl = (w) => (w.reduce((s, k, i) => s + k * d[i], 0) % 11) % 10;
if (d.length === 10) return ctrl([2, 4, 10, 3, 5, 9, 4, 6, 8]) === d[9];
if (d.length === 12) {
return ctrl([7, 2, 4, 10, 3, 5, 9, 4, 6, 8]) === d[10] &&
ctrl([3, 7, 2, 4, 10, 3, 5, 9, 4, 6, 8]) === d[11];
}
return false;
}
// Контрольное число СНИЛС
function snilsOk(s) {
const d = s.split('').map(Number);
let sum = 0;
for (let i = 0; i < 9; i++) sum += d[i] * (9 - i);
let ctrl = sum % 101;
if (ctrl === 100) ctrl = 0;
return ctrl === d[9] * 10 + d[10];
}
const map = {}; // метка -> исходное значение
const seen = new Map(); // нормализованное значение -> метка
const counters = {};
function tag(type, value, norm) {
const key = type + ':' + norm;
if (!seen.has(key)) {
counters[type] = (counters[type] || 0) + 1;
const label = `[${type}_${counters[type]}]`;
seen.set(key, label);
map[label] = value;
}
return seen.get(key);
}
let text = $json.text || '';
// 1. Email
text = text.replace(/[\w.+-]+@[\w-]+(?:\.[\w-]+)+/g,
(m) => tag('EMAIL', m, m.toLowerCase()));
// 2. Карты: 16 цифр подряд или группами по 4
text = text.replace(/(?<!\d)\d{4}([ -]?)\d{4}\1\d{4}\1\d{4}(?!\d)/g,
(m) => (luhnOk(digits(m)) ? tag('КАРТА', m, digits(m)) : m));
// 3. Телефоны: +7 или 8 и ещё 10 цифр
text = text.replace(/(?<!\d)(?:\+7|8)[\s(-]*\d{3}[\s)-]*\d{3}[\s-]*\d{2}[\s-]*\d{2}(?!\d)/g,
(m) => tag('ТЕЛЕФОН', m, digits(m).slice(-10)));
// 4. СНИЛС: 123-456-789 01 или 11 цифр подряд
text = text.replace(/(?<!\d)(?:\d{3}-\d{3}-\d{3}[ -]\d{2}|\d{11})(?!\d)/g,
(m) => (snilsOk(digits(m)) ? tag('СНИЛС', m, digits(m)) : m));
// 5. ИНН: 12 или 10 цифр с верными контрольными цифрами
text = text.replace(/(?<!\d)(?:\d{12}|\d{10})(?!\d)/g,
(m) => (innOk(m) ? tag('ИНН', m, m) : m));
return { json: { ...$json, text_masked: text, pd_map: map } };
Порядок замен важен. Сначала идут карты и телефоны, потом СНИЛС и ИНН, иначе длинные номера разрежутся на части. Одинаковые значения получают одну метку: +7 (916) 123-45-67 и 89161234567 станут одним [ТЕЛЕФОН_1]. Так модель понимает, что речь об одном номере.
Код намеренно перестраховывается. Десятизначный номер заказа иногда случайно проходит проверку ИНН и тоже заменяется меткой. Для паспортов, счетов и номеров карт другой длины добавьте свои шаблоны по тому же принципу.
Шаг 3. Найти имена и адреса
Регулярные выражения не отличат фамилию Орлов от слова «орлов» в тексте про птиц. Для имён нужны модели распознавания именованных сущностей. Обе библиотеки ниже работают на Python, поэтому их запускают отдельным сервисом на том же сервере и вызывают из n8n узлом HTTP Request.
- Natasha. Открытая библиотека для русского языка под лицензией MIT. Находит имена (PER), места (LOC) и организации (ORG). Умеет приводить найденное к начальной форме: «Петру Порошенко» превращается в «Петр Порошенко». Авторы предупреждают, что модели обучены на новостях и на других текстах качество может быть ниже.
- Presidio. Открытый фреймворк для поиска и обезличивания данных, лицензия MIT. Изначально его выпускала Microsoft, сейчас проект развивается в организации Data Privacy Stack. По умолчанию Presidio настроен на английский. Для русского нужно подключить русскую модель spaCy, например
ru_core_news_lg. Готовых распознавателей для ИНН и СНИЛС в Presidio нет, их добавляют как собственные распознаватели с регулярным выражением и проверкой контрольной суммы.
Сервис возвращает найденные фрагменты текста. n8n заменяет их метками [ИМЯ_1] или [АДРЕС_1] и дописывает в ту же таблицу соответствий. Для начала обычно хватает Natasha для имён и кода из шага 2 для остального. Presidio удобнее, когда нужна единая система правил для многих типов данных.
Шаг 4. Хранить таблицу соответствий у себя
Поле pd_map из шага 2 связывает метки с исходными значениями. В нейросеть уходит только text_masked. Таблица остаётся на вашем сервере: в данных выполнения n8n или в вашей базе, если ответ приходит позже.
Учтите, что n8n сохраняет данные выполнений вместе с исходным текстом. Срок их хранения задают переменные EXECUTIONS_DATA_PRUNE и EXECUTIONS_DATA_MAX_AGE. По умолчанию данные удаляются через 336 часов, то есть 14 дней.
Шаг 5. Вернуть данные в ответ
Если ответ нейросети нужно отправить клиенту, подставьте исходные значения обратно. Узел Code после запроса к модели, ответ в поле answer:
const map = $('Маскирование').item.json.pd_map;
let answer = $json.answer || '';
for (const [label, value] of Object.entries(map)) {
answer = answer.split(label).join(value);
}
return { json: { ...$json, answer } };
Здесь Маскирование это название узла из шага 2.
С именами есть проблема падежей. Если в таблице лежит «Иван Петров», а модель написала «Передайте [ИМЯ_1] документы», после подстановки получится «Передайте Иван Петров документы». Есть три способа с этим жить:
- просить модель ставить метки только там, где имя стоит в именительном падеже, например «Клиент: [ИМЯ_1]»;
- не возвращать имена в текст, если ответ нужен для внутреннего анализа;
- склонять имя перед подстановкой морфологическим анализатором, например pymorphy3. Это работает не для всех фамилий, особенно иностранных и редких.
Почему 100% не бывает
- Люди пишут номера с ошибками, пропусками и в словах: «восемь девятьсот шестнадцать…».
- Модели распознавания пропускают редкие имена и ошибаются на разговорном тексте и расшифровках звонков.
- Данные могут идентифицировать человека косвенно: должность, адрес дома, редкая болезнь, история заказа.
- Нейросеть может исказить метку, и обратная подстановка не сработает.
Поэтому маскирование уменьшает объём данных, которые уходят в нейросеть, но не заменяет выбор надёжного поставщика. Для самых чувствительных текстов надёжнее локальная модель на своём сервере.
Что говорит закон
По статье 3 152-ФЗ обезличивание персональных данных означает действия, после которых без дополнительной информации нельзя определить, кому принадлежат данные. Таблица соответствий и есть такая дополнительная информация. Обезличенные данные остаются персональными, и требования закона к ним сохраняются: цели обработки, хранение в России, защита, договор с подрядчиком. Подробнее в статьях про 152-ФЗ и автоматизацию и поручение на обработку данных.
Частые ошибки
- Регулярное выражение съело лишнее. Без границ
(?<!\d)и(?!\d)шаблон ИНН найдёт десять цифр внутри номера счёта. Проверяйте код на реальных текстах. - Метки в разных регистрах. Один и тот же email с большими и маленькими буквами получил две метки. Нормализуйте значения перед сравнением, как в коде выше.
- Таблица потерялась. Ответ пришёл позже, а данные выполнения уже удалены. Храните
pd_mapв своей базе, если обработка асинхронная. - Персональные данные в уведомлениях. Исходный текст попал в сообщение в Telegram, серверы которого находятся за рубежом. Отправляйте туда номер сделки и ссылку на CRM.
Частые вопросы
Если заменить данные метками, их можно отправлять куда угодно?
Нет. По 152-ФЗ обезличенные данные остаются персональными, а таблица соответствий позволяет восстановить исходные значения. Маскирование снижает риск утечки, но требования закона сохраняются.
Можно ли найти имена регулярными выражениями?
Надёжно нельзя. Имена, фамилии и адреса ищут моделями распознавания сущностей, например библиотекой Natasha для русского языка или Presidio с русской моделью spaCy.
Что делать, если нейросеть изменила метку?
Проверяйте ответ перед обратной подстановкой. Если в ответе осталась метка, которой нет в таблице, или исчезла нужная, отправляйте результат человеку на проверку.