Пользовательский контент (User Generated Content, UGC) — это любые данные, вводимые пользователем: текст, HTML-разметка, ссылки, изображения, JSON-объекты. При интеграции динамических интерфейсов с использованием Headroom.js такие данные могут взаимодействовать с DOM, что создаёт дополнительные векторы атак.
Наиболее распространённые угрозы:
Headroom.js работает с DOM-элементами (обычно header), изменяя их классы в зависимости от прокрутки. Если пользовательский контент влияет на эти элементы или их окружение, безопасность становится критически важной.
Безопасность начинается с понимания источников:
Любой из этих источников должен считаться недоверенным.
Принцип: никогда не доверять пользовательскому вводу без проверки.
Преобразование специальных символов в безопасные:
function escapeHTML(str) {
return str
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
Использование:
element.innerHTML = escapeHTML(userInput);
Удаление опасных конструкций, а не просто их экранирование.
Популярные библиотеки:
Пример:
const clean = DOMPurify.sanitize(userHTML);
element.innerHTML = clean;
Headroom.js часто используется вместе с динамически изменяемыми
шапками сайта. Если в header вставляется пользовательский контент через
innerHTML, это создаёт прямую уязвимость.
Опасный пример:
header.innerHTML = userInput;
Если userInput содержит:
<script>alert('XSS')</script>
код выполнится.
Безопасная альтернатива:
header.textContent = userInput;
или:
header.innerHTML = DOMPurify.sanitize(userInput);
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 — критическая ошибка.
Запрещённые конструкции:
Безопасная альтернатива:
использование заранее определённых функций:
const actions = {
hide: () => header.classList.add("hidden"),
show: () => header.classList.remove("hidden")
};
if (actions[userAction]) {
actions[userAction]();
}
Headroom.js управляет классами:
headroom--topheadroom--not-topheadroom--pinnedheadroom--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;
Если пользователь задаёт ссылки, используемые в 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;
}
CSP ограничивает выполнение вредоносного кода.
Пример заголовка:
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none';
Эффект:
Это особенно важно при работе с динамическими DOM-изменениями, связанными с Headroom.js.
<iframe sandbox="allow-same-origin"></iframe>
Используется, если требуется отображение пользовательского HTML.
Изолирует стили и структуру:
const shadow = element.attachShadow({ mode: "open" });
shadow.innerHTML = sanitizedContent;
Headroom.js активно использует CSS-классы. Пользовательские стили могут конфликтовать или ломать анимации.
Опасность:
style.innerHTML = userCSS;
Решение:
const allowedStyles = ["color", "background-color"];
Пользователь может создать элементы с именами, конфликтующими с переменными:
<form name="header"></form>
console.log(header); // теперь это форма, а не элемент
Решение:
getElementByIdПример корректной и безопасной инициализации:
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);
});
Анализ:
<script>element.textContent = userInput;
const div = document.createElement("div");
div.textContent = userInput;
Headroom.js должен использоваться из проверенного источника:
Регулярное обновление:
npm update headroom.js
function safeInsert(element, userInput) {
const clean = DOMPurify.sanitize(userInput, { ALLOWED_TAGS: [] });
element.textContent = clean;
}
const title = document.querySelector(".header-title");
safeInsert(title, userInput);
Небезопасный код может:
Безопасная обработка данных обеспечивает:
Методы:
Пример теста:
<img src=x oner ror=alert(1)>
Если выполняется — система уязвима.
Клиентская защита должна дополняться серверной:
Любой пользовательский ввод считается вредоносным до доказательства обратного. Это особенно важно при работе с библиотеками, напрямую взаимодействующими с DOM, такими как Headroom.js.