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

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

При использовании библиотеки Awesomplete вопрос санитизации становится особенно важным, поскольку автодополнение часто работает с текстовыми полями, динамическими списками, удалёнными источниками данных и пользовательскими запросами. Неправильно обработанный ввод способен привести к ошибкам интерфейса, некорректному отображению информации и уязвимостям безопасности.


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

Любое значение, вводимое в поле автодополнения, может содержать:

  • HTML-разметку;
  • JavaScript-код;
  • специальные символы;
  • управляющие последовательности;
  • вредоносные конструкции;
  • неожиданно длинные строки;
  • Unicode-символы и невидимые знаки.

Пример потенциально опасного ввода:

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

Другой пример:

<img src="x" oner ror="alert('hack')">

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


Роль Awesomplete в обработке данных

Awesomplete отображает элементы списка подсказок на основе массива значений.

Базовый пример:

const awesomplete = new Awesomplete(input, {
    list: [
        "JavaScript",
        "TypeScript",
        "Python"
    ]
});

В таком случае список полностью контролируется разработчиком и риск минимален.

Ситуация меняется при работе с внешними данными:

fetch("/api/search")
    .then(response => response.json())
    .then(data => {
        awesomplete.list = data;
    });

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


Санитизация перед отображением

Наиболее распространённый сценарий — очистка данных до передачи их в Awesomplete.

Исходный массив:

const data = [
    "JavaScript",
    "<script>alert(1)</script>",
    "Python"
];

Очистка:

function sanitize(text) {
    return text
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;");
}

const safeData = data.map(sanitize);

awesomplete.list = safeData;

После обработки специальные символы перестают восприниматься как HTML-разметка.


Экранирование HTML-символов

Наиболее опасными являются следующие символы:

Символ Замена
< <
> >
& &

Пример универсальной функции:

function escapeHtml(text) {
    const div = document.createElement("div");
    div.textContent = text;
    return div.innerHTML;
}

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

const safeValue = escapeHtml(userInput);

Такой подход считается одним из наиболее надёжных.


Санитизация данных с сервера

Даже если пользователь не взаимодействует напрямую с полем ввода, опасные данные могут поступать через API.

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

[
    "JavaScript",
    "<b>Python</b>",
    "<script>alert('xss')</script>"
]

Перед передачей данных в Awesomplete рекомендуется выполнять фильтрацию:

const safeItems = responseData.map(item =>
    item.replace(/<[^>]*>/g, "")
);

awesomplete.list = safeItems;

Результат:

[
    "JavaScript",
    "Python",
    "alert('xss')"
]

Удаление HTML-тегов

Иногда необходимо полностью избавиться от разметки.

Простейший вариант:

function stripTags(value) {
    return value.replace(/<[^>]+>/g, "");
}

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

const cleanValue = stripTags(userInput);

Однако регулярные выражения не способны корректно обработать всю сложность HTML.

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

function removeHtml(html) {
    const temp = document.createElement("div");
    temp.innerHTML = html;

    return temp.textContent || "";
}

Очистка поискового запроса

Awesomplete активно использует введённую строку для поиска совпадений.

Например:

input.addEventListener("input", event => {
    const query = event.target.value;
});

Перед использованием запроса желательно выполнять нормализацию:

function sanitizeQuery(query) {
    return query
        .trim()
        .replace(/\s+/g, " ");
}

Применение:

const safeQuery = sanitizeQuery(input.value);

Это позволяет устранить лишние пробелы и случайные символы форматирования.


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

Очень длинные строки способны вызывать проблемы производительности.

Пример ограничения:

function limitLength(text, max = 100) {
    return text.substring(0, max);
}

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

const query = limitLength(input.value);

Часто применяются ограничения:

  • 50 символов;
  • 100 символов;
  • 255 символов.

Конкретное значение зависит от требований проекта.


Удаление управляющих символов

Некоторые символы не отображаются визуально, но могут влиять на обработку данных.

Очистка:

function removeControlChars(text) {
    return text.replace(/[\x00-\x1F\x7F]/g, "");
}

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

const cleanText = removeControlChars(userInput);

Нормализация Unicode

Различные символы могут выглядеть одинаково, но иметь разные внутренние представления.

Пример:

const normalized = input.value.normalize("NFC");

Это особенно полезно при поиске совпадений в Awesomplete.

Пример:

awesomplete.list = data.map(item =>
    item.normalize("NFC")
);

Нормализация обеспечивает единообразное сравнение строк.


Белые списки допустимых символов

В некоторых случаях эффективнее разрешать только определённые символы.

Например, поиск по названиям товаров:

function sanitizeProductSearch(text) {
    return text.replace(/[^a-zA-Zа-яА-Я0-9\s-]/g, "");
}

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

const query = sanitizeProductSearch(input.value);

Такой подход блокирует большинство потенциально опасных конструкций.


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

Awesomplete позволяет переопределять отображение элементов.

Пример:

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

Такой код опасен.

Если значение содержит:

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

код будет интерпретирован браузером.

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

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

Здесь данные отображаются как обычный текст.


Опасность использования innerHTML

Одна из самых распространённых ошибок при интеграции Awesomplete связана с использованием innerHTML.

Небезопасный пример:

results.innerHTML = userInput;

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

results.textContent = userInput;

Либо:

results.appendChild(
    document.createTextNode(userInput)
);

Использование textContent практически всегда предпочтительнее.


Работа с DOMPurify

Для серьёзных проектов часто применяется специализированная библиотека санитизации.

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

const safeHtml = DOMPurify.sanitize(userInput);

Интеграция с Awesomplete:

const safeItems = data.map(item =>
    DOMPurify.sanitize(item)
);

awesomplete.list = safeItems;

DOMPurify эффективно обрабатывает множество сложных сценариев XSS-атак и считается одним из наиболее надёжных решений для очистки HTML.


Санитизация данных перед отправкой на сервер

Очистка необходима не только при отображении.

Пример:

const query = sanitizeQuery(input.value);

fetch("/search", {
    method: "POST",
    body: JSON.stringify({
        query
    })
});

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


Комбинированная схема защиты

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

Этапы обработки:

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

Схема для Awesomplete может выглядеть следующим образом:

function sanitize(text) {
    return text
        .trim()
        .replace(/[\x00-\x1F\x7F]/g, "")
        .substring(0, 100);
}

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

    fetch(`/search?q=${encodeURIComponent(value)}`)
        .then(response => response.json())
        .then(data => {
            const safeData = data.map(item =>
                escapeHtml(item)
            );

            awesomplete.list = safeData;
        });
});

Такой подход позволяет значительно снизить риск внедрения вредоносного кода, повысить устойчивость интерфейса к ошибочным данным и обеспечить безопасную работу механизма автодополнения при взаимодействии с пользовательским вводом и внешними источниками информации.