Когда начиналась реализация конструктора документов для razvodex.ru, задача на первый взгляд выглядела довольно просто: пользователь отвечает на вопросы, загружает документы, а система собирает из этого иск, заявление, ходатайство или другой юридический документ.
Потом в архитектуре появились OCR, распознавание речи и нейросеть. Пользователь может загрузить паспорт, свидетельство о рождении ребенка, документы на недвижимость или старый иск. Система сама извлекает нужные данные и использует их при формировании документа.
И примерно в этот момент обычный Laravel-проект превращается в информационную систему персональных данных.
Потому что мы имеем дело уже не с email и номером телефона. Через систему потенциально проходят ФИО, даты рождения, паспортные данные, ИНН, СНИЛС, адреса регистрации, сведения о детях, браке, недвижимости, доходах, судебных спорах и масса информации о других людях.
А значит, вопрос «у нас сервер находится в России, этого достаточно?» перестает быть риторическим.
Спойлер: нет.
И вот что мы поняли, пока разбирались, как технически строить такой сервис.
Самая неприятная ошибка — воспринимать 152-ФЗ как требование к серверу
Первое упрощение звучит примерно так:
Купим VPS в российском дата-центре, включим HTTPS и будем соответствовать
На практике российский сервер решает только часть задачи.
С 1 июля 2025 года часть 5 статьи 18 закона прямо запрещает при сборе персональных данных граждан РФ использовать находящиеся за пределами России базы данных для записи, систематизации, накопления, хранения, изменения и извлечения таких данных, кроме прямо установленных законом исключений. Поэтому место физического размещения базы действительно критично.
Но дальше начинаются нюансы.
Где находится сам VPS? Где провайдер хранит snapshots? Где лежат резервные копии? Может ли его техническая поддержка получить доступ к диску? Что происходит с данными в панели управления? Куда приложение отправляет ошибки? Где работает Redis? Куда уезжают Laravel jobs? Не отправляется ли тело запроса в зарубежный error-tracker? Не лежит ли загруженный паспорт в S3-бакете другого провайдера? Не попадает ли адрес пользователя в иностранную аналитику?
Оказалось, что база mysql далеко не единственное место, где приложение умеет хранить персональные данные.
Поэтому применительно к нашему конструктору мы стали смотреть не на «сервер», а на полный маршрут данных.
Примерно так:
Браузер пользователя
↓
Nginx
↓
Laravel
↙ ↓ ↘
файлы DB Redis/Queue
↓
Yandex Vision OCR
↓
извлеченные поля
↓
обезличивание
↓
YandexGPT / LLM
↓
шаблон документа
↓
Laravel подставляет реальные ФИО,
адреса и реквизиты
И только когда рисуешь такую схему, становится видно, что именно необходимо защищать.
Паспорт, ИНН и СНИЛС — это еще не самое сложное
Есть интересный юридический нюанс.
Паспортные реквизиты, СНИЛС, ИНН, адрес и сведения о недвижимости — персональные данные, но сами по себе они обычно не относятся к «специальным категориям» персональных данных.
Проблема в другом.
Мы разрешаем пользователю работать с юридическими документами.
А в иске или приложениях к нему может внезапно оказаться диагноз ребенка, сведения о лечении, национальности, семейной или интимной жизни и другие данные, которые уже попадают в специальные категории.
То есть нельзя построить модель угроз исходя из предположения:
«Мы собираем только паспорт и СНИЛС».
Если пользователь может загрузить произвольный судебный документ, архитектуру разумнее проектировать с учетом того, что внутри файла могут оказаться специальные категории персональных данных, даже если мы специально их не запрашивали.
Именно категория обрабатываемых данных, количество субъектов и актуальные для системы типы угроз используются при определении необходимого уровня защищенности по постановлению Правительства № 1119. В зависимости от комбинации этих факторов устанавливается один из четырех уровней защищенности.
Поэтому писать в документации «у нас УЗ-3» просто потому, что так сделал соседний SaaS, — плохая идея.
Сначала определяется состав ИСПДн, категории данных, субъекты, модель угроз и возможный вред. Потом уже определяется необходимый уровень защищенности и набор мер.
А фотография в паспорте это биометрия?
Еще один вопрос, на котором легко запутаться.
В системе есть паспорт. В паспорте есть фотография. Значит ли это, что мы автоматически обрабатываем биометрические персональные данные?
Не обязательно.
Статья 11 определяет биометрические персональные данные как физиологические или биологические особенности человека, которые используются оператором для установления личности.
То есть само наличие изображения лица еще не означает, что мы используем биометрию для идентификации.
Мы не сравниваем лицо с паспортом, не строим face embedding, не проводим аутентификацию по голосу или фотографии.
Vision OCR нужен нам для распознавания текста.
SpeechKit — для превращения речи в текст.
Это принципиальное отличие.
При этом фотография и голос все равно остаются информацией о человеке и требуют нормальной защиты. Поэтому отсутствие биометрической идентификации не означает, что файл можно спокойно положить в публичный bucket.
Главная архитектурная идея: нейросеть не должна знать, как зовут человека
Самое полезное решение, которое мы нашли, вообще не связано с сертифицированными межсетевыми экранами.
Мы просто перестали передавать генеративной модели лишние персональные данные.
Представим, что пользователь хочет сформировать иск о расторжении брака.
Технически можно отправить в LLM такой prompt:
Составь исковое заявление.
Истец: Иванов Иван Иванович,
паспорт 45 01 123456,
адрес: Москва, ул. ...
Ответчик: Иванова Мария Сергеевна...
Ребенок: Иванов Петр Иванович,
дата рождения...
Но возникает очевидный вопрос:
зачем языковой модели знать номер паспорта?
Для выбора структуры и формулировок документа это обычно совершенно не требуется.
Поэтому архитектура нашего проекта может работать иначе.
OCR получает документ и возвращает извлеченные поля. Laravel нормализует их и сохраняет в защищенном контуре. Перед обращением к LLM мы заменяем идентифицирующую информацию плейсхолдерами:
Составь исковое заявление.
Истец: [PLAINTIFF]
Ответчик: [DEFENDANT]
Брак зарегистрирован 12.03.2019.
Есть один несовершеннолетний ребенок.
Спора о месте проживания ребенка нет.
Требование о разделе имущества не заявляется.
Модель возвращает:
[PLAINTIFF] просит суд расторгнуть брак,
заключенный с [DEFENDANT]...
А реальные ФИО, адреса, даты и реквизиты уже после генерации подставляет Laravel.
Получается два разных контура.
Контур идентификации знает паспорт, ФИО, адрес и СНИЛС.
Контур генерации текста знает юридически значимые обстоятельства.
Это полезно не только с точки зрения
Поэтому один из наших внутренних принципов можно сформулировать так:
если нейросети не обязательно знать персональные данные — она их не получает.
OCR — другое дело
С Vision OCR полностью избавиться от передачи исходного документа сложнее.
Чтобы распознать паспорт, сервис должен увидеть изображение паспорта.
Здесь Яндекс становится не просто API, возвращающим текст, а участником обработки персональных данных.
В материалах Yandex Cloud прямо указано, что при размещении клиентом персональных данных на ресурсах облака Яндексу поручается их обработка. Yandex Cloud также заявляет соответствие инфраструктуры требованиям
Это важный момент.
Использование Yandex Cloud Vision, SpeechKit или другого сервиса Яндекса не делает проект автоматически соответствующим
Аттестат облачной платформы — это не аттестат нашего Laravel-приложения.
Поэтому для каждого внешнего процессора мы отдельно проверяем договорную конструкцию, актуальное соглашение об обработке данных, географию обработки и хранения, правила удаления и технического логирования, а также то, входит ли конкретный используемый сервис и сценарий в нужный нам контур соответствия.
Согласие нельзя прятать под кнопкой «Я принимаю оферту»
Это еще один момент, который изменился сравнительно недавно.
С 1 сентября 2025 года
Поэтому старая конструкция:
☑ Я принимаю оферту, политику конфиденциальности
и даю согласие на обработку персональных данных
для нового проекта выглядит особенно сомнительно.
У разводекс.ру юридические основания вообще приходится проектировать не менее тщательно, чем базу данных.
Для обработки данных самого клиента частью основания может быть необходимость исполнения договора с ним — такое основание прямо предусмотрено статьей 6. Но если мы поручаем обработку стороннему сервису, работаем с дополнительными целями или опираемся именно на согласие, это нужно корректно отражать в документах и интерфейсе.
Еще сложнее ситуация с чужими данными.
Пользователь может загрузить свидетельство о рождении ребенка, указать данные бывшего супруга, собственника квартиры или другого человека.
Галочка пользователя «я согласен» не дает ему магического права предоставлять согласие от имени всех этих людей.
Для ребенка ситуация зависит от полномочий законного представителя. Для остальных субъектов нужно отдельно определить законное основание обработки.
Закон, например, допускает обработку в связи с участием лица в гражданском и других видах судопроизводства, а также в некоторых случаях для осуществления законных интересов третьих лиц при условии, что права субъекта не нарушаются. Но использовать эти нормы как универсальную индульгенцию для любого загруженного документа мы бы не стали. Основания нужно картировать по конкретным бизнес-процессам.
Из этого появляется еще один важный продуктовый принцип:
не спрашивать у пользователя данные, которые не нужны для конкретного документа.
Для иска о разводе не нужно собирать СНИЛС ответчика «на всякий случай».
Лучший способ защитить персональные данные — вообще их не получить.
Самое опасное место Laravel-проекта — иногда не база, а лог
Представим обычную production-ошибку.
Пользователь отправляет форму:
{
"passport": "4501123456",
"snils": "123-456-789 00",
"address": "...",
"child_birth_certificate": "..."
}
Laravel получает исключение.
Разработчик заботливо пишет:
Log::error('Document generation failed', [
'request' => $request->all(),
'exception' => $e->getMessage(),
]);
Поздравляем.
Теперь у нас появилась еще одна база персональных данных — laravel.log.
А если подключен внешний error tracker, эти же данные потенциально отправились еще и туда.
Поэтому мы исходим из правила: production-логи не содержат значения персональных данных.
Нам достаточно записать:
user_id=48291
document_id=73d2...
operation=passport_ocr
status=failed
error_code=VISION_TIMEOUT
А не содержимое паспорта.
То же касается URL.
Вместо:
/document?id=123&snils=12345678900
используется непрозрачный идентификатор объекта.
Персональные данные не должны попадать в URL, nginx access logs, trace-id metadata, exception contexts и analytics events.
На production обязательно отключается APP_DEBUG, а middleware и обработчики исключений отдельно проверяются на предмет случайного логирования request body, cookies, Authorization header и файлов.
Laravel Queue тоже умеет незаметно превратиться в хранилище паспортов
Еще одна классическая проблема — очереди.
Можно написать:
GenerateClaim::dispatch(
$user->passport_number,
$ocrText,
$address
);
После чего весь этот набор сериализуется в Redis или таблицу jobs.
И снова появляется дополнительное место обработки персональных данных.
Мы стараемся передавать в queue только идентификатор:
GenerateClaim::dispatch($documentId);
Worker получает documentId, сам загружает разрешенные ему данные, выполняет операцию и забывает их.
Такая модель еще и сильно упрощает контроль доступа.
Если задача удалена из очереди, внутри Redis не остается текст целого искового заявления.
Загруженный паспорт — это временный объект, а не вечная фотография
Следующее решение касается файлов.
Первоначальная архитектура конструктора документов почти всегда соблазняет сделать так:
storage/app/uploads/passport.jpg
и оставить файл там навсегда.
Мы пришли к противоположному подходу.
Если пользователю не нужна функция постоянного хранения оригиналов, исходный документ имеет короткий жизненный цикл.
Он попадает во временное приватное хранилище, проходит проверку типа файла и антивирусную обработку, отправляется на OCR, из него извлекаются нужные поля, после чего исходник уничтожается согласно установленной политике хранения.
Для объектов используются UUID, а не имена вроде:
ivanov_ivan_passport_4501123456.jpg
Bucket никогда не публичный.
Ссылка на файл — временная и подписанная.
Служба поддержки по умолчанию файл не видит.
В базе хранится не «вечная ссылка на паспорт», а идентификатор объекта и состояние его жизненного цикла.
И отдельно проектируется удаление.
Это заставило нас отдельно подумать о backup.
Потому что удалить строку из PostgreSQL недостаточно, если следующие полгода она продолжает жить в ежедневных snapshots.
Поэтому срок хранения резервных копий тоже является частью privacy architecture.
Шифрование — это не функция Crypt::encryptString() и галочка «152-ФЗ»
Laravel позволяет достаточно просто шифровать значения.
Для паспорта, СНИЛС, ИНН и подобных атрибутов можно применять application-level encryption. Ключ при этом не должен лежать рядом с зашифрованной базой в том же git-репозитории.
Но мы специально не формулируем это как:
Используем AES-256, поэтому соответствуем
Закон требует не конкретного красивого названия алгоритма, а комплекса правовых, организационных и технических мер, основанного на актуальных угрозах. Статья 19 требует определить угрозы и применять меры, необходимые для установленного уровня защищенности, а их состав конкретизируется постановлением № 1119 и приказом ФСТЭК № 21.
Поэтому у нас есть несколько отдельных уровней защиты.
Диски и backup защищаются от физической компрометации. Критичные поля дополнительно шифруются на уровне приложения. Секреты API и ключи шифрования отделены от базы. Между компонентами используется защищенное соединение. Доступы строятся по принципу минимальных привилегий.
А вопрос о необходимости конкретных сертифицированных средств защиты и СКЗИ уже определяется моделью угроз и выбранной системой защиты, а не маркетинговой фразой в README.
Мы разделили обычные данные и «хранилище личности»
В обычном приложении таблица пользователей может постепенно превратиться в монстра:
users
├─ name
├─ email
├─ phone
├─ passport
├─ snils
├─ inn
├─ child_name
├─ child_birth_date
├─ spouse_passport
└─ ...
Для юридического сервиса нам больше нравится другой подход.
Есть обычный пользователь и техническая учетная запись.
Отдельно существует защищенный набор идентификационных данных.
Отдельно — факты конкретного юридического дела.
Отдельно — исходные документы.
Отдельно — сгенерированный документ.
Это означает, что сервису, который формирует юридическую логику, вовсе не обязательно иметь прямой доступ к таблице с паспортами.
Если злоумышленник получает read-only доступ к одной части приложения, он не должен автоматически получать полный профиль человека вместе со всеми документами.
Администратор сайта не должен быть богом
Обычно в раннем стартапе есть /admin, после авторизации в котором основатель может увидеть вообще всё.
Для сервиса с паспортами это очень плохая привычка.
Поддержке нужен статус операции:
OCR завершился ошибкой.
Код: INVALID_DOCUMENT_FORMAT.
Ей совершенно не обязательно видеть фотографию паспорта.
Разработчику нужен trace.
Ему не нужен текст иска.
Бухгалтеру нужна информация о платеже.
Ему не нужны сведения о ребенке.
Поэтому административные права разделяются.
Особенно чувствительный доступ должен быть исключением — например, временный break-glass доступ для расследования конкретного инцидента с обязательным журналированием.
И это как раз тот случай, когда требования закона хорошо совпадают с нормальной инженерной практикой: приказ ФСТЭК № 21 включает идентификацию и аутентификацию, управление доступом, регистрацию событий безопасности, защиту носителей, антивирусную защиту и другие группы мер.
Даже идеальный Yandex Cloud не исправит chmod 777
На стороне Yandex Cloud с комплаенсом ситуация для российского проекта довольно удобная.
По состоянию на 2026 год Yandex Cloud заявляет, что единый сегмент публичного облака соответствует требованиям
Но модель ответственности остается разделенной.
Если наш VPS открыт наружу на 3306, SSH доступен по паролю, APP_DEBUG=true, секретный ключ Yandex лежит в GitHub, а Object Storage публичный, никакой аттестат облачного провайдера нас не спасет.
То же самое относится к обычному VPS вне Yandex Cloud.
Такой вариант сам по себе не запрещен.
Но тогда нам приходится особенно внимательно проверять физическое местоположение инфраструктуры, договор с хостером, его доступ к данным, резервные копии, субподрядчиков и собственную систему защиты.
С точки зрения разработки базовая topology выглядит примерно так:
Internet
│
▼
Reverse proxy / WAF
│
▼
Laravel application
│
├──── PostgreSQL/MySQL
│ только private network
│
├──── Redis
│ только private network
│
├──── Private document storage
│
└──── AI gateway
│
├── Vision OCR
├── SpeechKit
└── LLM
У базы нет публичного IP.
Redis не виден из интернета.
SSH разрешен только администраторам через ограниченный контур.
У каждого внешнего API свой service account и минимально необходимые права.
Секреты не хранятся в исходном коде.
Production и development разделены.
А разработчики не скачивают production-базу с паспортами себе на MacBook, чтобы «быстро посмотреть баг».
Последний пункт, кстати, оказался одним из самых полезных.
Тестовая база должна быть ненастоящей
Очень просто хорошо защитить production и через месяц получить утечку через staging.
Кто-то делает:
pg_dump production > dump.sql
Потом импортирует дамп на dev-сервер.
И внезапно все настоящие ФИО, паспорта, СНИЛС и документы оказываются в среде, где доступ есть у половины команды.
Поэтому тестовые среды должны получать синтетические данные.
Не «Иван Иванов, но паспорт оставили настоящий».
А полностью искусственный профиль.
Если воспроизведение production-проблемы требует реальных данных, это уже отдельный контролируемый процесс, а не стандартный сценарий разработки.
SpeechKit мы рассматриваем так же, как OCR
Голосовой ввод кажется менее опасным.
Пользователь говорит:
«Мы поженились в 2017 году, есть ребенок, квартира куплена в браке...»
Но через несколько предложений он может произнести ФИО, номер телефона, адрес, диагноз или реквизиты документа.
Поэтому аудио тоже проходит через контур обработки персональных данных.
SpeechKit получает звук, возвращает текст, после чего система может извлечь из него факты и удалить ненужную идентифицирующую информацию перед отправкой текста дальше в LLM.
Снова работает тот же принцип:
каждый следующий компонент получает меньше данных, чем предыдущий.
Аудио
↓
SpeechKit
↓
Полный текст
↓
Извлечение фактов + PII redaction
↓
Минимальный юридический контекст
↓
LLM
Это намного безопаснее архитектуры:
всё, что сказал пользователь
↓
LLM
А что делать с Роскомнадзором?
До начала автоматизированной обработки оператор по общему правилу должен уведомить Роскомнадзор. После изменений 2022 года перечень исключений стал довольно узким, и обычный интернет-сервис вроде конструктора юридических документов, как правило, не стоит проектировать исходя из надежды «мы попадем под исключение».
В уведомлении указываются, в частности, цели обработки, категории данных и субъектов, правовые основания, перечень действий, способы обработки, меры защиты, наличие трансграничной передачи и место нахождения баз данных.
Это полезно сделать не в самом конце.
Если заполнить уведомление еще на этапе проектирования, оно довольно быстро обнаруживает архитектурные противоречия.
Например:
«А где физически находятся наши backup?»
«Яндекс является обработчиком?»
«А Sentry куда отправляет exception?»
«Мы заявили одну цель обработки, почему сохраняем документы после завершения заказа?»
«Зачем маркетинговой системе передается телефон из юридического профиля?»
Получается довольно хороший архитектурный тест.
Еще один обязательный сценарий — что мы делаем в первые 24 часа после утечки
При установлении факта неправомерной или случайной передачи или доступа к персональным данным, повлекшего нарушение прав субъектов, оператор должен уведомить Роскомнадзор в течение 24 часов о самом инциденте и первоначальных обстоятельствах, а в течение 72 часов — сообщить результаты внутреннего расследования.
Поэтому incident response нельзя писать после утечки.
Нужно заранее понимать, кто принимает решение, где находятся audit logs, как быстро отзываются API-ключи, как блокируется скомпрометированный аккаунт администратора и как определить, какие именно документы были доступны злоумышленнику.
После ужесточения ответственности это уже не теоретическая проблема. КоАП предусматривает отдельные крупные штрафы за утечки, включая существенно повышенную ответственность при компрометации специальных и биометрических персональных данных.
И здесь неожиданно снова помогает хорошая архитектура.
Если мы журналируем не содержимое документов, а операции над ними, то расследовать инцидент можно, не создавая еще одну копию утечки внутри логов.
Какие документы нужны кроме кода
В какой-то момент разработчик неизбежно спрашивает:
«Мы все это реализовали. Теперь мы соответствуем
И здесь появляется не самый приятный ответ.
Код — только половина системы.
Закон требует правовые, организационные и технические меры. Статья 18.1 предполагает, в частности, назначение ответственного, принятие локальных документов, определение угроз, оценку возможного вреда и внутренний контроль. Статья 19 добавляет требования непосредственно к безопасности обработки.
Для негосударственной коммерческой ИСПДн обязательный государственный «сертификат на
На практике это означает, что рядом с Laravel repository появляется второй, менее любимый разработчиками repository — организационный.
В нем живут политика обработки персональных данных, модель угроз, перечень ИСПДн и активов, регламент управления доступом, правила резервного копирования, порядок уничтожения данных, incident response, перечень обработчиков и другие документы.
И неприятная правда заключается в том, что документация должна описывать реальную систему.
Если в политике написано «удаляем исходные документы через 24 часа», а cron для удаления сломался полгода назад, документ не помогает.
Во что в итоге превратился наш подход
Мы начинали с мысли:
«Нужно безопасно хранить паспорт».
В итоге поняли, что задача выглядит иначе:
нужно сделать так, чтобы полный паспорт видел как можно меньший кусок системы и как можно меньшее количество времени.
На входе у нас может быть максимально чувствительный документ.
OCR получает его только потому, что иначе не сможет выполнить свою функцию.
После OCR исходник можно удалить, если для дальнейшего оказания услуги он не нужен.
Из распознанного текста извлекаются только необходимые поля.
Генеративная модель получает обезличенную юридическую ситуацию.
Шаблонизатор уже внутри нашего контролируемого контура возвращает в текст настоящие ФИО и реквизиты.
Сотрудники видят только ту часть информации, которая требуется для их работы.
Логи знают идентификаторы операций, но не содержимое паспортов.
Backup живут ограниченное время.
Development работает на синтетике.
И каждый новый сервис мы оцениваем не по вопросу:
«Он российский?»
А по более длинной цепочке:
какие данные мы туда отправляем, зачем мы их отправляем, где они обрабатываются, на каком основании, как долго существуют, кто может к ним получить доступ и можем ли мы вообще туда их не отправлять?
И, пожалуй, это главное, что дал нам
Он заставил перестать думать о персональных данных как о наборе колонок в PostgreSQL.
Персональные данные — это поток.
И защищать нужно весь поток целиком.
Как бы мы проектировали сейчас
Если переводить весь опыт в практический порядок работы, мы бы сначала зафиксировали оператора и цели обработки; затем составили карту всех данных и внешних обработчиков; определили правовые основания для данных клиента, детей и третьих лиц; проверили локализацию VPS, базы, backup и сторонних сервисов; подали или актуализировали уведомление Роскомнадзору; отдельно оформили необходимые согласия; описали ИСПДн и модель угроз; определили требуемый уровень защищенности по постановлению № 1119; после этого реализовали разграничение доступа, приватное файловое хранилище, минимизацию логов, управление секретами, резервное копирование и процедуру удаления; а AI-пайплайн построили бы по принципу исходник → распознавание → извлечение фактов → обезличивание → LLM → подстановка реальных данных.
И только после этого называли бы систему «готовой к работе с персональными данными».
Потому что соответствие
Это свойство всей архитектуры.
Материал описывает инженерный подход к проектированию сервиса и не является индивидуальным юридическим заключением. Для запуска конкретной ИСПДн состав данных, правовые основания, уровень защищенности и набор мер целесообразно отдельно проверить со специалистом по персональным данным и технической защите информации.

Добавляя комментарий вы даёте согласие на обработку персональных данных в соответствии с Политикой и принимаете условия Оферты