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

Автодополнение на основе пользовательского ввода относится к классу интерфейсов, которые постоянно обрабатывают недоверенные данные. В контексте Awesomplete это особенно критично, поскольку библиотека часто используется для отображения динамически формируемых списков подсказок, которые могут поступать как из локальных массивов, так и с удалённых API.

Основная проблема заключается в том, что любой элемент списка подсказок фактически становится частью DOM и потенциально может быть интерпретирован браузером как HTML-код. Это создаёт поверхность атаки для XSS, внедрения HTML-инъекций и подмены содержимого интерфейса.

Ключевой принцип: любые данные, пришедшие извне, должны рассматриваться как небезопасные по умолчанию


Опасность HTML-инъекций в списках подсказок

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

Типичный рискованный сценарий:

new Awesomplete(input, {
    list: ["<b>JavaScript</b>", "<i>Python</i>"]
});

Если библиотека или кастомная реализация рендеринга использует innerHTML, подобные значения могут быть интерпретированы как DOM-структура, а не как текст. Это открывает путь к выполнению произвольного HTML/JS.


Принцип безопасного отображения: textContent вместо innerHTML

Любое отображение пользовательских или внешних данных в DOM должно происходить через текстовые механизмы браузера.

Безопасный подход:

const item = document.createElement("li");
item.textContent = value;

Небезопасный подход:

item.innerHTML = value;

В контексте Awesomplete это особенно важно при кастомизации отображения элементов через item callback.


Безопасная настройка источника данных (list)

Awesomplete позволяет передавать список подсказок напрямую или через асинхронные источники. В обоих случаях необходимо гарантировать, что данные проходят этап нормализации.

Пример безопасной нормализации массива

function sanitizeList(arr) {
    return arr
        .filter(item => typeof item === "string")
        .map(item => item
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;")
        );
}

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

new Awesomplete(input, {
    list: sanitizeList(rawData)
});

Однако более надёжный подход — не хранить HTML-сущности в данных вообще, а всегда работать с чистыми строками и использовать textContent на этапе рендера.


Кастомный рендеринг элементов Awesomplete

Awesomplete позволяет переопределять поведение отображения через опцию item. Именно здесь чаще всего возникает уязвимость.

Небезопасный вариант

item: function(text, input) {
    const li = document.createElement("li");
    li.innerHTML = text;
    return li;
}

Безопасный вариант

item: function(text) {
    const li = document.createElement("li");
    li.textContent = text;
    return li;
}

Если требуется подсветка совпадений, нельзя напрямую вставлять HTML. Вместо этого используется разбиение строки и сборка DOM-узлов:

item: function(text, input) {
    const li = document.createElement("li");

    const index = text.toLowerCase().indexOf(input.toLowerCase());

    if (index >= 0) {
        const before = text.slice(0, index);
        const match = text.slice(index, index + input.length);
        const after = text.slice(index + input.length);

        li.appendChild(document.createTextNode(before));

        const mark = document.createElement("span");
        mark.textContent = match;
        mark.style.fontWeight = "bold";

        li.appendChild(mark);
        li.appendChild(document.createTextNode(after));
    } else {
        li.textContent = text;
    }

    return li;
}

Защита при работе с удалёнными источниками данных

При использовании API для подгрузки подсказок появляется дополнительный слой риска: данные могут быть скомпрометированы на сервере или в промежуточных слоях.

Рекомендации по обработке ответа API

fetch("/api/suggestions?q=" + encodeURIComponent(query))
    .then(res => res.json())
    .then(data => {
        const safe = data
            .filter(x => typeof x.name === "string")
            .map(x => x.name);

        new Awesomplete(input, { list: safe });
    });

Безопасная обработка JSON-ответов

Опасность возникает не только в HTML, но и в структуре данных. Например, если API возвращает неожиданные поля или вложенные объекты, их нельзя напрямую передавать в Awesomplete.

Небезопасно:

list: data.items

Без проверки структуры.

Безопасно:

function extractSuggestions(data) {
    if (!data || !Array.isArray(data.items)) return [];
    return data.items
        .map(x => x && x.label)
        .filter(x => typeof x === "string");
}

Защита от подмены логики через filter и replace

Awesomplete предоставляет filter и replace callbacks. Эти функции часто используются для кастомизации поведения, но могут стать точкой внедрения логики, нарушающей безопасность.

Пример безопасного filter

filter: function(text, input) {
    return text.toLowerCase().includes(input.toLowerCase());
}

Запрещено использовать eval-подобные конструкции или динамическое выполнение кода внутри фильтрации.


Экранирование специальных символов

При работе с текстом важно учитывать HTML-специфичные символы:

  • <
  • >
  • &
  • "

Универсальная функция экранирования:

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

Однако при использовании textContent необходимость в таком экранировании исчезает, что делает его предпочтительным способом отображения.


Изоляция данных и предотвращение DOM-инъекций

Любая строка, попадающая в DOM, должна проходить через один из двух безопасных путей:

  1. textContent (предпочтительно)
  2. создание DOM-узлов без HTML-интерпретации

Запрещённые практики:

  • конкатенация HTML-строк
  • использование innerHTML с внешними данными
  • вставка JSON напрямую в DOM

Content Security Policy как дополнительный уровень защиты

Даже при наличии ошибок в коде можно существенно снизить риск эксплуатации через CSP.

Рекомендуемые директивы:

  • запрет unsafe-inline
  • ограничение script-src только доверенными источниками
  • отключение inline event handlers

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

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

Обработка пользовательского ввода до передачи в Awesomplete

Ввод пользователя сам по себе не должен попадать в логику формирования списка без обработки.

Типовой безопасный pipeline:

  1. Получение input value
  2. Нормализация строки
  3. Фильтрация запрещённых символов
  4. Запрос к источнику данных
  5. Валидация ответа
  6. Передача в Awesomplete

Пример:

input.addEventListener("input", () => {
    const q = input.value.trim();

    if (q.length < 2) return;

    fetch("/api?q=" + encodeURIComponent(q))
        .then(r => r.json())
        .then(data => {
            const list = extractSuggestions(data);
            awesomplete.list = list;
        });
});

Изоляция логики отображения от бизнес-данных

Сильная архитектурная ошибка — смешивание UI-логики и данных. В случае Awesomplete это выражается в передаче “готового HTML” вместо структурированных данных.

Правильная модель:

  • данные: чистые строки или объекты
  • отображение: DOM-логика внутри item
  • бизнес-логика: отдельный слой

Такое разделение минимизирует вероятность внедрения вредоносных данных в интерфейс.


Обработка edge cases и неожиданных входных значений

В реальных условиях данные могут содержать:

  • null
  • undefined
  • числа вместо строк
  • вложенные объекты
  • пустые строки

Устойчивый фильтр:

function normalize(value) {
    if (typeof value !== "string") return "";
    return value.trim();
}

И последующая очистка:

const safeList = raw.map(normalize).filter(Boolean);

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

При работе с динамическими источниками полезно вводить дополнительную проверку уникальности и допустимых значений.

function unique(arr) {
    return [...new Set(arr)];
}

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


Итоговая модель безопасной интеграции Awesomplete

Безопасная архитектура использования библиотеки строится вокруг трёх принципов:

  • отсутствие HTML в данных
  • использование textContent вместо innerHTML
  • строгая валидация всех внешних источников

Любое отклонение от этих принципов превращает автодополнение в точку входа для DOM-инъекций и XSS-атак, особенно в сценариях с удалёнными API и пользовательским контентом.