Безопасность Nuxt
В Nuxt добавляется SSR: v-html в серверном рендеринге особенно опасен, потому что вредоносный код попадает в исходный HTML ещё до гидратации. Статья разбирает специфику Nuxt: риски SSR, runtimeConfig для секретов, nuxt-security для CSP и заголовков, серверные маршруты Nitro с валидацией и авторизацией, CSRF и CSP.
Содержание
- SSR и безопасность
- Nuxt Security Module
- Серверные маршруты Nitro
- runtimeConfig и секреты
- CSRF в Nuxt
- CSP в Nuxt
- Чек-лист безопасности Nuxt
- Терминология
- Ключевые тезисы для интервью
- Заключение
- Полезные ссылки
SSR и безопасность
Nuxt рендерит приложение на сервере. Это добавляет удобства, но и новые риски.
Universal rendering
В Nuxt 3 компоненты по умолчанию рендерятся на сервере, затем гидратируются на клиенте. Vue-интерполяции экранируются и на сервере, и на клиенте. Но v-html в SSR-контексте особенно опасен: вредоносный код попадает в исходный HTML и выполняется мгновенно, до гидратации.
Правильная обработка пользовательского ввода в SSR
<script setup>
import DOMPurify from 'dompurify';
const { data: article } = await useFetch('/api/article/1');
const safeContent = computed(() =>
DOMPurify.sanitize(article.value?.content || '')
);
</script>
<template>
<article v-html="safeContent"></article>
</template>
Опасность useFetch с пользовательским URL
<script setup>
// ❌ Опасно: URL из query-параметра без валидации
const { data } = await useFetch(() => `/api/${useRoute().params.slug}`);
</script>
Параметры маршрута должны валидироваться на сервере.
Nuxt Security Module
nuxt-security — официальный модуль безопасности для Nuxt. Он помогает настроить:
- CSP;
- security-заголовки;
- CORS;
- rate limiting;
- защиту от XSS;
- subresource integrity (SRI);
- и многое другое.
Базовая настройка
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['nuxt-security'],
security: {
headers: {
contentSecurityPolicy: {
'default-src': ["'self'"],
'script-src': ["'self'", "'nonce-{{nonce}}'", "'strict-dynamic'"],
'style-src': ["'self'", "'nonce-{{nonce}}'"],
'img-src': ["'self'", 'data:', 'https:'],
'connect-src': ["'self'"],
'frame-ancestors': ["'none'"],
},
crossOriginEmbedderPolicy: 'require-corp',
xFrameOptions: 'DENY',
},
rateLimiter: {
tokensPerInterval: 100,
interval: 'hour',
},
},
});
Модуль автоматически генерирует nonce для каждого запроса и встраивает его в скрипты.
Серверные маршруты Nitro
Nuxt использует Nitro для серверных маршрутов. Это аналог Route Handlers в Next.js.
Пример с валидацией
// server/api/posts.post.ts
import { z } from 'zod';
const schema = z.object({
title: z.string().min(1).max(200),
content: z.string().min(1).max(10000),
});
export default defineEventHandler(async (event) => {
const session = await getUserSession(event);
if (!session) {
throw createError({ statusCode: 401, statusMessage: 'Unauthorized' });
}
const body = await readBody(event);
const result = schema.safeParse(body);
if (!result.success) {
throw createError({ statusCode: 400, statusMessage: 'Invalid input' });
}
await createPost(session.user.id, result.data);
return { ok: true };
});
Проверка прав
// server/api/posts/[id].delete.ts
export default defineEventHandler(async (event) => {
const session = await getUserSession(event);
const id = getRouterParam(event, 'id');
const post = await getPost(id);
if (post.authorId !== session.user.id) {
throw createError({ statusCode: 403, statusMessage: 'Forbidden' });
}
await deletePost(id);
return { ok: true };
});
runtimeConfig и секреты
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
apiSecret: process.env.API_SECRET,
public: {
apiBase: process.env.NUXT_PUBLIC_API_BASE,
},
},
});
<script setup>
const config = useRuntimeConfig();
// Только на сервере
console.log(config.apiSecret);
// Доступно на клиенте и сервере
console.log(config.public.apiBase);
</script>
Правило: секреты всегда в runtimeConfig без public, публичные значения — в runtimeConfig.public.
CSRF в Nuxt
Nuxt не имеет встроенной CSRF-защиты из коробки, но её легко добавить.
SameSite cookies
// server/utils/session.ts
export async function setUserSession(event, user) {
await setCookie(event, 'session', JSON.stringify(user), {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 60 * 60 * 24 * 7,
});
}
CSRF-токены
Можно использовать h3-cors или встроенные middleware для проверки Origin и CSRF-токенов.
// server/middleware/csrf.ts
export default defineEventHandler((event) => {
if (event.method === 'POST') {
const origin = getHeader(event, 'origin');
const allowed = ['https://mysite.com'];
if (!origin || !allowed.includes(origin)) {
throw createError({ statusCode: 403 });
}
}
});
CSP в Nuxt
Через nuxt-security:
export default defineNuxtConfig({
modules: ['nuxt-security'],
security: {
headers: {
contentSecurityPolicy: {
'default-src': ["'self'"],
'script-src': ["'self'", "'nonce-{{nonce}}'"],
'style-src': ["'self'", "'nonce-{{nonce}}'"],
},
},
},
});
Без модуля — через Nuxt middleware:
// server/middleware/csp.ts
export default defineEventHandler((event) => {
appendResponseHeader(
event,
'Content-Security-Policy',
"default-src 'self'; script-src 'self'; style-src 'self';"
);
});
Чек-лист безопасности Nuxt
- Установить
nuxt-securityи настроить CSP. - Использовать
runtimeConfigдля секретов. - Валидировать входные данные в server routes.
- Проверять авторизацию в server routes и middleware.
- Настроить HttpOnly, Secure, SameSite cookies.
- Передавать клиенту только необходимые данные.
Терминология
| Термин | Значение |
|---|---|
| Nitro | Серверный движок Nuxt |
| runtimeConfig | Конфигурация Nuxt для серверных и публичных значений |
| nuxt-security | Модуль безопасности для Nuxt |
| Universal rendering | SSR + CSR в одном приложении |
| getUserSession | Утилита для получения сессии в Nuxt Auth |
Ключевые тезисы для интервью
- В Nuxt
v-htmlв SSR попадает в исходный HTML, который выполняется мгновенно до гидратации — санитизация обязательна и на сервере. runtimeConfigбезpublic— только сервер;runtimeConfig.public— клиент и сервер;nuxt-securityиз коробки настраивает CSP с nonce, security-заголовки, rate limiting и SRI.- Nitro — серверный движок Nuxt; серверные маршруты (
server/api/) требуют валидации (Zod) и проверки авторизации/владения ресурсом. - Nuxt не имеет встроенной CSRF-защиты — настраивайте SameSite cookies, проверку Origin и CSRF-токены в middleware.
Заключение
Nuxt предоставляет хорошую базовую защиту через автоматическое экранирование Vue, но особенности SSR удваивают риски: вредоносный v-html попадает в исходный HTML и индексируется поисковиками. Модуль nuxt-security закрывает большинство инфраструктурных задач: CSP с nonce, security-заголовки, rate limiting. Для SOC2 и CASA важно показать, что команда понимает различие между клиентом и сервером в Nuxt, использует runtimeConfig для секретов, валидирует данные в Nitro и настраивает защиту через nuxt-security. Про XSS-базу смотрите XSS: анатомия атаки, про Vue — Безопасность Vue.