Локализация в JavaScript через Intl API опирается на стандарты BCP 47 и данные CLDR, где ключевым параметром выступает строка локали. Эта строка влияет на формат чисел, дат, валют, единиц измерения, сортировку и отображение языковых вариантов. Несмотря на кажущуюся безобидность, локаль становится точкой входа для класса уязвимостей, связанных с подменой форматирования и косвенными инъекциями через интернационализацию.
Практически все основные конструкторы Intl принимают строку или массив строк локалей:
Intl.NumberFormatIntl.DateTimeFormatIntl.CollatorIntl.RelativeTimeFormatIntl.ListFormatIntl.DisplayNamesКаждый из них допускает передачу пользовательского значения:
new Intl.DateTimeFormat(userLocale)
new Intl.NumberFormat(userLocale)
Если значение локали формируется на основе внешнего ввода (параметры URL, заголовки HTTP, профиль пользователя, данные из БД), появляется возможность вмешательства в поведение форматирования.
BCP 47 определяет строгий синтаксис языковых тегов:
en, ru, deen-US, ru-KZzh-Hans, sr-Cyrlsl-rozaj,
en-US-u-ca-gregoryОднако реализация в JavaScript допускает более гибкое поведение:
Эта гибкость создаёт возможность «смещения смысла» локали, когда входная строка не вызывает ошибку, но приводит к неожиданному результату форматирования.
Наиболее значимая зона риска — Unicode extension sequences
(-u-), которые позволяют изменять параметры
форматирования:
en-US-u-nu-latn-ca-buddhist
Расширения могут влиять на:
ca)nu)co)hc)При неконтролируемом вводе локали возможны сценарии, где злоумышленно сформированная строка изменяет отображение данных без изменения бизнес-логики.
Пример:
new Intl.NumberFormat("ru-RU-u-nu-roman").format(2024)
Результат перестаёт быть привычным арабским числом и становится римским представлением, что может искажать интерфейсные значения.
Региональный код влияет не только на формат, но и на культурные правила:
en-US vs en-GB (валюта, порядок дат)fr-FR vs fr-CA (разделители, орфография в
некоторых форматах)de-DE vs de-ATПри использовании локали из пользовательского профиля без валидации возможно:
new Intl.NumberFormat("de-DE", { style: "currency", currency: "EUR" })
и
new Intl.NumberFormat("en-US", { style: "currency", currency: "EUR" })
дают различное визуальное представление одной и той же суммы.
Если локаль подставляется динамически, это превращается в канал управления визуальной интерпретацией финансовых данных.
Intl API не всегда жёстко отклоняет некорректные строки. Многие значения проходят через механизм best-fit fallback:
en_US → интерпретируется как en-USru-KZ-variant-x-private → частично игнорируетсяx-private → пользовательские расширенияЭта особенность приводит к тому, что:
Пример:
new Intl.DateTimeFormat("en-US-x-internal-hack")
Результат остаётся валидным форматтером, но исходная строка теряет предсказуемость.
Все конструкторы Intl принимают массив локалей:
new Intl.NumberFormat(["xx-INVALID", "ru-RU", "en"])
Алгоритм выбора:
Если входной массив формируется извне, появляется возможность:
Особенно опасны случаи, где первая локаль контролируется пользователем, а последующие добавляются системой.
Локаль влияет на результат форматирования, который затем может попадать в другие системы:
При этом возникает цепочка:
Если форматированный текст воспринимается как структурированный (например, CSV или шаблон), возможны вторичные инъекции.
Пример сценария:
1 000,00 (de-DE)1,000.00 (en-US)При парсинге без учёта локали возможна некорректная интерпретация числа.
Некоторые локали используют разные формы Unicode-нормализации и символы:
new Intl.NumberFormat("fr-FR").format(1000000)
может вернуть:
1 000 000
где разделитель — U+202F (narrow no-break space)
При копировании или сравнении строк это приводит к:
Intl использует механизм fallback:
Пример:
zh-Hans-CN-x-private
→ zh-Hans-CN
→ zh-Hans
→ zh
→ default
Если система полагается на точность локали для принятия решений (например, выбор формата валюты или даты), fallback может привести к смене правил отображения без явного сигнала.
Intl.Collator особенно чувствителен к локали:
new Intl.Collator("de").compare("ä", "z")
и
new Intl.Collator("sv").compare("ä", "z")
дают разные результаты порядка сортировки.
Если локаль задаётся извне, возможны сценарии:
Особенно критично при использовании sensitivity,
numeric, caseFirst.
Intl.DateTimeFormat использует локальные правила:
new Intl.DateTimeFormat("en-US")
new Intl.DateTimeFormat("en-GB")
одна и та же дата превращается в разные строки:
Если результат используется в логике парсинга или сравнения, возникает риск перепутанных дат и некорректных временных интервалов.
Безопасная работа с Intl строится вокруг принципов контроля входных данных:
Использование allowlist:
Проверка через:
Intl.getCanonicalLocales(locale)
позволяет привести строку к каноническому виду и выявить некорректные значения.
Это предотвращает влияние локали на вычисления, где важна однозначность.
Intl должен использоваться только для представления данных, но не для принятия решений:
Node.js и браузеры используют разные версии ICU (International Components for Unicode). Это приводит к:
Если локаль не фиксирована, результат Intl может отличаться между средами, создавая эффект «логической инъекции через окружение».
Формально Intl не выполняет код и не интерпретирует шаблоны. Однако локаль влияет на представление данных, которое может:
Таким образом, локаль становится параметром управления не только отображением, но и структурной интерпретацией данных в цепочке обработки.