Строим AI-ассистента для бизнеса — и обнаруживаем, что каждое сообщение пользователя с персональными данными уходит в Google. Рассказываю, как это исправить, не сломав UX.

Когда мы запускали AI-ассистента для квалификации лидов в строительном бизнесе, первый же вопрос от клиента поставил меня в тупик: «А куда уходят персональные данные, которые люди вводят в чат?»

Я знал ответ. И он мне не нравился.

Пользователь пишет: «Меня зовут Дмитрий, наша компания ООО Ромашка, телефон +7 903 123-45-67, email dmitriy@company.com». Это сообщение в том же виде уходит в Google Gemini API для генерации ответа. Google получает PII — имя, телефон, email конкретного человека. Каждый раз. С каждым пользователем.

Для бизнеса в России это три проблемы одновременно.

Юридическая. 152-ФЗ требует, чтобы персональные данные российских граждан обрабатывались на территории РФ. Передача данных на серверы Google — даже для обработки, не хранения — это трансграничная передача данных, которая требует уведомления Роскомнадзора и согласия субъекта. Штрафы начинаются от 3 млн рублей.

Бизнес-риск. Контактная база клиентов — главный актив отдела продаж. Отдавать её в третьи руки, пусть даже крупной корпорации — вопрос корпоративной гигиены.

Этика. Клиент пишет в ваш чат. Он доверяет вам свои данные. Не Google.

Задача сформулировалась чётко: большая языковая модель должна вести диалог естественно — обращаться по имени, знать компанию, упоминать email — но никогда не получать реальные персональные данные. Звучит как противоречие. Решение оказалось элегантным.

Паттерн токен-подмены: принцип работы

Представьте сотрудника колл-центра, которому перед звонком говорят: «Клиента зовут КЛИЕНТ_ИМЯ, его компания — КОМПАНИЯ, телефон — ТЕЛЕФОН». Сотрудник ведёт разговор, использует эти метки, а на выходе стенограмма подставляет реальные данные. Сотрудник никогда не знал, с кем говорил.

Именно так работает защита PII при обращении к LLM.

Шаг 1. Пользователь вводит сообщение с реальными данными.

Шаг 2. Система перехватывает сообщение до отправки в Gemini API и заменяет все персональные данные на токены: Дмитрий → [USER_NAME], dmitriy@company.com → [USER_EMAIL], ООО Ромашка → [USER_COMPANY].

Шаг 3. LLM получает очищенный текст. Отвечает: «Отлично, [USER_NAME]! Подготовлю расчёт для [USER_COMPANY] и пришлю на [USER_EMAIL]».

Шаг 4. Система подставляет реальные данные обратно в ответ AI перед показом пользователю.

Шаг 5. Пользователь видит естественный ответ. AI не знал ничего реального. Роскомнадзор доволен.


Что пришлось решать по дороге — без купюр

Концепция простая. Production-реализация на NestJS — нет. Вот где мы споткнулись.

WebSocket стриминг ломал всё. LLM отвечает не целым текстом, а кусочками в реальном времени через WebSocket. Мы сначала восстанавливали PII только в финальный текст — и пользователь видел мелькающий [USER_NAME] пока ответ генерировался.

Потом обнаружили следующий баг: токен может быть разрезан между двумя чанками. Первый приходит [USER_, второй — NAME]. Каждый по отдельности — не токен, замена не срабатывает. Решение — буферизация: держать хвост чанка, убедиться что токен закрыт ], только тогда эмитить в WebSocket. Отдельный класс StreamingPiiRestorer, инстанцируется per-stream, не singleton — иначе буфер шарится между параллельными сессиями разных пользователей.

Один модуль обходил весь pipeline. Генератор финального расчёта читал сообщения напрямую из PostgreSQL и отправлял их в Gemini API без очистки. Все PII, собранные за весь диалог, утекали именно в этот момент — когда их было больше всего. Нашли только через аудит кода. Теперь каждый LLM-вызов проходит через единую точку входа — обойти физически невозможно.

Числа в строительных сметах взрывали regex. Для перехвата ИНН и ОГРН написали паттерн «10–15 цифр подряд». В строительном контексте это суммы: 100 000 000 рублей, артикулы, номера заказов — всё помечалось как персональные данные. Пришлось написать отдельные валидаторы с проверкой контрольных цифр по алгоритму ФНС и разделить паттерны: ИНН юрлица строго 10 цифр, ИНН физлица строго 12, ОГРН начинается с 1 или 5.

Архитектура защиты: пять слоёв

Слой 1 — токен-подмена известных PII. Имя, email, телефон, Telegram, компания из профиля диалога. Детерминированная замена, надёжность ~99%.

Слой 2 — regex-сканер для данных третьих лиц. Пользователь может упомянуть чужой контакт: «позвоните партнёру на partner@gmail.com» — этих PII нет в профиле. Сканер покрывает email, телефоны, ИНН, ОГРН, паспортные данные, IP-адреса, номера карт с валидацией.

Слой 3 — шифрование в PostgreSQL. Все PII-поля шифруются алгоритмом AES-256-GCM до записи в базу. Ключ хранится в env отдельно от БД. Снапшот без ключа — набор нечитаемых байт.

Слой 4 — HMAC-верифицируемый audit log. Каждая отправка в LLM фиксируется: что ушло (очищенный текст), какие поля были заменены. Для доказательства корректности обезличивания используется HMAC-SHA256 от оригинального текста с секретным ключом. Важно: SHA-256 без ключа от email — это псевдонимизация, которая по позиции РКН всё ещё является персональными данными. HMAC с управляемым ключом — иной правовой статус: без ключа значение необратимо.

Слой 5 — автоматическое удаление по data retention policy. По ст. 21 152-ФЗ данные уничтожаются после достижения цели обработки. Ночной cron анонимизирует PII в завершённых диалогах старше 90 дней батчами по 500 записей — без table lock на PostgreSQL.

Что это даёт бизнесу

Если вы владелец или менеджер: ваши клиенты общаются с AI-ассистентом и оставляют контакты. Эти контакты остаются вашими. Они не уходят в Google, не хранятся на зарубежных серверах. При проверке Роскомнадзора у вас есть журнал с криптографическими доказательствами того, что персональные данные никогда не покидали ваш контур в открытом виде.

При этом LLM работает так же хорошо — обращается по имени, помнит компанию, ведёт естественный диалог. Пользователь не замечает ничего.

Что осталось за кадром — честно

Имена третьих лиц в свободном тексте не перехватываются: «позвоните Ивану Петрову» — имя уйдёт в LLM. Regex не умеет отличать имена от слов. NER-модели (Named Entity Recognition) для русского языка дают точность 65–75% — это создаёт обратную проблему ложных срабатываний. Компромисс: имя без контактных данных — не actionable PII по 152-ФЗ.

Важнее другое: никакая техническая защита не заменяет административный compliance. Форма согласия на обработку персональных данных, уведомление Роскомнадзора по ст. 22, договор с поставщиком AI-сервиса (DPA с Google) — это отдельная работа. Технология решает технический слой. Юридический слой решают юристы.

Мы потратили несколько недель на то, чтобы довести это до уровня, где можно спокойно отвечать на вопрос «а куда уходят данные?».

Теперь есть что ответить.

Если вы строите AI-решения для российского рынка и столкнулись с похожей задачей — буду рад обсудить в комментариях. Исходная архитектура: NestJS + PostgreSQL + Google Vertex AI (Gemini), но паттерн применим к любому LLM-провайдеру.

Теги: информационная безопасность, персональные данные, 152-ФЗ, LLM, большие языковые модели, NestJS, Gemini API, защита данных, PII, бэкенд, разработка

Комментарии (15)


  1. drbond
    27.03.2026 07:46

    А не проще локальный инференс поднять? У вас с расчётом сметы только Gemini 3.1 Pro может справиться? Или если совсем жалко денег - купить токены у российских провайдеров (Яндекс, Сбер, MWS)?


    1. Dimus-frontes Автор
      27.03.2026 07:46

      Сразу уточню: используется Gemini 2.5 Flash, не Pro — по соотношению качество/цена на этой задаче он вне конкуренции.

      По пунктам:

      Локальный инференс — рабочий вариант, но цена входа высокая. Качественный reasoning на русскоязычном строительном контексте начинается от 70B-моделей, это ~40 ГБ VRAM + инфраструктура + обслуживание. Для стартапа на ранней стадии — overhead без очевидной отдачи.

      Российские провайдеры — честная альтернатива по 152-ФЗ, данные не покидают правовое поле без доп. усилий. Единственный нюанс: YandexGPT и GigaChat пока уступают по структурированному выводу и многошаговому reasoning на специализированных задачах. Это не навсегда — слежу за развитием.

      Но главное, что статья не про выбор модели. Описанный паттерн PII-изоляции одинаково работает с любым провайдером — Gemini, YandexGPT, локальной Llama. Слой санитизации отделён от транспорта: смена провайдера = замена одного адаптера, архитектура не меняется. В этом и есть суть подхода.


  1. pasgalk
    27.03.2026 07:46

    Сейчас исследую возможности и способы по анонимизации данных
    В статье не увидел конкретных библиотек/фреймворков/инструментов, которыми пользовались помимо regexp. Но ведь регулярными выражениями далеко не все покрывается...
    Еще в статье указан NER, но не совсем понятно что использовали или вовсе не использовали, тк точность хромает.
    Если бы немного раскрыли эту тематику, то было бы супер


    1. Dimus-frontes Автор
      27.03.2026 07:46

      Хороший вопрос — намеренно не раскрыл это в статье…

      Рассматривал три варианта:

      Microsoft Presidio — самый зрелый фреймворк, но для моего кейса не подошёл: требует Python-сайдкар к NestJS-стеку, NER на русском даёт 70–85% точности (модель обучена на новостях, а не на бизнес-переписке), плюс 20–100 мс латентности на каждое сообщение при стриминге — это ощутимо.

      Google Cloud DLP — отличный API, но есть ирония: отправляем PII в Google, чтобы не отправлять PII в Google Gemini. Юридически та же трансграничная передача.

      Почему в итоге regex, а не NER. Ключевой инсайт, который я недостаточно раскрыл в статье: выбор зависит не от того, «что точнее», а от того, откуда приходят PII.

      В моём кейсе AI-консультант сам запрашивает email, телефон и телеграм через структурированный флоу — к моменту sanitization система уже знает все данные пользователя. Дальше это тривиальный String.replace() с ~99% надёжностью и нулевой латентностью. Regex-guard нужен только для данных третьих лиц (email, телефоны, ИНН с валидацией контрольных цифр).

      Если у вас другой кейс — анализ произвольных документов, где PII заранее не известны — без NER не обойтись. Тогда смотрите в сторону Presidio + DeepPavlov BERT для русского, или Stanza (точнее spaCy из коробки, но медленнее). Для JS/TS — ONNX Runtime с экспортированной моделью, но честно: зрелых решений под русский NER в этой экосистеме пока нет.


      1. pasgalk
        27.03.2026 07:46

        Да, как раз пробую разные NER spaCy (ru), Natasha в связке с presidio, но получается так себе, очень много ложных срабатываний. И есть надежда на покрытие regexp телефонов, email, ИНН и тд
        У меня нет потребности RealTime анонимизации, думаю мб модельку небольшую еще натравить.
        Спасибо за ответ


  1. S1mleX
    27.03.2026 07:46

    gigachat3.1-10b-a1.8b отлично справляется с этой задачей
    вы не думали что можно просто дать модели российской маленькой сделать то что вы делали ручками

    Скорость более чем подходящая и для стартапа приемлемо иметь более просто и костыльное решение.

    NVIDIA GeForce RTX 5060 Ti

    VRAM Capacity: 15.93 GB

    Мне кажется ваши труды не стоят одной потребительской видюхи.

    Во всяком случае ловит эта модель неплохо. Но для коммерции стоит взять либо по больше либо не квантованную.


    1. Dimus-frontes Автор
      27.03.2026 07:46

      Спасибо за замечание, но здесь есть важный нюанс — мы с вами решаем разные задачи.

      GigaChat для детекции PII — это окей идея, я даже согласен что для Layer 2 (имена третьих лиц) что-то подобное было бы уместно. Но вся остальная архитектура — это не про «поймать данные», это про доказуемо доказать что ты их не передал.

      Квантованная 10b-модель — вероятностная. У неё будут false negatives. В spam-фильтре — норм. Но когда приходит РКН и спрашивает «а вы точно не гнали персоналку в Google?» — ответ «ну модель обычно ловит процентов девяносто пять» не очень работает.

      Наш Layer 1 детерминирован именно потому что данные уже известны системе заранее — это не детекция, это механическая подстановка. GigaChat сюда просто не нужен физически.

      Про GPU в продакшне — RTX 5060 Ti для экспериментов отличная штука, но это единая точка отказа без автоскейлинга и SLA. Для стартапа это не «проще и дешевле», это другая статья расходов — только платишь не деньгами, а временем и нервами.

      HMAC audit log, data retention, схема верификации — это не overengineering ради overengineering. Это доказательная база. Видеокарта её не заменит.

      Так что — интересное дополнение к одному конкретному слою? Да. Замена всей архитектуры? Нет, потому что архитектура решает юридическую задачу, а не только техническую.


      1. pasgalk
        27.03.2026 07:46

        Могу ошибаться, но мне кажется помимо запрашиваемых PII у пользователя в переписку может попасть что-то, что является pii и пользователь ввел их самостоятельно по какой-то причине и в формате, котором посчитал нужным. Причем не обязательно третьих лиц, и даже если третьих лиц, то можно ли считать это персональными данными? Будто бы, можно.
        Тогда далеко не факт, что ваши проверки вычислят такое по regexp.
        Конечно тут больше интересно, как регулятор на такое смотрит и может ли докопаться


        1. Dimus-frontes Автор
          27.03.2026 07:46

          Честно — замечание попадает в реальную дыру. Пользователь может написать «мой прораб Иван Петрович, звони ему напрямую» — и никакой regex это имя не поймает. По 152-ФЗ это всё равно персональные данные третьего лица, и ответственность с оператора не снимается. РКН за такое при плановой проверке скорее всего не докопается — но если случится утечка и в ней окажутся такие данные, вопрос «почему не фильтровали» встанет в полный рост.

          Решается это не переработкой архитектуры, а добавлением одного слоя — локальной NER-модели для русского языка. Например natasha: 50MB, работает на CPU, ~10ms на сообщение, ставится через pip. Она умеет находить имена людей в свободном тексте включая падежные формы — «Петровичу», «Ромашки» — и заменять их на [REDACTED_NAME_1] до отправки в Gemini. Ничего наружу не уходит, всё крутится на том же сервере. Это не замена текущей архитектуры — это закрытие одной конкретной дыры одним небольшим инструментом, который как раз кейс с GigaChat, только без видеокарты и внешних зависимостей.


  1. S1mleX
    27.03.2026 07:46

    Важно: SHA-256 без ключа от email — это псевдонимизация, которая по позиции РКН всё ещё является персональными данными. HMAC с управляемым ключом — иной правовой статус: без ключа значение необратимо.

    А как это объясняется? Атака дополнением? Тогда можно SHA-3 вместо SHA-2.


    1. Dimus-frontes Автор
      27.03.2026 07:46

      Да, length extension attack — реальная штука, и SHA-3 её действительно лишён. Но проблема с SHA-256(email) совсем в другом. Email-адреса — это данные с катастрофически низкой энтропией. Берёшь утёкшую базу на 100 млн адресов, прогоняешь все через SHA-256 — получаешь радужную таблицу за несколько часов. SHA-3 от той же проблемы не спасёт вообще никак, просто хэши будут другие, а перебор работает идентично.

      HMAC решает именно это — не потому что SHA-2 «слабее», а потому что секретный ключ добавляет ту энтропию, которой у email от природы нет. Без ключа задача перебора становится физически неразрешимой. Так что вопрос не в том, какой алгоритм хэширования выбрать — а в том, есть ли ключ вообще. Это принципиально разные вещи.


      1. S1mleX
        27.03.2026 07:46

        Да я понял о каком кейсе идет речь но где используется хеш? Для адресации по базе с PII?

        Ключ желательно чтобы был не один. По сути нужен алгоритм посолки или для вас ближе к перчению хешей.

        https://en.wikipedia.org/wiki/Pepper_(cryptography)


        1. Dimus-frontes Автор
          27.03.2026 07:46

          Хороший вопрос, у нас хэши используются исключительно в audit log и только для integrity check — то есть доказать что конкретный текст был именно таким в момент отправки в AI. Для адресации PII в базе хэши вообще не используются — там зашифрованные поля AES-256-GCM, поиск по ним не предусмотрен.

          Теперь про перец. Концептуально он решает ту же задачу что и HMAC-ключ — добавляет секрет, который хранится отдельно от базы. Разница в том, что pepper обычно применяется к хэшам паролей (bcrypt + pepper), а HMAC изначально спроектирован именно как «хэш с ключом» и стандартизирован для таких сценариев. В нашем случае originalHmac = HMAC(text, SECRET_KEY) — это уже и есть pepper по своей природе, только без лишней самодеятельности.

          Идея про несколько ключей — валидная для key rotation: когда меняешь ключ, старые записи верифицируются старым, новые — новым. Это запланировано, но отложено — ключ пока ни разу не менялся, задача на будущее.

          Так что спасибо за ссылку, но pepper здесь уже есть — он просто называется HMAC-ключом.


          1. S1mleX
            27.03.2026 07:46

            audit log и только для integrity check — то есть доказать что конкретный текст был именно таким в момент отправки в AI.

            "конкретный текст" значит все такие хешируеться промт после санитизации? Тогда у такого текста вполне достаточная энтропия.

            Я наверно не очень понимаю, что такое аудит в данном контексте.


  1. tbp2k5
    27.03.2026 07:46

    По-моему вы занимаетесь самообманом. LLM по сколько либо заметному разговору/разговорам с обычным человеком определит и кто USER_NAME_1 и COMPANY_2 и остальное. В этом суть LLM. Можете кстати протестировать и попросить Gemini построить соответствия между вашими идентификаторами и реальными людьми/фирмами/почтовыми адресами - может он даже вам ответит... И представите результат специализированного промпта без оганичений окна и с дополнительным контекстом.

    Не совсем в тему, но близко - почитайте как с Google Translate спалились товарищи : https://theins.ru/inv/290209