Защита от XSS через даты

Даты сами по себе редко воспринимаются как источник угрозы, однако в веб-приложениях они часто становятся частью динамического HTML, формируемого на стороне клиента. Именно на этом этапе возникает класс уязвимостей XSS (Cross-Site Scripting), когда результат форматирования даты оказывается внедрённым в DOM без должной изоляции.

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


Как даты становятся вектором атаки

В JavaScript дата часто приходит из внешних источников:

  • API (REST/GraphQL)
  • пользовательский ввод
  • URL-параметры
  • cookies и localStorage

Типичный сценарий опасности возникает, когда дата не является строго валидированным ISO-значением, а представляет собой строку, которую затем форматируют через Day.js и вставляют в HTML.

Опасность усиливается, если исходная строка содержит неожиданные символы или HTML-подобные конструкции, например:

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

Если такая строка проходит без фильтрации и затем отображается через DOM-интерполяцию, она становится полноценным XSS-вектором.


Опасные паттерны форматирования

Наиболее распространённый источник проблем — использование результата форматирования даты напрямую в innerHTML.

const formatted = dayjs(userInput).format('DD.MM.YYYY');
element.innerHTML = formatted;

Сам Day.js не выполняет интерпретацию HTML и не генерирует скрипты, однако уязвимость появляется из-за того, что результат помещается в HTML-контекст без экранирования.

Дополнительный риск возникает при конкатенации строк:

element.innerHTML = "Дата: " + dayjs(userInput).format('YYYY-MM-DD');

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


Day.js и безопасная модель работы с датами

Day.js оперирует строго строками и объектами Date, не создавая DOM-структур и не интерпретируя HTML. Это делает библиотеку безопасной на уровне вычислений, но не на уровне отображения.

Ключевой принцип:

Day.js не защищает от XSS — он только формирует данные для отображения.

Безопасность определяется тем, как результат используется в UI.


Разделение парсинга и отображения

Корректная архитектура работы с датами предполагает строгое разделение этапов:

  1. Парсинг входной строки
  2. Валидация результата
  3. Форматирование
  4. Отображение через безопасный механизм рендеринга

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

const parsed = dayjs(userInput, 'YYYY-MM-DD', true);

if (parsed.isValid()) {
  const output = parsed.format('DD.MM.YYYY');
  element.textContent = output;
}

Ключевой момент — использование textContent вместо innerHTML. Это исключает интерпретацию любых символов как HTML.


Строгий парсинг как защита от инъекций

Day.js поддерживает строгий режим парсинга при использовании плагина customParseFormat.

import customParseFormat from 'dayjs/plugin/customParseFormat';
dayjs.extend(customParseFormat);

const parsed = dayjs(input, 'YYYY-MM-DD', true);

Строгий режим исключает невалидные строки, которые могут содержать посторонние символы или неожиданные конструкции.

Важно учитывать, что слабый парсинг может интерпретировать мусорные строки как валидные даты, что увеличивает поверхность атаки.


Роль формата вывода в XSS-защите

Форматирование через Day.js не является опасным само по себе:

dayjs(date).format('DD.MM.YYYY')

Однако риск возникает при:

  • вставке результата в HTML без экранирования
  • использовании пользовательских шаблонов форматирования
  • смешивании данных и HTML-разметки

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

element.innerHTML = `<span>${dayjs(date).format('DD.MM.YYYY')}</span>`;

Хотя здесь нет прямого XSS, сама практика использования innerHTML создаёт потенциальную точку расширения уязвимости при изменении входных данных.


Локализация и инъекции через форматы

Day.js поддерживает локали, что влияет на текстовые представления дат:

dayjs.locale('ru');
dayjs().format('dddd, D MMMM');

Локализация может казаться безопасной, но становится рискованной при:

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

Особенно опасны случаи, когда формат строки приходит с сервера:

const format = userConfig.dateFormat;
dayjs(date).format(format);

Если формат не ограничен whitelist-ом, возможны неожиданные сценарии отображения, включая вставку небезопасных символов в DOM-контекст.


Безопасные механизмы отображения

При работе с результатами Day.js безопасные практики сводятся к контролю DOM-контекста.

Рекомендуемые подходы:

Использование textContent

element.textContent = dayjs(date).format('YYYY-MM-DD');

Использование React/Vue интерполяции

Фреймворки автоматически экранируют строки:

<span>{dayjs(date).format('DD.MM.YYYY')}</span>

Избегание HTML-интерполяции

// небезопасно при любых внешних данных
element.innerHTML = formattedDate;

Даты из API как основной источник риска

Наиболее частый источник XSS через даты — неконтролируемые API-ответы. Даже если API формально возвращает дату, фактически это может быть строка произвольного содержания.

Пример уязвимого сценария:

fetch('/api/data')
  .then(r => r.json())
  .then(data => {
    const date = dayjs(data.date).format('DD.MM.YYYY');
    element.innerHTML = date;
  });

Если data.date подменён, результат форматирования становится частью HTML-инъекции.

Корректная версия:

const parsed = dayjs(data.date, dayjs.ISO_8601, true);

if (parsed.isValid()) {
  element.textContent = parsed.format('DD.MM.YYYY');
}

Нормализация входных данных

Безопасная обработка дат требует жёсткой нормализации:

  • принятие только ISO 8601
  • отказ от свободного парсинга
  • явная проверка валидности
  • запрет неизвестных форматов

ISO-строки минимизируют вероятность скрытых инъекций:

dayjs('2026-05-23T10:00:00Z')

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


Контекст отображения как ключевой фактор

XSS через даты не является свойством Day.js. Он возникает исключительно из-за контекста использования результата:

  • HTML-контекст (innerHTML)
  • атрибуты (setAttribute)
  • шаблоны строк
  • серверный рендеринг без экранирования

Даже строго отформатированная дата становится уязвимой, если попадает в неподходящий контекст.

Пример опасного использования в атрибуте:

element.setAttribute('data-date', dayjs(date).format());

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


Изоляция данных как основная стратегия защиты

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

  • дата рассматривается как данные, а не как HTML
  • форматирование не должно влиять на структуру DOM
  • результат всегда трактуется как plain text

Day.js в этой модели выполняет роль чистого преобразователя данных, а защита реализуется на уровне UI-слоя и политики вставки в DOM.