Логи без персональных данных: 9 ошибок веб-приложения
Логи помогают искать ошибки, но легко превращаются в копию базы пользователей. Разбираем, какие поля нельзя писать в журнал, почему маскирование часто не спасает и как проверить логи перед продакшеном.

Веб-приложение может случайно записать в лог email, токен сброса пароля и полный заголовок запроса уже при обычном ответе 500. Очевидное решение — заменить чувствительные значения звёздочками, но оно часто ломается на вложенных объектах, URL и исключениях. Ниже — практический разбор типичных ошибок для приложений на любом стеке, с акцентом на логи HTTP-запросов, авторизации и ошибок.
Что считать персональными данными в логах
Начинайте не со списка запрещённых ключей, а с маршрута данных. Запись проходит через middleware, обработчик запроса, клиент базы данных, библиотеку HTTP и систему сбора логов. Любой из этих слоёв может добавить значение, которого разработчик не видел в основном сообщении.
К чувствительным полям обычно относятся:
- имя, email, номер телефона, адрес и другие идентификаторы пользователя;
- IP-адрес, идентификатор устройства, cookie и идентификатор сессии;
- пароли, API-ключи, access token, refresh token и коды восстановления;
- параметры URL, тела POST-запросов и заголовки вроде
AuthorizationиCookie; - тексты обращений, адреса доставки, платёжные реквизиты и данные из прикреплённых документов.
Даже внутренний идентификатор нельзя автоматически считать безопасным. Если по нему легко найти конкретного человека в другой системе, для логов это всё равно чувствительное поле.
Персональные данные — это информация, которая прямо или косвенно относится к определённому или определяемому человеку. Косвенный идентификатор не становится безопасным только потому, что выглядит как случайная строка.[1]
Девять ошибок, из-за которых логи раскрывают больше, чем нужно
1. Логируется весь объект запроса
Вызов вроде «записать request целиком» удобен во время отладки. В нём могут оказаться заголовки, cookie, query-параметры и тело запроса. Особенно опасны универсальные логгеры, которые автоматически сериализуют объект исключения вместе с контекстом.
Безопаснее собирать запись из разрешённого набора полей: время, маршрут без секретов, код ответа, длительность, размер ответа и внутренний идентификатор операции. Такой подход называют allowlist: в лог попадает только то, что заранее разрешено.
2. Маскируется значение, но не имя поля
Запись password=[REDACTED] лучше, чем открытый пароль, но она всё равно показывает, что пользователь выполнял чувствительную операцию. В некоторых системах это уже лишняя информация для аналитика или подрядчика, у которого есть доступ к журналу.
Для секретов важно скрывать не только значение, но и сам факт, когда он не нужен для диагностики. В большинстве случаев достаточно события «неуспешная проверка учётных данных», кода ошибки и идентификатора запроса.
3. Секрет прячется в URL
Разработчик может не писать токен вручную, но он попадёт в access log веб-сервера, историю прокси, трассировку или систему аналитики, если передавать его в query-параметре. Маскирование только в приложении не удалит запись, которая уже появилась на предыдущем слое.
Токены и коды нужно передавать способом, который не попадает в URL, а на уровне инфраструктуры отдельно фильтровать query-параметры. Для существующего endpoint проверьте access logs, reverse proxy и APM, а не только файл приложения.
4. Поле маскируется только на верхнем уровне
Фильтр может скрыть email в основном объекте, но пропустить user.profile.email, элемент массива заказов или значение внутри строки JSON. Частая причина — фильтр знает несколько имён ключей, а структура события меняется от маршрута к маршруту.
Проверяйте вложенные объекты, массивы, исключения и сериализованные строки. Если библиотека логирования не гарантирует рекурсивную очистку, не передавайте ей произвольные объекты.
5. Персональные данные попадают в текст ошибки
Сообщение исключения может содержать email из SQL-запроса, фрагмент тела запроса или путь к файлу с именем пользователя. Stack trace полезен разработчику, но его нельзя без фильтра отправлять в общий журнал и тем более показывать клиенту.
Разделяйте внутреннюю диагностику и ответ API. Клиенту возвращайте стабильный код ошибки и идентификатор операции, а подробности храните в защищённом контуре с ограниченным доступом.
6. Хеширование используется без оценки риска
Хеш email или телефона не превращает данные в безобидный набор символов. Для коротких и предсказуемых значений возможен перебор: атакующий заранее вычисляет хеши распространённых адресов и сравнивает их с журналом.
Если нужна связь событий, используйте отдельный стабильный идентификатор пользователя, который не раскрывает исходное значение. Секрет для псевдонимизации должен храниться отдельно от логов, а срок его действия и доступ к нему должны быть понятны команде.
7. Режим отладки включается в продакшене
Подробный debug-лог часто добавляет тела запросов, SQL-параметры и содержимое внешних ответов. Проблема усугубляется, если уровень логирования можно включить через административный endpoint без аудита и ограничения по времени.
В продакшене оставляйте диагностические события включёнными точечно: по идентификатору операции, тестовому пользователю или короткому окну времени. Переключение уровня должно требовать аутентификации, фиксироваться в отдельном аудите и автоматически возвращаться к обычному режиму.
8. Тестовые данные считаются безопасными навсегда
Демо-база часто копируется в staging, систему мониторинга и резервные хранилища. Email вроде test@example.com не доказывает, что остальные поля тоже вымышлены. Настоящие обращения, телефоны или адреса могли попасть в дамп раньше.
Перед передачей данных в тестовую среду удаляйте или заменяйте чувствительные поля на уровне всего набора. Отдельно проверьте логи, дампы, очереди и вложения: очистка только таблиц базы не решает проблему.
9. Логи хранятся дольше, чем нужно для задачи
Даже правильно очищенный журнал расширяет поверхность риска, если копии лежат в нескольких системах без срока удаления. Хранение должно зависеть от задачи: поиск аварии, расследование инцидента и продуктовая аналитика требуют разного периода.
Задайте срок для каждого типа логов, автоматическое удаление и контроль архивов. Нельзя считать данные удалёнными, пока они остаются в резервных копиях, индексе поисковой системы или экспортированном файле.
Лучший лог — не тот, где можно найти всё. Это запись, по которой можно восстановить ход операции, не получив копию пользовательской базы.
Три подхода к очистке логов
Универсального фильтра нет. Выбор зависит от того, контролируете ли вы формат событий, сколько библиотек пишут в журнал и нужно ли искать пользователя по событиям.
| Подход | Что даёт | Цена и риск | Когда выбирать |
|---|---|---|---|
| Allowlist полей | В событие попадает только заранее описанный набор | Нужно обновлять схему при добавлении новых сценариев | Для критичных событий, авторизации и платежных операций |
| Blocklist ключей | Быстро скрывает известные поля вроде password и token | Новое или вложенное поле легко пропустить | Как дополнительный слой, но не как единственную защиту |
| Очистка на сборщике логов | Закрывает сразу несколько приложений и источников | Секрет уже мог попасть на диск, в очередь или транспорт | Для централизованного контроля после фильтрации в приложении |
Практический вариант — сочетать allowlist в приложении с фильтрацией на границе сбора. Первый слой не создаёт лишние данные, второй ловит ошибку в новом сервисе или инфраструктурном компоненте.
Как проверять решение до релиза
- Составьте карту источников. Перечислите application log, access log, reverse proxy, APM, очереди, систему ошибок, дампы и резервные копии. Проверка готова, когда для каждого источника указан владелец и срок хранения.
- Опишите схему событий. Для каждого события укажите разрешённые поля, тип значения и причину хранения. Если команда не может объяснить, зачем нужен атрибут, его не следует добавлять по умолчанию.
- Проверьте негативные сценарии. Отправьте запросы с тестовыми значениями в заголовках, URL, теле, cookie и вложенных массивах. В журнале не должно остаться исходных строк, а не только верхнеуровневого поля.
- Проверьте ошибки и трассировки. Создайте исключение на авторизации, внешнем HTTP-вызове и обработке файла. Убедитесь, что stack trace не содержит токенов, тел запросов и персональных значений.
- Проверьте доступ. Убедитесь, что разработчик, поддержка и подрядчик видят только нужный набор журналов. Результат — понятная матрица доступа и запись о каждом административном действии.
Что ломается в реальном проекте
«Мы замаскировали token, но секрет всё равно нашли»
Сначала ищите копии в access log, трассировке, сообщении исключения и логах внешнего клиента. Затем проверьте, не записывается ли токен как часть URL или сериализованного заголовка. После исправления старые записи нужно удалить из доступных хранилищ по принятой процедуре, иначе фильтр защищает только будущие события.
«Нам нужен поиск по email»
Поиск по открытому email удобен, но создаёт лишнюю копию идентификатора. Рассмотрите стабильный внутренний ID или заранее согласованный псевдоним, а сопоставление с пользователем оставьте в отдельной системе с более строгим доступом.
«После фильтра пропали полезные данные»
Это сигнал, что в журнал отправлялся необработанный объект. Добавьте отдельные безопасные поля: тип операции, код результата, длительность, размер, версию сервиса и корреляционный идентификатор. Не возвращайте для удобства весь исходный объект.
«Фильтр работает в одном сервисе, но не в другом»
Одинаковое имя поля не означает одинаковую структуру события. Зафиксируйте общий контракт логов и проведите тесты для каждого runtime и логирующей библиотеки. Если версии библиотек отличаются, проверяйте поведение на каждой поддерживаемой версии, а не только в локальной среде.
Что делать дальше
Начните с инвентаризации: соберите несколько реальных событий из каждого источника и найдите в них email, IP, cookie, токены и тексты исключений. Затем переведите критичные события на allowlist, добавьте автоматический тест с маркерами вроде TEST_EMAIL и YOUR_API_KEY, а после релиза наблюдайте не только за ошибками, но и за попытками записать запрещённые поля. Отдельно измеряйте долю событий с неизвестной схемой: её рост обычно показывает, что новый код обходит договорённости о логировании.
[1] Псевдонимизация — замена прямого идентификатора другим значением, при этом связь с исходным объектом технически сохраняется. Это не то же самое, что необратимое обезличивание.
Персональный стилист: Фасоны по фото, капсульный гардероб, сочетания цветов.
Спросить за 12 ₽Источники: OWASP Logging Cheat Sheet, NIST SP 800-92, Guide to Computer Security Log Management
Частые вопросы
Можно ли полностью гарантировать отсутствие персональных данных в логах?
Достаточно ли заменить email и токены на [REDACTED]?
Что делать, если токен уже попал в продакшен-логи?
Можно ли хешировать email, чтобы искать пользователя в логах?
Что делать, если в новой версии логирующей библиотеки фильтр работает иначе?
Как не потерять данные, нужные для расследования инцидента?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.