Архитектура React Server Components кардинально меняет модель безопасности веб-приложений. Критические уязвимости CVSS 10.0, новая поверхность атаки Server Actions, CSP, аудит зависимостей и защита деплоя — всё, что нужно знать разработчику Next.js для продакшен-безопасности.
Если вы используете Next.js 15.x или 16.x с App Router, ваше приложение может быть уязвимо для RCE без аутентификации (CVE-2025-66478, CVSS 10.0). Проверьте версию Next.js и установите патч, если не сделали этого ранее.
React Server Components (RSC) — ключевое архитектурное изменение в Next.js, которое переносит рендеринг и доступ к данным на сервер. Это радикально улучшает производительность, но создаёт новую серверную поверхность атаки. В декабре 2025 — январе 2026 годов были раскрыты несколько критических уязвимостей, затрагивающих стек RSC.
CVE-2025-66478 (CVSS 10.0) — уязвимость протокола RSC, позволяющая удалённое выполнение кода (RCE) без аутентификации через специально сформированные HTTP-запросы. Происходит из upstream React (CVE-2025-55182). Затрагивает Next.js 15.x, 16.x, а также 14.3.0-canary.77+. Не затрагивает 13.x, 14.x stable, Pages Router и Edge Runtime.
Исправлена в версиях: 15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7, 16.0.7. Если ваше приложение оставалось неисправленным по состоянию на 4 декабря 2025 года 13:00 PT — незамедлительно смените все секреты.
CVE-2025-55184 (High, CVSS 7.5) — DoS через специально сформированный HTTP-запрос, вызывающий бесконечный цикл. Любой App Router endpoint может быть целью. Первоначальное исправление было неполным; окончательное — под CVE-2025-67779.
CVE-2025-55183 (Medium, CVSS 5.3) — утечка исходного кода Server Functions. Сконструированный запрос может вернуть скомпилированный исходный код других Server Functions. Риск: утечка бизнес-логики и секретов, встроенных в скомпилированный вывод (не переменные окружения).
| Версия | CVE-2025-66478 (RCE) | CVE-2025-55184 (DoS) | CVE-2025-55183 (Утечка) | Исправлено в |
|---|---|---|---|---|
| 13.3+ | — | ✓ | — | 14.2.35 |
| 14.x | — | ✓ | — | 14.2.35 |
| 15.0.x | ✓ | ✓ | ✓ | 15.0.7 |
| 15.1.x | ✓ | ✓ | ✓ | 15.1.11 |
| 15.2.x | ✓ | ✓ | ✓ | 15.2.8 |
| 15.3.x | ✓ | ✓ | ✓ | 15.3.8 |
| 15.4.x | ✓ | ✓ | ✓ | 15.4.10 |
| 15.5.x | ✓ | ✓ | ✓ | 15.5.9 |
| 16.0.x | ✓ | ✓ | ✓ | 16.0.7 |
Server Components могут напрямую обращаться к базам данных, файловой системе и секретам — но могут непреднамеренно утекать данные через пропсы, передаваемые Client Components. Основные риски:
Решения: Data Access Layer (DAL) с директивами server-only и
React Taint API (experimental_taintObjectReference, experimental_taintUniqueValue),
включаемый через experimental: { taint: true } в next.config.js.
// server-only — вызовет ошибку сборки при импорте из Client Component
import 'server-only'
export function getAuthorizedData(userId: string) {
// Этот код гарантированно не попадёт в клиентский бандл
}
Используйте import 'server-only' для принудительной изоляции серверных модулей.
Переменные окружения: process.env доступен только на сервере; префикс
NEXT_PUBLIC_ экспортирует значение в клиентский бандл.
// НЕПРАВИЛЬНО: проверка только в UI
export default function DeletePost({ postId }: { postId: string }) {
// ... пользователь видит кнопку удаления
// Server Action ниже — НЕ ЗАЩИЩЁН
async function deletePost() {
'use server'
await db.post.delete({ where: { id: postId } })
}
}
// ПРАВИЛЬНО: проверка внутри Server Action
export default function DeletePost({ postId }: { postId: string }) {
async function deletePost(formData: FormData) {
'use server'
const session = await getSession()
if (!session?.user) throw new Error('Не авторизован')
const post = await db.post.findUnique({ where: { id: postId } })
if (!post || post.authorId !== session.user.id) {
throw new Error('Нет прав на удаление')
}
await db.post.delete({ where: { id: postId } })
}
}
Insecure Direct Object Reference — уязвимость, когда пользователь может получить доступ к чужому ресурсу, подменив ID в запросе. Server Actions особенно уязвимы, так как параметры (ID поста, ID пользователя) приходят от клиента.
// Всегда проверяйте принадлежность ресурса
async function updatePost(formData: FormData) {
'use server'
const session = await getSession()
const post = await db.post.findUnique({ where: { id: formData.get('postId') } })
// Проверка: принадлежит ли пост текущему пользователю?
if (post.authorId !== session.user.id) {
throw new Error('У вас нет прав на редактирование этого поста')
}
// Только после проверки — выполнение мутации
await db.post.update({ where: { id: post.id }, data: { title: formData.get('title') } })
}
Server Actions, определённые внутри компонентов, создают замыкания с захваченными
переменными. Next.js автоматически шифрует эти переменные. Ключ шифрования генерируется
автоматически при каждой сборке; для self-hosted можно переопределить через
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY.
Важно: не полагайтесь на шифрование замыканий как на единственную защиту для чувствительных значений. Ключи ротации — стандартная практика для self-hosted деплоев.
Server Actions сравнивают Origin с Host (или X-Forwarded-Host) для защиты от CSRF.
Для multi-layered бэкендов с обратными прокси настройте serverActions.allowedOrigins
в next.config.js.
Никогда не доверяйте searchParams, form data или URL-параметрам без валидации.
Используйте схемы валидации (Zod, Yup) внутри Server Actions. DTO-паттерн: возвращайте
только то, что нужно UI, а не сырые записи БД.
import { z } from 'zod'
const postSchema = z.object({
title: z.string().min(3).max(200),
content: z.string().min(10),
})
async function createPost(formData: FormData) {
'use server'
const data = postSchema.parse({
title: formData.get('title'),
content: formData.get('content'),
})
// data — безопасный, проверенный объект
const post = await db.post.create({ data })
return { success: true, id: post.id } // DTO — не сырая запись
}
Server Actions, выполняющие дорогие операции (отправка email, запись в БД), должны иметь ограничение частоты запросов. Используйте Backend for Frontend (BFF) паттерн с rate limiting на уровне обратного прокси или middleware.
Требует динамического рендеринга всех страниц — все страницы должны быть SSR, без статики/CDN. Компромисс: нет кэширования CDN, выше нагрузка на сервер, несовместимо с ISR/PPR.
// proxy.ts (бывший middleware.ts)
import { NextRequest, NextResponse } from 'next/server'
export function middleware(request: NextRequest) {
const nonce = crypto.randomUUID()
const csp = [
`default-src 'self'`,
`script-src 'nonce-${nonce}' 'strict-dynamic'`,
`style-src 'nonce-${nonce}' 'unsafe-inline'`,
`img-src 'self' data: blob: https:`,
`base-uri 'none'`,
].join('; ')
const response = NextResponse.next()
response.headers.set('Content-Security-Policy', csp)
response.headers.set('x-nonce', nonce)
return response
}
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon).*)'],
}
Альтернатива nonce — поддерживает статическую генерацию и кэширование CDN.
Хэши генерируются на этапе сборки через experimental.sri.algorithm.
Ограничения: экспериментальная функция, только App Router, не работает
с динамическими скриптами.
Для приложений без строгих требований CSP — установите заголовки через
async headers() в next.config.js. Позволяет 'unsafe-inline' для
скриптов и стилей — проще, но менее безопасно. Поддерживает статическую генерацию и CDN.
В development режиме требуется 'unsafe-eval' — React использует eval для
стек-трейсов. В production eval не нужен. Используйте условный шаблон:
const isDev = process.env.NODE_ENV === 'development'
const csp = `script-src 'nonce-${nonce}' 'strict-dynamic' ${isDev ? "'unsafe-eval'" : ''}`
sharp для оптимизации изображений — требует native build tools@next/bundle-analyzer для выявления раздувания цепочки поставокpackage.json scripts — postinstall скрипты могут выполнять произвольный кодserver-only модулях?/[param]?dependency-cruiser): визуализация и контроль границ модулейВажно: безопасность npm-пакетов — отдельная большая тема. Уязвимость CVE-2026-41242 в protobuf.js (CVSS 9.8) — отличный пример того, как одна несанитизированная интерполяция строк скомпрометировала миллионы приложений. Используйте чеклист оценки npm пакета перед установкой для предотвращения таких инцидентов.
Настройте через async headers() в next.config.js:
// next.config.js
const securityHeaders = [
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
{ key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
]
module.exports = {
async headers() {
return [
{ source: '/(.*)', headers: securityHeaders },
]
},
}
Proxy выполняется перед каждым запросом — мощный, но рискованный инструмент.
Ключевое различие: NextResponse.next({ request: { headers } }) модифицирует
внутренние заголовки, а New Response(..., { headers }) отправляет заголовки
клиенту — чувствительные данные могут утечь.
Никогда не暴露йте Next.js сервер напрямую в интернет. Используйте nginx, Caddy или Traefik как обратный прокси для обработки malformed-запросов, slow connection attacks, rate limiting, ограничения размера payload и TLS-терминации.
# Пример nginx reverse proxy
server {
listen 443 ssl;
server_name my-nextjs-app.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
}
}
images.dangerouslyAllowLocalIP: true только для приватных сетей.images.remotePatterns (не deprecated images.domains) — требует протокол + hostname для внешних изображений.NEXT_PUBLIC_ префикс экспортирует в клиентский бандл.env.* обязательно в .gitignore
Централизованный, server-only слой доступа к данным с обязательной проверкой
авторизации. Возвращает минимальные DTO — никогда не сырые записи БД.
Использует React.cache() для разделяемого кэширования.
import 'server-only'
import { cache } from 'react'
export const getProfileDTO = cache(async (userId: string) => {
const session = await getSession()
if (!session?.user) throw new Error('Unauthorized')
const user = await db.user.findUnique({ where: { id: userId } })
if (!user) return null
// DTO — только то, что нужно UI
return {
name: user.name,
email: user.email,
avatar: user.avatarUrl,
role: user.role,
}
})
Относитесь к любому доступу Server Component к данным как к недоверенному по умолчанию. Не предполагайте безопасность внутренней сети — проверяйте на каждом уровне. Каждый модуль DAL должен проверять авторизацию независимо.
Никогда не выполняйте мутации (logout, запись в БД, инвалидация кэша) во время рендеринга. Next.js явно предотвращает cookies/cache revalidation внутри render-методов. Используйте Server Actions для мутаций через отправку формы.
serverActions.allowedOrigins для reverse proxy
При стриминге частичный контент может утечь, если проверка авторизации отложена.
Используйте connection() API для принудительного ожидания входящего запроса
перед рендерингом. Балансируйте между Cache Components и Dynamic rendering в зависимости
от требований безопасности.
Финальный чеклист — пройдите перед деплоем или после любого обновления:
experimental.taint: true в next.config.jsБезопасность Next.js 16 — это многослойная задача. Уязвимости React Server Components (CVE-2025-66478, CVSS 10.0) показали, что новая архитектура требует нового подхода к безопасности. Server Actions меняют модель аутентификации — проверки на странице больше не работают, авторизация должна быть внутри каждой action. Content Security Policy с nonce или SRI защищает от XSS и code injection. Аудит зависимостей и безопасность цепочки поставок становятся обязательным элементом CI/CD.
Хорошая новость в том, что Next.js предоставляет все необходимые инструменты:
server-only, React Taint API, встроенную CSRF-защиту, гибкую конфигурацию
CSP и security headers. Ключ к безопасности — применение всех этих инструментов
как единой системы, а не выборочно.
Архитектура Defense-in-Depth с DAL, zero trust, многоуровневой CSRF-защитой и правильной конфигурацией деплоя — единственный подход, который работает в современных условиях, когда уязвимости эксплуатируются в течение часов после раскрытия.
Я разрабатываю production-приложения на Next.js, React и Node.js. Обсудим безопасность и архитектуру вашего проекта.