Появление архитектуры React Server Components (RSC) и серверных экшенов (Server Actions) полностью перевернуло парадигму fullstack-разработки на React. Мы привыкли писать асинхронные серверные функции прямо в одном файле с интерфейсом: пометил функцию директивой 'use server', повесил на обработчик формы — и фреймворк сам координирует сетевые запросы, сериализацию данных и обновление дерева компонентов.
Это подарило потрясающий комфорт и скорость разработки. Но под капотом возник сложнейший сетевой протокол сериализации (RSC Payload Protocol), который работает как невидимый мост между браузером и сервером.
В октябре 2026 года в этом мосту была обнаружена катастрофическая трещина: критическая уязвимость удаленного выполнения кода без аутентификации (Unauthenticated RCE), получившая идентификатор GHSA-xqc2-xf4c-x3h3. Уязвимость затрагивает все проекты на базе Next.js App Router и Waku, использующие серверные экшены.
Давай без паники разберем, как именно была устроена эта брешь, почему она сработала и как надежно закрыть свои боевые сервера.
Как работают Server Actions под капотом?
Чтобы понять уязвимость, нужно освежить механику работы серверных экшенов.
Когда компилятор встречает директиву 'use server', он вырезает тело функции из клиентского бандла и присваивает ей уникальный криптографический идентификатор (Action ID, например $ACTION_ID_481516). На клиенте остается лишь легковесная заглушка.
При вызове экшена клиент отправляет на сервер стандартный POST-запрос с особым заголовком Next-Action: $ACTION_ID_481516. В теле запроса передаются сериализованные аргументы.
Но в JavaScript аргументы функции — это не просто плоский JSON из строк и чисел. Серверные компоненты поддерживают передачу дат, бинарных массивов, структур ошибок и даже неразрешенных промисов (Promises). Для этого команда React разработала собственный строковый диалект поверх JSON, использующий специальные префиксы:
$@: ссылка на другой объект в графе (Circular References);$Q: промис;$F: ссылка на серверную функцию.
И именно в парсере этих специальных префиксов скрывалась роковая ловушка.
Вектор атаки: Prototype Pollution и отравление диспетчера
Парсер десериализации RSC принимал входящую строку и рекурсивно восстанавливал структуры объектов в оперативной памяти Node.js рантайма.

Атакующий отправлял на любой публичный URL сайта вредоносный POST-запрос со специально сформированным деревом JSON:
- Загрязнение прототипа (Prototype Pollution): в тело передавались свойства с именами
__proto__иconstructor.prototype. Внутренний парсер не валидировал безопасность ключей объектов перед слиянием (Object Merge). - Переопределение таблицы диспетчеризации: загрязнив базовый прототип объекта в контексте Node.js процесса, атакующий получал возможность переопределить внутренние поля таблицы маппинга идентификаторов экшенов (
serverActionsManifest). - Исполнение системного кода: подменив ссылку на функцию-обработчик на вызов встроенного системного метода
child_process.execSync, злоумышленник добивался выполнения произвольной shell-команды от имени пользователя, запустившего контейнер Next.js!
Самое страшное в этой уязвимости — отсутствие требования к авторизации. Серверные экшены по умолчанию открыты для вызова по сети. Даже если на фронтенде кнопка формы скрыта за авторизацией, сам эндпоинт экшена остается доступным для отправки прямых HTTP-запросов, если разработчик явно не вызвал проверку прав внутри тела экшена.
О том, как архитектура сборки влияет на безопасность и поведение приложений, мы ранее рассуждали в контексте опыта внедрения React Compiler в продакшен Next.js.
Кого затронула уязвимость?
Уязвимость присутствует в следующих конфигурациях:
- Проекты на базе Next.js версий с 14.0.0 по 14.2.14;
- Проекты на базе Next.js версий с 15.0.0-canary по 15.0.1;
- Любые кастомные реализации RSC, использующие пакет
react-server-dom-webpackуязвимых версий.
Если твой проект использует классический Pages Router (pages/) и не включает экспериментальные серверные экшены, проект находится в безопасности. Но если используется App Router (app/), обновление является экстренным и обязательным.
Что исправили в патчах безопасности?
Команды React и Vercel оперативно выпустили корректирующие релизы:
- Строгая изоляция объектов: десериализатор перешел на создание объектов через
Object.create(null)без прототипа, что делает Prototype Pollution физически невозможным. - Запрет служебных префиксов от внешних клиентов: клиентские запросы больше не могут напрямую передавать зарезервированные символы
$Fи$@в теле входных аргументов экшена. - Ужесточение проверки Origin и CSRF: сервер сверяет заголовок
Originвходящего запроса со списком доверенных доменов приложения.
Чек-лист экстренного аудита для команд
- Немедленно обновите зависимости:
В проектах на Next.js 14 обновитесь до версии 14.2.15 или новее.
В проектах на Next.js 15 обновитесь до версии 15.0.2 или новее.
Если вы используете пакет
react-server-dom-webpackнапрямую без фреймворка, накатите версию с закрытым CVE из бюллетеня безопасности React. - Проверьте авторизацию внутри серверных экшенов:
Помните золотое правило архитектуры: директива
'use server'превращает функцию в публичный сетевой API эндпоинт. Никогда не полагайтесь на то, что «эту кнопку на фронтенде видит только авторизованный админ». Любой запрос можно повторить через curl или Postman. Всегда проверяйте сессию пользователя и права доступа первой же строчкой внутри тела функции экшена. - Ограничьте права контейнера в Kubernetes и Docker:
Запускайте контейнер веб-сервера под непривилегированным системным пользователем (
USER node), монтируйте корневую файловую систему в режимеreadOnlyRootFilesystem: trueи закрывайте исходящий сетевой доступ во внутреннюю сеть дата-центра для фронтенд-подов с помощью сетевых политик NetworkPolicy. Даже в случае успешной эксплуатации уязвимости атакующий окажется заперт в пустом контейнере без прав записи на диск. - Проведите аудит сетевых логов на подозрительные заголовки:
Настройте в WAF или Ingress-контроллере фильтрацию входящих POST-запросов: обращайте внимание на аномальные структуры с ключами
__proto__или служебными маркерами сериализации.
Безопасность сложных распределенных фреймворков — это непрерывная гонка, и своевременная установка патчей вместе с многоуровневой защитой (Defense in Depth) остается главной обязанностью каждого ответственного инженера.
