Безопасность в Vue приложениях

f

Вы написали красивое Vue-приложение. Компоненты летают, реактивность работает как часы, а клиент доволен. Но если вы не проверили три конкретных места — всё это может разлететься осколками. Не очередное перечисление очевидных истин, а взгляд на то, о чём молчат в стандартных гайдах.

Вы используете v-html — и знаете, что не стоит. Но что насчёт dangerouslySetInnerHTML-аналогов в сторонних компонентах? Зависимости, которые вы тянете через npm, могут содержать уязвимости, о которых вы даже не догадываетесь. И это не гипотетические риски — реальные кейсы взломов через скомпрометированные пакеты уже стали рутиной. Вам нужно смотреть глубже.

Угроза приходит не только извне. Неправильное управление состоянием, утечка данных через Vuex или Pinia, забытые токены в кеше браузера — это зона вашей ответственности. В этой разборе вы найдёте не просто списки, а конкретные сценарии: как выглядит атака через ваш же код, где на самом деле кроется XSS, и почему стандартный Content Security Policy часто не работает с Vue.

1. XSS в Vue: неочевидные векторы атаки, о которых молчат

Вы уверены, что Vue автоматически экранирует все вставки? Да, {{ }} экранирует HTML-сущности. Но как только вы используете v-html или передаёте строку в директиву href через JavaScript-выражение — ваша защита исчезает. Главная опасность: динамически создаваемые шаблоны через new Function() или строковые шаблоны в вычисляемых свойствах.

Пример: вы делаете :class="userInput" — и злоумышленник передаёт {'xss': 'onerror=alert(1)'}. Vue, конечно, проверяет допустимые значения, но если ваша валидация на входе пропускает объект, атрибуты могут быть подменены. Подобные атаки встречаются реже, но без них на ходу сложно защититься.

Совет: никогда не доверяйте динамическим атрибутам, которые создаются на основе пользовательского ввода. Используйте DOMPurify для санитации любых строк, которые попадают в v-html или динамические v-bind. Но помните: санитация на клиенте — это хорошо, но она не заменяет проверку на сервере.

2. Зависимости npm: невидимая бомба замедленного действия

Стандартный npm audit показывает далеко не всё. Вы можете увидеть сотни предупреждений, но какая уязвимость реально угрожает вашему приложению? Vue-приложения часто используют такие пакеты, как vue-router, vuex, pinia, axios, vue-apollo, element-plus — и у каждого из них есть миллионы загрузок, а значит, каждый является потенциальной мишенью.

Реальный пример: в 2023 году в одном из популярных UI-фреймворков для Vue нашли уязвимость, позволяющую внедрить произвольный атрибут в DOM через компонент кнопки. Пока вы не обновили версию — ваше приложение уязвимо. Более того: если ваша команда использует монорепозиторий с сотнями зависимостей, вы даже не заметите, что какая-то из них тянет устаревшую версию Vue или уязвимую библиотеку.

Что с этим делать? Используйте npm audit fix с умом, но всегда проверяйте changelogs. Добавьте в CI/CD этапы сканирования уязвимостей с помощью Synk или Socket. Заморозьте версии зависимостей в package-lock.json и не обновляйте пакеты автоматически — дайте команде время на тестирование.

3. CSP и Vue: почему настройки часто ломают приложение

Content Security Policy (CSP) — ваш щит от XSS. Но с Vue этот щит может превратиться в подушку безопасности: он даёт ложное спокойствие. Стандартный CSP для Vue-приложений часто требует unsafe-eval для script-src, что убивает весь смысл защиты. Почему? Потому что Vue в режиме разработчика использует new Function() для компиляции шаблонов. Если вы используете сборку Vue, которая включает компилятор в рантайме, CSP-политика с unsafe-eval не заблокирует XSS-инъекцию через v-html — она разрешает выполнение любого кода.

Более тонкий момент: CSP может блокировать инлайн-стили, которые Vue использует для динамического управления классами. Если ваш компонент использует :style с объектом, браузер может не применить стили, если политика запрещает инлайн-атрибуты. Это не уязвимость, но ведёт к поломке интерфейса, что может быть использовано для атаки на логику (например, визуальный DoS).

Правильная CSP для Vue-приложения: script-src 'self' 'nonce-xyz'; style-src 'self' 'nonce-xyz'; font-src 'self' data:; img-src 'self' data: https:; connect-src 'self' https://api.yoursite.com; object-src 'none'; base-uri 'none';. И никогда не пишите 'unsafe-inline' для скриптов — это прямой путь к XSS.

4. Серверный рендеринг (SSR) и утечка данных: что вы не проверяете

Vue SSR — мощный инструмент, но он открывает дверь для утечек данных, о которых вы можете и не подозревать. Когда вы рендерите страницу на сервере, все данные, которые вы передаёте в шаблон, сериализуются. Если случайно в контекст рендеринга попал объект с чувствительной информацией (токен, пароль, внутренние логи), он будет встроен в HTML в виде window.__INITIAL_STATE__. Клиент заберёт эти данные — и любой может прочитать их через инспектор кода.

Более тонкая угроза: при SSR-рендеринге вы можете вызвать на сервере API-запрос, который возвращает данные для авторизованного пользователя. Если не обработать ошибку, эти данные могут быть вставлены в HTML для всех пользователей, а не только для того, кто имеет к ним доступ. Пример: вы рендерите блок с email-адресом пользователя, но не проверяете, что это именно тот пользователь, который запросил страницу. Бот может собрать все email’ы из HTML.

Совет: всегда используйте helmet (для серверного рендеринга) для заголовков безопасности, но помните, что он не защищает от утечки данных через HTML-контент. Проверяйте, что вы вставляете в window.__INITIAL_STATE__.

5. Vuex/Pinia: опасность реактивного состояния

Вы храните данные в store, и они реактивно доступны каждому компоненту. Это удобно. Но если вы не контролируете, какой компонент может изменить состояние — вы открываете дверь для атаки типа «state poisoning». Злоумышленник может внедрить вредоносное значение в атрибут компонента, и через реактивность оно распространится по всему приложению.

Пример: у вас есть форма для редактирования профиля. Пользователь отправляет данные, которые попадают в Pinia. Если вы не валидируете входные данные на стороне клиента (хотя это должно быть на сервере), то злоумышленник может сохранить JavaScript-строку в поле name. Когда другой пользователь просматривает профиль, v-html на старнице может вывести эту строку — и сработает XSS. Ваш store стал вектором атаки.

Ещё одна ловушка: мутации из плагинов. Вы используете плагин для авторизации (например, vue-router с guard). Если плагин изменяет состояние store (например, сохраняет роль пользователя), и вы не проверяете, что роль получена от доверенного источника, злоумышленник может подменить запрос и установить себе роль администратора. Это не гипотетика — такая уязвимость найдена в реальных проектах.

Хорошая практика: в Pinia настройте строгий режим strict: true в разработке — это выявит нежелательные изменения через devtools. Но не полагайтесь на это в продакшне: используйте deepFreeze для глубокой заморозки state после мутации.

В конечном счёте, безопасность вашего Vue-приложения стоит на трёх китах: контроль входных данных и атрибутов, управление зависимостями и грамотная CSP. Каждый из этих аспектов требует внимания не разово, а на протяжении всего цикла разработки. Не думайте, что если ваш код работает без ошибок — он защищён. Проверьте каждую точку, где пользовательский ввод касается реактивной системы. Только тогда вы сможете спать спокойно.

Добавлено: 23.04.2026