Принципы очистки входных данных

При работе с пользовательскими данными ключевая ошибка — смешивание проверки и преобразования. Библиотека validator.js изначально ориентирована на проверку строковых значений, но в реальных приложениях она часто используется вместе с этапом очистки (sanitization), который должен быть строго отделён от логики валидации.

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

Пример различия:

  • isEmail(value) — проверяет корректность email
  • normalizeEmail(value) — приводит email к каноническому виду

Смешивание этих этапов приводит к трудноуловимым ошибкам, особенно в больших системах, где данные проходят несколько слоёв обработки.


Принцип доверия к входу отсутствует

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

  • пользовательские формы
  • API-запросы
  • данные из cookies
  • параметры URL
  • интеграции с внешними сервисами

Очистка входных данных строится на предположении, что значение может содержать:

  • внедрённые HTML/JS конструкции (XSS)
  • SQL-инъекции (на уровне последующего использования)
  • управляющие символы
  • неожиданные типы данных

Поэтому sanitization — это не косметическая операция, а слой безопасности и нормализации.


Нормализация как первый этап очистки

Перед применением любой логики данные приводятся к предсказуемому виду.

Типовые операции нормализации:

Приведение к строке

Многие функции validator.js работают только со строками, поэтому вход часто приводится явно:

const value = String(input);

Удаление лишних пробелов

import validator from 'validator';

const cleaned = validator.trim(value);

Это важно для email, логинов и URL, где пробелы могут быть незаметными, но критичными.

Приведение регистра

const normalized = value.toLowerCase();

Используется для:

  • email-адресов
  • идентификаторов
  • slug-полей

Очистка против преобразования смысла

Очистка не должна менять семантику данных. Например:

  • допустимо: удаление пробелов, экранирование HTML
  • недопустимо: изменение значения, влияющего на смысл (например, округление ID)

Пример опасной практики:

value = parseInt(value);

Если вход "0012abc", результат будет 12, что может исказить данные.


Экранирование и защита от XSS

Один из ключевых сценариев очистки — предотвращение внедрения HTML/JS.

В validator.js есть функция:

validator.escape('<script>alert(1)</script>');

Результат:

&lt;script&gt;alert(1)&lt;/script&gt;

Это критично при выводе данных в HTML-контекст без дополнительного экранирования.

Важно различать:

  • экранирование (escape) — защита при отображении
  • удаление тегов — радикальная очистка
  • валидация HTML — отдельная задача, не покрываемая библиотекой полностью

Белый список как основная стратегия

Очистка данных строится по принципу whitelist, а не blacklist.

Неправильный подход:

Удалить “плохие” символы

Правильный подход:

Разрешить только допустимые символы

Пример:

validator.whitelist(value, 'a-zA-Z0-9_');

Это снижает риск обхода фильтров, так как неизвестные символы просто не допускаются.


Обработка email как пример нормализации

Email — один из самых сложных типов данных с точки зрения очистки.

validator.normalizeEmail(email, {
  gmail_remove_dots: true,
  gmail_remove_subaddress: true,
  all_lowercase: true
});

Что происходит:

  • приведение к нижнему регистру
  • удаление точек (для Gmail-адресов)
  • удаление alias-частей (+tag)

Такой подход позволяет:

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

Порядок операций имеет значение

Типичная ошибка — проверка до очистки или смешивание этапов.

Корректный порядок:

  1. Приведение типа
  2. Базовая нормализация
  3. Очистка (sanitize)
  4. Валидация
  5. Финальная фиксация значения

Пример неправильного порядка:

if (validator.isEmail(value)) {
  value = validator.trim(value);
}

Здесь проверяется необработанное значение, что может привести к ложным результатам.


Обработка объектов и вложенных структур

validator.js работает со строками, поэтому при работе с объектами требуется явная стратегия:

function sanitizeUser(user) {
  return {
    name: validator.escape(validator.trim(user.name || '')),
    email: validator.normalizeEmail(user.email || ''),
    bio: validator.escape(user.bio || '')
  };
}

Особенности:

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

Опасность неявных преобразований типов

JavaScript часто приводит типы автоматически, что создаёт уязвимости.

Примеры:

  • null"null"
  • undefined"undefined"
  • 0"0"

Поэтому перед очисткой важно:

if (value === null || value === undefined) value = '';
value = String(value);

Использование цепочек преобразований

Очистка часто реализуется как цепочка операций:

const result = validator.escape(
  validator.trim(
    String(input)
  )
);

Проблема такого подхода — ухудшение читаемости при росте количества шагов. В таких случаях лучше выделять функции:

function sanitize(input) {
  return validator.escape(validator.trim(String(input)));
}

Ограничения библиотеки при очистке

validator.js не предназначена для полноценной sanitization-логики. Она не покрывает:

  • глубокую очистку JSON
  • удаление опасных HTML-структур
  • контекстно-зависимое экранирование (SQL, shell)
  • нормализацию сложных структур данных

Поэтому очистка на её основе должна дополняться:

  • серверной логикой
  • шаблонизаторами с авто-escaping
  • ORM-слоем при работе с БД

Идемпотентность очистки

Хорошая функция очистки должна быть идемпотентной: повторное применение не должно изменять результат.

Пример:

validator.escape('&lt;div&gt;')

Результат не должен снова экранироваться при повторной обработке.

Нарушение этого принципа приводит к:

  • двойному экранированию
  • повреждению данных при повторной обработке

Согласованность между слоями системы

Очистка входных данных должна быть согласована между:

  • клиентской частью
  • API-слоем
  • бизнес-логикой
  • базой данных

Если хотя бы один слой допускает неочищенные данные, вся цепочка становится уязвимой.


Минимизация побочных эффектов

Функции очистки должны:

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

Пример предпочтительного подхода:

function clean(value) {
  return validator.trim(validator.escape(String(value)));
}

А не:

value.trim();
value = escape(value);

Работа с URL и внешними данными

При обработке ссылок используется:

validator.isURL(value)

Но очистка URL требует осторожности:

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

Часто применяется комбинированный подход: проверка + нормализация + повторная проверка.


Контекстная чувствительность очистки

Один и тот же метод очистки может быть корректен в одном контексте и опасен в другом:

  • HTML-контекст → escape
  • JSON-контекст → stringify
  • SQL-контекст → параметризованные запросы (очистка не заменяет параметры)

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