Что делает каждый заголовок
| Заголовок | Защищает от |
|---|---|
Content-Security-Policy | XSS, инъекций кода |
Strict-Transport-Security | Атак на понижение протокола |
X-Frame-Options | Clickjacking |
X-Content-Type-Options | MIME sniffing / XSS |
Referrer-Policy | Утечки данных через заголовок Referer |
Permissions-Policy | Нежелательного доступа к API браузера |
X-Powered-By | Раскрытия стека (нужно убрать) |
Next.js
// next.config.js
const securityHeaders = [
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'SAMEORIGIN' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
]
const nextConfig = {
poweredByHeader: false,
async headers() {
return [{ source: '/(.*)', headers: securityHeaders }]
},
}
module.exports = nextConfig
Astro
Для статических сборок (SSG) заголовки нужно выставлять на уровне сервера или CDN — Astro не может добавить HTTP-заголовки к статическим файлам. Используйте конфигурацию Nginx, Vercel или Netlify ниже.
Для SSR (с адаптером Node.js или Cloudflare) добавьте заголовки в middleware:
// src/middleware.ts
import { defineMiddleware } from 'astro:middleware'
export const onRequest = defineMiddleware(async (context, next) => {
const response = await next()
response.headers.set('X-Content-Type-Options', 'nosniff')
response.headers.set('X-Frame-Options', 'SAMEORIGIN')
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin')
response.headers.set('Strict-Transport-Security', 'max-age=63072000; includeSubDomains')
response.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()')
return response
})
Vue (Nuxt)
Вариант 1 — через routeRules в nuxt.config.ts (работает с SSR и Nitro-сервером):
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
routeRules: {
'/**': {
headers: {
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'SAMEORIGIN',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Strict-Transport-Security': 'max-age=63072000; includeSubDomains',
'Permissions-Policy': 'camera=(), microphone=(), geolocation=()',
},
},
},
},
})
Вариант 2 — через серверный middleware (более гибкий):
// server/middleware/security-headers.ts
export default defineEventHandler((event) => {
setResponseHeaders(event, {
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'SAMEORIGIN',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Strict-Transport-Security': 'max-age=63072000; includeSubDomains',
'Permissions-Policy': 'camera=(), microphone=(), geolocation=()',
})
})
Вариант 3 — модуль nuxt-security (наиболее полный, включает авто-настройку CSP):
npx nuxi module add security
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['nuxt-security'],
security: {
headers: {
xFrameOptions: 'SAMEORIGIN',
xContentTypeOptions: 'nosniff',
referrerPolicy: 'strict-origin-when-cross-origin',
strictTransportSecurity: 'max-age=63072000; includeSubDomains',
permissionsPolicy: { camera: [], microphone: [], geolocation: [] },
},
},
})
Для статических сборок (nuxt generate) используйте конфигурации Nginx, Vercel или Netlify ниже.
Angular
Angular не управляет HTTP-заголовками ответа — они всегда задаются на уровне сервера или CDN.
Angular Universal (SSR) с Express:
// server.ts
import express from 'express'
const app = express()
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff')
res.setHeader('X-Frame-Options', 'SAMEORIGIN')
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin')
res.setHeader('Strict-Transport-Security', 'max-age=63072000; includeSubDomains')
res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()')
next()
})
Для статических Angular-сборок (без SSR): используйте конфигурации Nginx, Vercel или Netlify — заголовки применяются независимо от кода Angular-приложения.
Nginx
server {
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options SAMEORIGIN always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
server_tokens off;
}
Apache / PHP / WordPress
Добавьте в .htaccess в корне сайта:
<IfModule mod_headers.c>
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
Header unset X-Powered-By
Header always unset X-Powered-By
</IfModule>
Убедитесь, что включён mod_headers:
a2enmod headers && systemctl restart apache2
Вариант через плагин WordPress: если нет доступа к .htaccess или httpd.conf, используйте плагин Headers & API Manager.
Убрать X-Powered-By через PHP:
// В начале index.php или в functions.php (WordPress)
header_remove('X-Powered-By');
Vercel
{
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "X-Frame-Options", "value": "SAMEORIGIN" },
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
{ "key": "Strict-Transport-Security", "value": "max-age=63072000; includeSubDomains" },
{ "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" }
]
}
]
}
Для Next.js на Vercel лучше использовать next.config.js — он имеет приоритет и проще поддерживается.
Netlify
# netlify.toml
[[headers]]
for = "/*"
[headers.values]
X-Content-Type-Options = "nosniff"
X-Frame-Options = "SAMEORIGIN"
Referrer-Policy = "strict-origin-when-cross-origin"
Strict-Transport-Security = "max-age=63072000; includeSubDomains"
Permissions-Policy = "camera=(), microphone=(), geolocation=()"
Tilda
Tilda не поддерживает установку кастомных HTTP-заголовков — они контролируются инфраструктурой платформы.
Единственный обходной путь — разместить экспортированный сайт (если ваш план это поддерживает) за своим Nginx или Cloudflare. Без этого проблему на Tilda не решить.
CSP — начните с report-only режима
Content-Security-Policy — самый мощный заголовок, но может сломать приложение. Начните с Report-Only: нарушения логируются в консоль, ничего не блокируется:
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;
Устраните нарушения из консоли, затем переключитесь на жёсткий режим:
Content-Security-Policy: default-src 'self'; script-src 'self'; ...
Проверка
curl -I https://yoursite.com
Или используйте securityheaders.com для полного отчёта с оценкой. После — запустите vibeblame снова.