Content Security Policy для ShopStory
Content Security Policy (CSP) — это дополнительный уровень безопасности, который помогает предотвратить XSS, clickjacking и другие атаки на ваш сайт при встраивании внешнего контента, включая ShopStory плееры и виджеты.
🎯 Быстрый старт: Минимальная CSP
Если вы встраиваете ShopStory плеер через iframe:
HTML meta tag
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
frame-src https://watch.shopstory.live;
connect-src https://app.shopstory.live;
img-src https://cdn.shopstory.live https:;
style-src 'self' 'unsafe-inline';
">
Nginx конфигурация
/etc/nginx/sites-available/yoursite.conf
server {
listen 443 ssl http2;
server_name yoursite.com;
# Content Security Policy
add_header Content-Security-Policy "
default-src 'self';
frame-src https://watch.shopstory.live;
connect-src 'self' https://app.shopstory.live;
img-src 'self' https://cdn.shopstory.live https: data:;
style-src 'self' 'unsafe-inline';
script-src 'self' 'unsafe-inline';
" always;
# Другие security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location / {
root /var/www/yoursite;
try_files $uri $uri/ /index.html;
}
}
📋 Полная CSP конфигурация
Для сайтов с ShopStory интеграцией
Продвинутая Nginx CSP конфигурация
# Определяем CSP политику как переменную для удобства
map $request_uri $csp_policy {
default "
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
img-src 'self' https://cdn.shopstory.live https: data:;
media-src 'self' https://cdn.shopstory.live;
connect-src 'self' https://app.shopstory.live https://www.google-analytics.com;
frame-src 'self' https://watch.shopstory.live;
frame-ancestors 'self';
base-uri 'self';
form-action 'self';
upgrade-insecure-requests;
";
}
server {
listen 443 ssl http2;
server_name yoursite.com;
# SSL configuration
ssl_certificate /etc/letsencrypt/live/yoursite.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yoursite.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# CSP Header
add_header Content-Security-Policy $csp_policy always;
# Дополнительные security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
location / {
root /var/www/yoursite;
try_files $uri $uri/ /index.html;
# Кэширование статики
location ~* \.(jpg|jpeg|png|gif|ico|svg|webp)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
}
🔧 Директивы CSP для ShopStory
Обязательные директивы
| Директива | Значение | Назначение |
|---|---|---|
frame-src | https://watch.shopstory.live | Разрешает встраивание iframe с плеером |
connect-src | https://app.shopstory.live | Разрешает AJAX запросы к API |
img-src | https://cdn.shopstory.live https: | Разрешает загрузку изображений |
Опциональные (для расширенной интеграции)
| Директива | Значение | Назначение |
|---|---|---|
media-src | https://cdn.shopstory.live | Для прямой загрузки видео (если используете) |
worker-src | 'self' blob: | Для Service Workers и PWA |
manifest-src | 'self' | Для Web App Manifest |
🌐 Apache конфигурация
/etc/apache2/sites-available/yoursite.conf
<VirtualHost *:443>
ServerName yoursite.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/yoursite.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/yoursite.com/privkey.pem
# Content Security Policy
Header always set Content-Security-Policy "\
default-src 'self'; \
script-src 'self' 'unsafe-inline'; \
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; \
font-src 'self' https://fonts.gstatic.com; \
img-src 'self' https://cdn.shopstory.live https: data:; \
connect-src 'self' https://app.shopstory.live; \
frame-src 'self' https://watch.shopstory.live; \
frame-ancestors 'self'; \
"
# Дополнительные security headers
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set X-XSS-Protection "1; mode=block"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
DocumentRoot /var/www/yoursite
<Directory /var/www/yoursite>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
💻 Клиентская CSP (JavaScript)
Динамическое добавление CSP через meta tag
Добавление CSP в SPA приложениях
/**
* Добавить CSP meta tag для ShopStory интеграции
*/
function setupShopStoryCSP() {
const existingCSP = document.querySelector('meta[http-equiv="Content-Security-Policy"]');
// Если CSP уже есть, обновляем
if (existingCSP) {
console.warn('CSP уже установлен, обновите вручную');
return;
}
const cspMeta = document.createElement('meta');
cspMeta.httpEquiv = 'Content-Security-Policy';
cspMeta.content = `
default-src 'self';
frame-src https://watch.shopstory.live;
connect-src 'self' https://app.shopstory.live;
img-src 'self' https://cdn.shopstory.live https: data:;
style-src 'self' 'unsafe-inline';
script-src 'self' 'unsafe-inline';
`.replace(/\s+/g, ' ').trim();
document.head.appendChild(cspMeta);
console.log('✅ CSP для ShopStory установлен');
}
// Вызываем при загрузке приложения
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', setupShopStoryCSP);
} else {
setupShopStoryCSP();
}
React приложения
CSP для Create React App
// public/index.html
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- Content Security Policy -->
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline';
img-src 'self' https://cdn.shopstory.live https: data:;
font-src 'self' data:;
connect-src 'self' https://app.shopstory.live;
frame-src https://watch.shopstory.live;
worker-src 'self' blob:;
">
<title>Your App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
🧪 Режим report-only (для тестирования)
Перед применением CSP в продакшене, протестируйте в режиме report-only:
CSP в режиме мониторинга
server {
listen 443 ssl http2;
server_name yoursite.com;
# Report-Only режим - не блокирует, только логирует
add_header Content-Security-Policy-Report-Only "
default-src 'self';
frame-src https://watch.shopstory.live;
connect-src 'self' https://app.shopstory.live;
report-uri /csp-violation-report;
" always;
# Endpoint для получения CSP нарушений
location = /csp-violation-report {
access_log /var/log/nginx/csp_violations.log;
return 204;
}
}
Анализ CSP нарушений
Мониторинг CSP нарушений в браузере
// Слушаем CSP нарушения
document.addEventListener('securitypolicyviolation', (e) => {
console.warn('🚨 CSP Violation:', {
blockedURI: e.blockedURI,
violatedDirective: e.violatedDirective,
originalPolicy: e.originalPolicy,
sourceFile: e.sourceFile,
lineNumber: e.lineNumber
});
// Отправляем в систему мониторинга
fetch('/api/log-csp-violation', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
blockedURI: e.blockedURI,
directive: e.violatedDirective,
timestamp: new Date().toISOString()
})
}).catch(err => console.error('Failed to log CSP violation:', err));
});
🔒 CSP для разных окружений
Development (разработка)
<!-- Разрешаем больше для удобства разработки -->
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline';
connect-src 'self' https://app.shopstory.live http://localhost:* ws://localhost:*;
frame-src https://watch.shopstory.live;
img-src 'self' https://cdn.shopstory.live https: data:;
">
Staging (тестирование)
<!-- Строже чем dev, но с report-only -->
<meta http-equiv="Content-Security-Policy-Report-Only" content="
default-src 'self';
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
connect-src 'self' https://app.shopstory.live;
frame-src https://watch.shopstory.live;
img-src 'self' https://cdn.shopstory.live https: data:;
report-uri /csp-report;
">
Production (продакшн)
<!-- Максимально строгая политика -->
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self';
style-src 'self';
connect-src 'self' https://app.shopstory.live;
frame-src https://watch.shopstory.live;
img-src 'self' https://cdn.shopstory.live https:;
frame-ancestors 'self';
base-uri 'self';
form-action 'self';
upgrade-insecure-requests;
">
🎨 Next.js конфигурация
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'Content-Security-Policy',
value: `
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline';
img-src 'self' https://cdn.shopstory.live https: data:;
font-src 'self' data:;
connect-src 'self' https://app.shopstory.live;
frame-src https://watch.shopstory.live;
worker-src 'self' blob:;
child-src 'self' blob:;
`.replace(/\s{2,}/g, ' ').trim()
},
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN'
},
{
key: 'X-Content-Type-Options',
value: 'nosniff'
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin'
},
{
key: 'Permissions-Policy',
value: 'geolocation=(), microphone=(), camera=()'
}
]
}
];
}
};
module.exports = nextConfig;
⚡ Vercel конфигурация
vercel.json
{
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "Content-Security-Policy",
"value": "default-src 'self'; frame-src https://watch.shopstory.live; connect-src 'self' https://app.shopstory.live; img-src 'self' https://cdn.shopstory.live https: data:; style-src 'self' 'unsafe-inline';"
},
{
"key": "X-Frame-Options",
"value": "SAMEORIGIN"
},
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "Strict-Transport-Security",
"value": "max-age=31536000; includeSubDomains"
}
]
}
]
}
🚀 Netlify конфигурация
netlify.toml
[[headers]]
for = "/*"
[headers.values]
Content-Security-Policy = '''
default-src 'self';
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' https://cdn.shopstory.live https: data:;
connect-src 'self' https://app.shopstory.live;
frame-src https://watch.shopstory.live;
font-src 'self' data:;
'''
X-Frame-Options = "SAMEORIGIN"
X-Content-Type-Options = "nosniff"
X-XSS-Protection = "1; mode=block"
Referrer-Policy = "strict-origin-when-cross-origin"
Strict-Transport-Security = "max-age=31536000; includeSubDomains; preload"