Навигация

Безопасность Nuxt

В Nuxt добавляется SSR: v-html в серверном рендеринге особенно опасен, потому что вредоносный код попадает в исходный HTML ещё до гидратации. Статья разбирает специфику Nuxt: риски SSR, runtimeConfig для секретов, nuxt-security для CSP и заголовков, серверные маршруты Nitro с валидацией и авторизацией, CSRF и CSP.

Содержание

  1. SSR и безопасность
  2. Nuxt Security Module
  3. Серверные маршруты Nitro
  4. runtimeConfig и секреты
  5. CSRF в Nuxt
  6. CSP в Nuxt
  7. Чек-лист безопасности Nuxt
  8. Терминология
  9. Ключевые тезисы для интервью
  10. Заключение
  11. Полезные ссылки

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 renderingSSR + 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.

Полезные ссылки