Любой компонент, который принимает текст от пользователя и отображает его в DOM, становится потенциальной точкой внедрения вредоносного кода. В контексте Tom Select, где элементы списка, теги и опции часто формируются динамически, основная угроза связана с внедрением HTML и JavaScript через неподготовленные строки.
Типичные векторы атак включают:
<script> через значения опций;onerror, onload);Tom Select по умолчанию минимизирует риск, но не исключает его
полностью, особенно при использовании кастомных рендеров
(render) и динамических источников данных
(load).
Tom Select работает с двумя основными сущностями:
value — идентификатор элемента;text — отображаемый текст.При стандартной конфигурации библиотека экранирует текстовые значения при вставке в DOM, используя внутренние механизмы безопасного создания узлов. Однако это поведение может быть нарушено в следующих случаях:
render.option или
render.item с возвратом HTML-строк;innerHTML;load callback.Пример безопасного поведения:
new TomSelect("#select", {
options: [
{ value: "1", text: "JavaScript" },
{ value: "2", text: "TypeScript" }
]
});
В этом случае библиотека сама создаёт текстовые узлы, предотвращая интерпретацию HTML.
Наиболее частая причина уязвимостей — переопределение
render.
Пример небезопасной реализации:
new TomSelect("#select", {
render: {
option: (data) => {
return `<div class="option">${data.text}</div>`;
}
}
});
Если data.text содержит вредоносный код, он будет
интерпретирован браузером как HTML:
data.text = `<img src=x oner ror=alert(1)>`;
В результате происходит выполнение скрипта через событие
onerror.
Минимально необходимый уровень защиты — экранирование специальных символов:
< → <> → >& → &" → "' → 'Базовая функция санитизации:
function escapeHTML(str) {
return String(str)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
Применение в render:
new TomSelect("#select", {
render: {
option: (data) => {
return `<div class="option">${escapeHTML(data.text)}</div>`;
}
}
});
Такой подход исключает интерпретацию HTML, превращая любой ввод в безопасный текстовый вывод.
При использовании асинхронной загрузки данных через load
риск возрастает, так как источник часто внешний.
new TomSelect("#select", {
load: function(query, callback) {
fetch(`/api/search?q=${encodeURIComponent(query)}`)
.then(res => res.json())
.then(data => callback(data.items));
}
});
Если API возвращает небезопасные строки, они попадут в интерфейс напрямую.
Безопасная обработка:
load: function(query, callback) {
fetch(`/api/search?q=${encodeURIComponent(query)}`)
.then(res => res.json())
.then(data => {
const safe = data.items.map(item => ({
value: item.value,
text: escapeHTML(item.text)
}));
callback(safe);
});
}
Ключевой принцип: санитизация должна происходить до передачи данных в Tom Select, а не после рендеринга.
Правильная модель данных снижает риск XSS:
value никогда не должен содержать HTML;text должен считаться недоверенным;description,
label, meta) также требуют обработки.Пример корректной структуры:
{
value: "user_123",
text: "John Doe",
role: "Administrator"
}
Даже при утечке text в HTML, значение value
не должно использоваться для рендеринга без обработки.
Безопасный способ генерации DOM-элементов в кастомных рендерах:
render: {
option: (data) => {
const div = document.createElement("div");
div.className = "option";
div.textContent = data.text;
return div;
}
}
textContent гарантирует, что содержимое будет
интерпретировано только как текст, без HTML-разметки.
Этот подход полностью исключает XSS через рендеринг.
Tom Select часто используется для создания тегов
(create: true), что делает ввод пользователя частью модели
данных.
new TomSelect("#select", {
create: true
});
В этом режиме пользователь может ввести произвольную строку, которая затем становится элементом списка.
Проблема возникает, если созданные значения отображаются через небезопасный render:
render: {
item: (data) => `<div>${data.text}</div>`
}
Без экранирования это прямой путь к XSS.
Безопасный вариант:
render: {
item: (data) => {
const el = document.createElement("div");
el.textContent = data.text;
return el;
}
}
Дополнительный уровень защиты — нормализация данных до вставки в Tom Select:
const sanitize = (input) => input.trim();
select.addOption({
value: "tag",
text: sanitize(userInput)
});
Для более строгой обработки может использоваться белый список символов:
function strictSanitize(str) {
return str.replace(/[^\w\s-]/g, "");
}
Некоторые реализации намеренно используют HTML в
render.option для кастомного интерфейса. Это допустимо
только при строгом контроле источника данных.
Опасная практика:
return `<div>${data.label}</div>`;
Без гарантии происхождения data.label это приводит к
внедрению HTML.
Безопасная альтернатива с частичным HTML:
const wrapper = document.createElement("div");
const title = document.createElement("span");
title.textContent = data.label;
wrapper.appendChild(title);
return wrapper;
Content Security Policy снижает последствия ошибок санитизации.
Пример политики:
Content-Security-Policy: default-src 'self'; script-src 'self'
Даже при наличии XSS-инъекции выполнение inline-script будет заблокировано.
Однако CSP не заменяет санитизацию, а лишь ограничивает ущерб.
При использовании Tom Select вместе с React, Vue или Angular часто возникает дублирование ответственности за рендеринг.
Проблема:
Типичный антипример:
const options = props.items.map(i => ({
value: i.id,
text: i.label
}));
Если i.label приходит из внешнего API без очистки, риск
сохраняется даже при использовании фреймворка.
Безопасная интеграция Tom Select строится на нескольких уровнях защиты:
innerHTML в пользу
textContent;value/text;Такая модель исключает большинство сценариев XSS и делает поведение компонента предсказуемым даже при работе с недоверенными данными.