Логирование без утечки секретов

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

Основная задача — обеспечить максимальную информативность логов при полном исключении утечек секретов.

Типы чувствительных данных

К категории данных, которые не должны попадать в логи:

  • учетные данные (пароли, PIN-коды)
  • токены авторизации (JWT, API-ключи)
  • cookie и session ID
  • персональные данные (email, телефон, адрес)
  • криптографические ключи
  • содержимое заголовков Authorization

Даже частичное логирование таких данных может привести к компрометации системы.

Подходы к защите логов

1. Маскирование (redaction)

Замена чувствительных данных на безопасные значения:

const mask = (value) => '***';

Пример:

{
  email: "user@example.com",
  password: "***"
}

2. Хэширование

Используется для сохранения возможности идентификации без раскрытия данных:

const crypto = require('crypto');

const hash = (value) =>
  crypto.createHash('sha256').update(value).digest('hex');

Применяется, например, для email или user ID.

3. Полное исключение

Самый безопасный вариант — не логировать данные вовсе:

delete payload.password;

Логирование в Iron: архитектурные особенности

Библиотека Iron предоставляет гибкую систему логирования с возможностью внедрения middleware и трансформации данных перед записью.

Ключевая идея: централизованная обработка логируемых данных перед их выводом.

Базовый пример логирования

import { createLogger } from 'iron';

const logger = createLogger({
  level: 'info'
});

logger.info('User login', {
  email: 'user@example.com',
  password: 'secret123'
});

Без дополнительной обработки пароль попадет в лог.

Middleware для очистки данных

Iron поддерживает цепочку middleware, через которую проходят все лог-сообщения.

Пример фильтрации

const redactMiddleware = (log) => {
  if (log.meta?.password) {
    log.meta.password = '***';
  }
  return log;
};

const logger = createLogger({
  middleware: [redactMiddleware]
});

Теперь любые поля password автоматически маскируются.

Универсальный механизм редактирования

Реальные структуры данных сложны, поэтому требуется рекурсивная обработка.

const SENSITIVE_KEYS = ['password', 'token', 'authorization', 'apiKey'];

const redact = (obj) => {
  if (Array.isArray(obj)) {
    return obj.map(redact);
  }

  if (obj && typeof obj === 'object') {
    return Object.keys(obj).reduce((acc, key) => {
      if (SENSITIVE_KEYS.includes(key.toLowerCase())) {
        acc[key] = '***';
      } else {
        acc[key] = redact(obj[key]);
      }
      return acc;
    }, {});
  }

  return obj;
};

Интеграция:

const logger = createLogger({
  middleware: [
    (log) => {
      log.meta = redact(log.meta);
      return log;
    }
  ]
});

Обработка HTTP-запросов

Наиболее частый источник утечек — логирование HTTP-запросов.

Опасный пример

logger.info('Request', {
  headers: req.headers,
  body: req.body
});

Безопасный вариант

const safeHeaders = { ...req.headers };
delete safeHeaders.authorization;

const safeBody = redact(req.body);

logger.info('Request', {
  headers: safeHeaders,
  body: safeBody
});

Стратегия белых списков

Вместо удаления опасных полей можно явно разрешать только безопасные:

const pickSafeFields = (obj, allowed) =>
  Object.fromEntries(
    Object.entries(obj).filter(([key]) => allowed.includes(key))
  );

Пример:

const safeUser = pickSafeFields(user, ['id', 'email']);

Этот подход снижает риск пропустить новое чувствительное поле.

Логирование ошибок

Ошибки часто содержат стек вызовов и контекст, включающий секреты.

try {
  // ...
} catch (err) {
  logger.error('Error occurred', {
    message: err.message,
    stack: err.stack
  });
}

Рекомендуется:

  • очищать контекст ошибки
  • не логировать raw input пользователя
  • избегать включения request body целиком

Форматирование и структурирование

Iron поддерживает структурированные логи:

logger.info({
  event: 'user_login',
  userId: 123
});

Преимущества:

  • удобство фильтрации
  • интеграция с системами анализа
  • контроль над полями

Разделение уровней логирования

Чувствительность данных может зависеть от уровня:

  • debug — максимум информации (в dev-среде)
  • info — безопасные события
  • error — минимально необходимый контекст
if (process.env.NODE_ENV === 'production') {
  logger.setLevel('info');
}

Хранение и доступ к логам

Даже при очистке данных важно:

  • ограничивать доступ к логам
  • использовать шифрование при хранении
  • настраивать ротацию логов
  • удалять старые записи

Интеграция с внешними системами

При отправке логов в сторонние сервисы (например, ELK, Datadog):

  • убедиться в наличии фильтрации на стороне приложения
  • не полагаться только на внешние инструменты
  • проверять, какие поля индексируются

Тестирование безопасности логирования

Регулярная проверка:

test('password is not logged', () => {
  const log = captureLog(() =>
    logger.info('test', { password: '123456' })
  );

  expect(log).not.toContain('123456');
});

Автоматизация предотвращает регрессии.

Типичные ошибки

  • логирование всего объекта запроса без фильтрации
  • сохранение JWT целиком
  • включение секретов в сообщения об ошибках
  • отсутствие централизованной обработки логов
  • полагание на разработчиков вместо автоматических механизмов

Практика: централизованный безопасный логгер

const createSafeLogger = () => {
  return createLogger({
    middleware: [
      (log) => {
        log.meta = redact(log.meta);
        return log;
      }
    ]
  });
};

export const logger = createSafeLogger();

Единая точка входа гарантирует соблюдение правил по всей системе.

Аудит и соответствие требованиям

Во многих стандартах (GDPR, ISO 27001) логирование регулируется:

  • запрещено хранение персональных данных без необходимости
  • требуется контроль доступа
  • обязательна трассируемость событий без раскрытия секретов

Баланс между безопасностью и диагностикой

Полное удаление данных снижает полезность логов. Компромисс достигается через:

  • частичное маскирование (user@example.comu***@example.com)
  • хэширование
  • логирование идентификаторов вместо содержимого

Грамотно настроенная система логирования позволяет эффективно диагностировать проблемы без риска утечки конфиденциальной информации.