Безопасная работа с пользовательским контентом

Пользовательский контент (User Generated Content, UGC) — это любые данные, вводимые пользователем: текст, HTML-разметка, ссылки, изображения, JSON-объекты. При интеграции динамических интерфейсов с использованием Headroom.js такие данные могут взаимодействовать с DOM, что создаёт дополнительные векторы атак.

Наиболее распространённые угрозы:

  • XSS (Cross-Site Scripting) — внедрение вредоносного JavaScript-кода
  • DOM-based XSS — выполнение кода через манипуляции с DOM
  • HTML-инъекции — внедрение нежелательной разметки
  • CSS-инъекции — изменение внешнего вида и поведения интерфейса
  • Open Redirect — подмена ссылок

Headroom.js работает с DOM-элементами (обычно header), изменяя их классы в зависимости от прокрутки. Если пользовательский контент влияет на эти элементы или их окружение, безопасность становится критически важной.


Контроль источников данных

Безопасность начинается с понимания источников:

  • Формы ввода
  • API-запросы
  • URL-параметры
  • LocalStorage / SessionStorage
  • Внешние виджеты

Любой из этих источников должен считаться недоверенным.

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


Экранирование и очистка данных

Экранирование (escaping)

Преобразование специальных символов в безопасные:

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

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

element.innerHTML = escapeHTML(userInput);

Очистка (sanitization)

Удаление опасных конструкций, а не просто их экранирование.

Популярные библиотеки:

  • DOMPurify
  • sanitize-html

Пример:

const clean = DOMPurify.sanitize(userHTML);
element.innerHTML = clean;

Опасности innerHTML при работе с Headroom.js

Headroom.js часто используется вместе с динамически изменяемыми шапками сайта. Если в header вставляется пользовательский контент через innerHTML, это создаёт прямую уязвимость.

Опасный пример:

header.innerHTML = userInput;

Если userInput содержит:

<script>alert('XSS')</script>

код выполнится.

Безопасная альтернатива:

header.textContent = userInput;

или:

header.innerHTML = DOMPurify.sanitize(userInput);

Работа с атрибутами и dataset

Headroom.js добавляет и удаляет классы (headroom--pinned, headroom--unpinned). Иногда разработчики используют data-* атрибуты, основанные на пользовательских данных.

Опасность:

element.setAttribute("data-user", userInput);

Если затем значение используется в HTML без проверки:

element.innerHTML = element.dataset.user;

возникает XSS.

Решение:

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

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

Headroom.js реагирует на scroll-события. При добавлении пользовательского поведения:

window.addEventListener("scroll", function() {
  eval(userCode);
});

использование eval — критическая ошибка.

Запрещённые конструкции:

  • eval()
  • new Function()
  • setTimeout(string)
  • setInterval(string)

Безопасная альтернатива:

использование заранее определённых функций:

const actions = {
  hide: () => header.classList.add("hidden"),
  show: () => header.classList.remove("hidden")
};

if (actions[userAction]) {
  actions[userAction]();
}

Ограничение влияния пользовательского контента на Headroom

Headroom.js управляет классами:

  • headroom--top
  • headroom--not-top
  • headroom--pinned
  • headroom--unpinned

Если пользователь может влиять на эти классы, можно нарушить логику интерфейса.

Риск:

header.className = userInput;

Пользователь может удалить служебные классы.

Решение:

использовать безопасное добавление:

header.classList.add("custom-class");

или фильтрацию:

const allowedClasses = ["theme-dark", "theme-light"];

if (allowedClasses.includes(userClass)) {
  header.classList.add(userClass);
}

Валидация пользовательских данных

Типизация

if (typeof userInput !== "string") return;

Ограничение длины

if (userInput.length > 200) return;

Проверка формата

const regex = /^[a-zA-Z0-9\s]+$/;
if (!regex.test(userInput)) return;

Работа с URL и ссылками

Если пользователь задаёт ссылки, используемые в header:

link.href = userURL;

Риск:

jav * ascript:alert(1)

Решение:

function isSafeURL(url) {
  try {
    const parsed = new URL(url);
    return ["http:", "https:"].includes(parsed.protocol);
  } catch {
    return false;
  }
}

if (isSafeURL(userURL)) {
  link.href = userURL;
}

Использование Content Security Policy (CSP)

CSP ограничивает выполнение вредоносного кода.

Пример заголовка:

Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none';

Эффект:

  • запрещает inline-скрипты
  • блокирует сторонние источники

Это особенно важно при работе с динамическими DOM-изменениями, связанными с Headroom.js.


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

iframe sandbox

<iframe sandbox="allow-same-origin"></iframe>

Используется, если требуется отображение пользовательского HTML.

Shadow DOM

Изолирует стили и структуру:

const shadow = element.attachShadow({ mode: "open" });
shadow.innerHTML = sanitizedContent;

Работа с CSS и пользовательскими стилями

Headroom.js активно использует CSS-классы. Пользовательские стили могут конфликтовать или ломать анимации.

Опасность:

style.innerHTML = userCSS;

Решение:

  • не позволять произвольный CSS
  • использовать whitelist свойств:
const allowedStyles = ["color", "background-color"];

Защита от DOM Clobbering

Пользователь может создать элементы с именами, конфликтующими с переменными:

<form name="header"></form>
console.log(header); // теперь это форма, а не элемент

Решение:

  • использовать getElementById
  • избегать глобальных переменных

Безопасная интеграция с Headroom.js

Пример корректной и безопасной инициализации:

const header = document.querySelector("header");

const headroom = new Headroom(header, {
  offset: 100,
  tolerance: 5,
  classes: {
    initial: "headroom",
    pinned: "headroom--pinned",
    unpinned: "headroom--unpinned"
  }
});

headroom.init();

При добавлении пользовательского контента:

const safeContent = DOMPurify.sanitize(userInput);
header.querySelector(".title").textContent = safeContent;

Логирование и мониторинг

Для выявления атак:

window.addEventListener("error", function(e) {
  console.log("Error detected:", e.message);
});

Анализ:

  • неожиданные изменения DOM
  • появление <script>
  • подозрительные URL

Ограничение прав доступа

  • минимизация доступа к DOM
  • изоляция модулей
  • отказ от глобальных переменных

Использование современных API

textContent вместо innerHTML

element.textContent = userInput;

createElement вместо строк HTML

const div = document.createElement("div");
div.textContent = userInput;

Проверка библиотек и зависимостей

Headroom.js должен использоваться из проверенного источника:

  • официальные CDN
  • npm-пакеты

Регулярное обновление:

npm update headroom.js

Типичные ошибки

  • вставка пользовательского HTML без очистки
  • использование eval
  • доверие URL-параметрам
  • смешивание логики Headroom с пользовательскими данными
  • отсутствие CSP

Архитектурные рекомендации

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

Пример защищённого подхода

function safeInsert(element, userInput) {
  const clean = DOMPurify.sanitize(userInput, { ALLOWED_TAGS: [] });
  element.textContent = clean;
}

const title = document.querySelector(".header-title");
safeInsert(title, userInput);

Влияние безопасности на UX

Небезопасный код может:

  • ломать анимации Headroom
  • вызывать перерисовки
  • снижать производительность
  • создавать визуальные артефакты

Безопасная обработка данных обеспечивает:

  • стабильность интерфейса
  • предсказуемое поведение header
  • корректную работу scroll-логики

Тестирование безопасности

Методы:

  • ручное тестирование XSS
  • использование payload’ов
  • автоматические сканеры (OWASP ZAP)

Пример теста:

<img src=x oner ror=alert(1)>

Если выполняется — система уязвима.


Интеграция с серверной защитой

Клиентская защита должна дополняться серверной:

  • фильтрация входящих данных
  • экранирование на сервере
  • использование HTTP-only cookies
  • защита от CSRF

Подход “zero trust”

Любой пользовательский ввод считается вредоносным до доказательства обратного. Это особенно важно при работе с библиотеками, напрямую взаимодействующими с DOM, такими как Headroom.js.