АвтопилотЛабстудия автоматизацииОбсудить задачу
Навигация по базе знаний

База знаний · Нейросети

Как скрыть персональные данные перед отправкой в нейросеть

Как скрыть персональные данные перед отправкой в нейросеть. Регулярные выражения для телефонов, ИНН, СНИЛС и карт, поиск имён и обратная подстановка.

Опубликовано 5 мин чтения

Эта статья описывает технический приём. Она не заменяет консультацию юриста по обработке персональных данных.

Перед отправкой текста в языковую модель персональные данные можно заменить метками вида [ТЕЛЕФОН_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.

Что делать, если нейросеть изменила метку?

Проверяйте ответ перед обратной подстановкой. Если в ответе осталась метка, которой нет в таблице, или исчезла нужная, отправляйте результат человеку на проверку.

Готовые модули по теме