Санитизация пользовательского ввода

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

Основная опасность связана с тем, что переводимые строки нередко содержат HTML-контекст или используются в UI-фреймворках, которые могут интерпретировать строку как разметку. При отсутствии санитизации возможны следующие проблемы:

  • внедрение XSS через интерполяцию значений;
  • подмена структуры интерфейса через HTML-вставки;
  • утечка данных через небезопасные шаблоны;
  • неконтролируемая интерпретация пользовательских параметров в атрибутах DOM.

Особенно уязвимы случаи, когда перевод хранится на сервере или загружается из внешнего backend-источника и не проходит предварительную очистку.

Механизм интерполяции и базовая защита

i18next использует систему интерполяции вида:

i18next.t('welcome_user', { name: userInput })

Внутри строки перевода:

{
  "welcome_user": "Добро пожаловать, {{name}}"
}

Ключевой момент — интерполяция по умолчанию экранирует значения. Это поведение регулируется параметром:

interpolation: {
  escapeValue: true
}

При escapeValue: true специальные символы HTML преобразуются в безопасные сущности, предотвращая вставку исполняемого HTML.

Пример трансформации:

  • вход: <script>alert(1)</script>
  • выход: &lt;script&gt;alert(1)&lt;/script&gt;

Таким образом, даже если пользовательский ввод содержит HTML, он отображается как текст.

Отключение экранирования и связанные риски

Отключение защиты:

i18next.init({
  interpolation: {
    escapeValue: false
  }
});

Такой режим используется редко и только в окружениях, где санитизация выполняется на другом уровне. При этом интерполяция начинает подставлять значения «как есть», что делает систему уязвимой к XSS при наличии HTML-контекста.

Особенно опасно сочетание:

{
  "greeting": "Hello <strong>{{name}}</strong>"
}

и пользовательского ввода, содержащего HTML-теги.

Разделение контекстов: текст vs HTML

Безопасная архитектура предполагает строгую сегрегацию:

  • текстовые переводы → всегда экранируются
  • HTML-шаблоны → не содержат пользовательских данных напрямую
  • динамические значения → проходят санитизацию до передачи в t()

Пример безопасного подхода:

const safeName = sanitize(userInput);

t('greeting', { name: safeName });

Где sanitize выполняет удаление или нейтрализацию опасных конструкций.

HTML-интерполяция и риски dangerouslySetInnerHTML

При использовании React или аналогичных библиотек часто встречается паттерн:

<div dangerouslySetInnerHTML={{ __html: t('html_string') }} />

В таком случае i18next перестаёт быть точкой защиты, потому что строка интерпретируется как HTML.

Безопасная модель требует:

  • либо полного запрета HTML в переводах;
  • либо строгой серверной санитизации;
  • либо использования безопасных компонентов разметки.

Даже при включённом escapeValue опасность сохраняется, если перевод содержит заранее подготовленный HTML.

Пользовательский escape-функционал

i18next позволяет переопределять логику экранирования:

i18next.init({
  interpolation: {
    escape: (value) => {
      return customEscape(value);
    }
  }
});

Такой подход используется для:

  • унификации правил санитизации;
  • интеграции с корпоративными security-политиками;
  • применения специализированных HTML-энкодеров.

Типичная реализация:

function customEscape(str) {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#039;');
}

Санитизация на уровне ICU и сложных форматов

При использовании ICU-подобных структур (pluralization, context rules) данные также могут включать пользовательские значения:

{
  "items": "У вас {count, number} сообщений"
}

Хотя числовые значения менее опасны, форматные поля всё равно требуют контроля:

  • предотвращение NaN и Infinity;
  • ограничение диапазонов;
  • нормализация входных данных до передачи в интерполяцию.

Backend-загрузки переводов и внешние источники

При использовании backend-плагинов i18next переводы могут загружаться динамически:

  • HTTP API
  • CMS
  • JSON-файлы из сторонних систем

Основной риск возникает, если атакующий получает возможность модифицировать перевод:

{
  "welcome": "Hello <img src=x oner ror=alert(1)>"
}

Такие строки обходят стандартную интерполяцию, потому что опасный код уже находится в самом переводе.

Следовательно, санитизация должна применяться:

  • на этапе загрузки переводов;
  • на уровне CI/CD пайплайна;
  • при административном вводе контента.

Post-processing и цепочки трансформаций

i18next поддерживает postProcess плагины:

i18next.t('key', {
  postProcess: 'myProcessor'
});

Эта стадия может модифицировать результат перевода после интерполяции, что создаёт дополнительную точку риска:

  • двойное декодирование;
  • повторная интерпретация HTML;
  • обход экранирования.

Без строгого контроля postProcess способен полностью нивелировать встроенную защиту интерполяции.

Безопасная модель использования переменных

Наиболее устойчивый подход строится вокруг нескольких принципов:

  • все пользовательские значения рассматриваются как небезопасные;
  • HTML не допускается в переменных интерполяции;
  • экранирование всегда включено по умолчанию;
  • перевод не должен зависеть от структуры пользовательского HTML;
  • сложные визуальные элементы выносятся в компоненты, а не в строки перевода.

Пример разделения:

// небезопасно
t('text', { html: userHtml });

// безопасно
t('text', { name: sanitizedName });

Контекстные особенности фреймворков

В React-экосистеме i18next часто используется через обёртки. Несмотря на автоматическое экранирование React, оно не заменяет защиту i18next:

  • React экранирует JSX;
  • i18next экранирует интерполяцию;
  • комбинация снижает риск, но не устраняет его при использовании HTML.

Особенно уязвимы гибридные схемы, где перевод вставляется в raw HTML.

Типичные ошибки интеграции

  • отключение escapeValue без альтернативной защиты;
  • хранение HTML внутри переводов;
  • использование пользовательского ввода в ключах перевода;
  • применение dangerouslySetInnerHTML без санитизации;
  • доверие backend-источникам переводов без проверки.

Каждая из этих ошибок превращает i18next из слоя локализации в потенциальную точку внедрения уязвимостей.

Архитектурные практики безопасной локализации

Стабильная модель санитизации в системах с i18next опирается на разделение обязанностей:

  • локализация отвечает только за текст;
  • санитизация отвечает за данные;
  • UI-слой отвечает за представление;
  • backend не должен поставлять исполняемую разметку.

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