security-headers
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSecurity Headers - Defense Against Multiple Attack Types
安全头——抵御多种攻击类型
Why Security Headers Are Critical
为什么安全头至关重要
Think of security headers as the walls and moat around your castle. Even if attackers get past the gate (your authentication), the walls (headers) prevent them from moving freely or exfiltrating data.
你可以把安全头看作是城堡周围的城墙和护城河。即便攻击者突破了城门(你的身份验证体系),城墙(安全头)也能阻止他们肆意行动或窃取数据。
The Browser Security Model
浏览器安全模型
Modern browsers have built-in security features, but they're opt-in. Without the right headers, browsers allow:
- Your site to be embedded in malicious iframes (clickjacking)
- Scripts from any origin (XSS amplification)
- Insecure HTTP connections (man-in-the-middle attacks)
- MIME type confusion (executing images as scripts)
Security headers tell the browser: "Enable all your security features for my site."
现代浏览器内置了安全特性,但这些特性是需要主动启用的。如果没有配置正确的请求头,浏览器会允许以下行为:
- 你的站点被嵌入到恶意iframe中(点击劫持)
- 任意来源的脚本加载运行(放大XSS攻击危害)
- 不安全的HTTP连接(中间人攻击)
- MIME类型混淆(将图片当作脚本执行)
安全头会告知浏览器:「为我的站点启用你所有的安全特性。」
Real-World Consequences of Missing Headers
缺失安全头的实际后果
According to a 2023 security audit of top 10,000 websites by Scott Helme, only 2.8% properly implement all recommended security headers. The remaining 97.2% are vulnerable to attacks that headers would prevent.
Magecart Attacks (2018-2020):
Hundreds of e-commerce sites were compromised by injected payment-stealing JavaScript. Content-Security-Policy headers would have prevented these scripts from executing. Sites without CSP lost millions in fraudulent transactions.
根据Scott Helme在2023年对Top 10,000网站的安全审计结果,仅有2.8%的网站正确配置了所有推荐的安全头。剩余97.2%的网站都面临可被安全头拦截的攻击风险。
Magecart攻击(2018-2020年):
数百家电商网站被注入窃取支付信息的JavaScript脚本攻破。Content-Security-Policy头本可以阻止这些脚本执行,没有配置CSP的站点因欺诈交易损失了数百万美元。
Our Security Headers Architecture
我们的安全头架构
All headers are applied automatically via on every request. You don't need to manually set them—they're already protecting you.
middleware.ts所有安全头都会通过自动应用到每一个请求上。你不需要手动设置,它们已经在为你提供防护。
middleware.tsHeaders We Apply
我们配置的安全头
- Content-Security-Policy (CSP) - Controls resource loading
- X-Frame-Options: DENY - Prevents clickjacking
- X-Content-Type-Options: nosniff - Prevents MIME confusion
- Strict-Transport-Security (HSTS) - Forces HTTPS (production only)
- X-Robots-Tag: noindex, nofollow - Protects private routes
- Content-Security-Policy (CSP) - 控制资源加载规则
- X-Frame-Options: DENY - 阻止点击劫持
- X-Content-Type-Options: nosniff - 阻止MIME混淆
- Strict-Transport-Security (HSTS) - 强制使用HTTPS(仅生产环境生效)
- X-Robots-Tag: noindex, nofollow - 保护私有路由
Header Descriptions & Implementation
安全头说明与实现
1. Content-Security-Policy (CSP)
1. Content-Security-Policy (CSP)
What it does: Controls what resources (scripts, styles, images) can load and from where.
Our configuration:
typescript
// Dynamic CSP based on environment variables
const clerkDomain = process.env.NEXT_PUBLIC_CLERK_FRONTEND_API_URL
? new URL(process.env.NEXT_PUBLIC_CLERK_FRONTEND_API_URL).origin
: '';
const convexDomain = process.env.NEXT_PUBLIC_CONVEX_URL
? new URL(process.env.NEXT_PUBLIC_CONVEX_URL).origin
: '';
const csp = [
"default-src 'self'",
`script-src 'self' 'unsafe-inline' 'unsafe-eval' ${clerkDomain} https://js.stripe.com`,
`style-src 'self' 'unsafe-inline' ${clerkDomain}`,
`connect-src 'self' ${clerkDomain} ${convexDomain} https://api.stripe.com`,
`frame-src 'self' ${clerkDomain} https://js.stripe.com https://hooks.stripe.com`,
"img-src 'self' data: https: blob:",
"font-src 'self' data:",
"object-src 'none'",
"base-uri 'self'",
"form-action 'self'",
"frame-ancestors 'none'"
].join('; ');
response.headers.set('Content-Security-Policy', csp);What This Means:
- Scripts: Only from our domain, Clerk, and Stripe
- Styles: Only from our domain and Clerk
- Connections: Only to Clerk, Convex, Stripe APIs
- Frames: Only Clerk and Stripe
- Images: Any HTTPS source (for user avatars, external images)
Why Dynamic Configuration:
typescript
const clerkDomain = process.env.NEXT_PUBLIC_CLERK_FRONTEND_API_URL
? new URL(process.env.NEXT_PUBLIC_CLERK_FRONTEND_API_URL).origin
: ''We don't hardcode Clerk's domain. It comes from environment variables. This means:
- ✅ Different domains for dev/staging/prod (automatic)
- ✅ Easy to change without code modifications
- ✅ No security secrets in codebase
Trade-off: We allow and for scripts. This is required for Next.js and Clerk to function. We mitigate this risk through input sanitization and validation.
unsafe-inlineunsafe-evalPrevent Data Exfiltration:
The directive prevents malicious scripts from sending data to unauthorized domains. Even if XSS bypasses sanitization, the browser blocks unauthorized network requests.
connect-src作用: 控制可以加载的资源类型(脚本、样式、图片等)以及允许的来源。
我们的配置:
typescript
// Dynamic CSP based on environment variables
const clerkDomain = process.env.NEXT_PUBLIC_CLERK_FRONTEND_API_URL
? new URL(process.env.NEXT_PUBLIC_CLERK_FRONTEND_API_URL).origin
: '';
const convexDomain = process.env.NEXT_PUBLIC_CONVEX_URL
? new URL(process.env.NEXT_PUBLIC_CONVEX_URL).origin
: '';
const csp = [
"default-src 'self'",
`script-src 'self' 'unsafe-inline' 'unsafe-eval' ${clerkDomain} https://js.stripe.com`,
`style-src 'self' 'unsafe-inline' ${clerkDomain}`,
`connect-src 'self' ${clerkDomain} ${convexDomain} https://api.stripe.com`,
`frame-src 'self' ${clerkDomain} https://js.stripe.com https://hooks.stripe.com`,
"img-src 'self' data: https: blob:",
"font-src 'self' data:",
"object-src 'none'",
"base-uri 'self'",
"form-action 'self'",
"frame-ancestors 'none'"
].join('; ');
response.headers.set('Content-Security-Policy', csp);配置含义:
- 脚本: 仅允许来自本站域名、Clerk和Stripe的脚本加载
- 样式: 仅允许来自本站域名和Clerk的样式加载
- 网络请求: 仅允许向Clerk、Convex、Stripe API发起请求
- 框架: 仅允许加载Clerk和Stripe的iframe
- 图片: 允许任意HTTPS来源的图片(用于用户头像、外部图片等场景)
为什么用动态配置:
typescript
const clerkDomain = process.env.NEXT_PUBLIC_CLERK_FRONTEND_API_URL
? new URL(process.env.NEXT_PUBLIC_CLERK_FRONTEND_API_URL).origin
: ''我们没有硬编码Clerk的域名,而是从环境变量中读取,这意味着:
- ✅ 开发/预发/生产环境自动适配不同域名
- ✅ 无需修改代码即可轻松变更域名
- ✅ 代码库中不会留存安全敏感信息
权衡: 我们允许脚本使用和,这是Next.js和Clerk正常运行的必要配置。我们通过输入 sanitization 和校验来降低该配置带来的风险。
unsafe-inlineunsafe-eval阻止数据窃取:
指令可以阻止恶意脚本向未授权的域名发送数据。即便XSS绕过了输入校验,浏览器也会拦截未授权的网络请求。
connect-src2. X-Frame-Options: DENY
2. X-Frame-Options: DENY
What it prevents: Clickjacking attacks where your site is embedded in invisible iframe on attacker's site.
Attack scenario:
Attacker embeds your "Delete Account" button in an invisible iframe overlay on a game site. Users think they're clicking "Play Game" but actually click "Delete Account."
Our protection:
typescript
response.headers.set('X-Frame-Options', 'DENY');DENY means browsers refuse to embed our site in ANY iframe, even our own. Maximum security.
Alternative values:
- - No iframes at all (most secure, what we use)
DENY - - Only our own site can iframe us
SAMEORIGIN - - Deprecated, don't use
ALLOW-FROM uri
防护目标: 点击劫持攻击,这类攻击会将你的站点嵌入到攻击者站点的不可见iframe中。
攻击场景:
攻击者将你的「删除账户」按钮嵌入到游戏站点的不可见iframe浮层中,用户以为自己点击的是「开始游戏」,实际点击的是「删除账户」。
我们的防护方案:
typescript
response.headers.set('X-Frame-Options', 'DENY');DENY表示浏览器会拒绝将我们的站点嵌入到任何iframe中,即便是我们自己的站点也不行,实现最高级别的安全防护。
可选值说明:
- - 完全禁止iframe嵌入(最安全,我们使用的配置)
DENY - - 仅允许本站点iframe嵌入
SAMEORIGIN - - 已废弃,不要使用
ALLOW-FROM uri
3. X-Content-Type-Options: nosniff
3. X-Content-Type-Options: nosniff
What it prevents: MIME confusion attacks where browsers execute images as JavaScript.
Attack scenario:
Attacker uploads file "avatar.jpg" that contains JavaScript. Old browsers try to be "helpful" and "sniff" the file type, detecting JavaScript, and execute it.
Our protection:
typescript
response.headers.set('X-Content-Type-Options', 'nosniff');nosniffWhy This Matters:
Without this header, an attacker could:
- Upload "image.jpg" containing
<script>evil()</script> - Link to it:
<img src="/uploads/image.jpg"> - Browser sniffs file, detects script, executes it
- XSS achieved despite upload validation
With , browser sees Content-Type: image/jpeg and refuses to execute as script.
nosniff防护目标: MIME混淆攻击,这类攻击会让浏览器将图片当作JavaScript执行。
攻击场景:
攻击者上传包含JavaScript代码的文件「avatar.jpg」,旧版本浏览器会自作主张地「嗅探」文件类型,识别出JavaScript代码并执行。
我们的防护方案:
typescript
response.headers.set('X-Content-Type-Options', 'nosniff');nosniff重要性:
如果没有这个头,攻击者可以:
- 上传包含的「image.jpg」文件
<script>evil()</script> - 插入引用链接:
<img src="/uploads/image.jpg"> - 浏览器嗅探文件识别出脚本并执行
- 即便上传校验通过也会出现XSS漏洞
配置后,浏览器识别到Content-Type为image/jpeg时,会拒绝将其作为脚本执行。
nosniff4. Strict-Transport-Security (HSTS) - Production Only
4. Strict-Transport-Security (HSTS) - 仅生产环境生效
What it prevents: SSL stripping attacks where man-in-the-middle downgrades HTTPS to HTTP.
Attack scenario:
User types "yourapp.com" (no https://). Browser initially requests HTTP. Attacker intercepts, serves fake HTTP version, steals credentials.
Our protection:
typescript
if (process.env.NODE_ENV === 'production') {
response.headers.set(
'Strict-Transport-Security',
'max-age=31536000; includeSubDomains'
);
}Configuration breakdown:
- - 1 year duration
max-age=31536000 - - Applies to all subdomains
includeSubDomains - Once set, browsers ONLY use HTTPS for your domain
Why production only:
In development, you're on localhost (HTTP). HSTS would break local development. Our middleware detects environment and enables HSTS only in production.
Important: Once HSTS is set for a domain, browsers remember it. If you need to remove it, you must:
- Set
max-age=0 - Wait for all users' browsers to receive the new header
- This can take months if max-age was long
防护目标: SSL剥离攻击,这类中间人攻击会将HTTPS连接降级为HTTP连接。
攻击场景:
用户输入「yourapp.com」(不带https://),浏览器首先发起HTTP请求,攻击者拦截请求后返回伪造的HTTP版本站点,窃取用户凭证。
我们的防护方案:
typescript
if (process.env.NODE_ENV === 'production') {
response.headers.set(
'Strict-Transport-Security',
'max-age=31536000; includeSubDomains'
);
}配置解析:
- - 有效期为1年
max-age=31536000 - - 规则对所有子域名生效
includeSubDomains - 配置生效后,浏览器只会通过HTTPS访问你的域名
为什么仅生产环境生效:
开发环境你使用的是localhost(HTTP协议),HSTS会破坏本地开发流程。我们的中间件会自动识别环境,仅在生产环境启用HSTS。
注意: 一旦为某个域名设置了HSTS,浏览器会记住该配置。如果你需要移除HSTS,必须:
- 设置
max-age=0 - 等待所有用户的浏览器接收到新的头配置
- 如果之前的max-age设置得很长,这个过程可能需要数月
5. X-Robots-Tag: noindex, nofollow (Protected Routes)
5. X-Robots-Tag: noindex, nofollow(受保护路由)
What it prevents: Search engines indexing private content.
Why it matters:
You don't want showing up in Google search results.
/dashboard/payment-detailsOur implementation:
typescript
if (req.nextUrl.pathname.startsWith('/dashboard')) {
response.headers.set('X-Robots-Tag', 'noindex, nofollow');
}Applied to: routes only (public pages should be indexed)
/dashboard/*What this tells search engines:
- - Don't add this page to search results
noindex - - Don't follow links on this page
nofollow
防护目标: 搜索引擎索引私有内容。
重要性:
你肯定不希望这类页面出现在谷歌搜索结果中。
/dashboard/payment-details我们的实现:
typescript
if (req.nextUrl.pathname.startsWith('/dashboard')) {
response.headers.set('X-Robots-Tag', 'noindex, nofollow');
}生效范围: 仅路由(公开页面应该被搜索引擎索引)
/dashboard/*告知搜索引擎的规则:
- - 不要将该页面加入搜索结果
noindex - - 不要爬取该页面上的链接
nofollow
Implementation in Middleware
中间件实现
middleware.ts Pattern
middleware.ts 模板
typescript
import { clerkMiddleware } from '@clerk/nextjs/server';
import { NextResponse } from 'next/server';
export default clerkMiddleware((auth, req) => {
const response = NextResponse.next();
// Get dynamic domains from environment
const clerkDomain = process.env.NEXT_PUBLIC_CLERK_FRONTEND_API_URL
? new URL(process.env.NEXT_PUBLIC_CLERK_FRONTEND_API_URL).origin
: '';
const convexDomain = process.env.NEXT_PUBLIC_CONVEX_URL
? new URL(process.env.NEXT_PUBLIC_CONVEX_URL).origin
: '';
// Build CSP dynamically
const csp = [
"default-src 'self'",
`script-src 'self' 'unsafe-inline' 'unsafe-eval' ${clerkDomain} https://js.stripe.com`,
`style-src 'self' 'unsafe-inline' ${clerkDomain}`,
`connect-src 'self' ${clerkDomain} ${convexDomain} https://api.stripe.com`,
`frame-src 'self' ${clerkDomain} https://js.stripe.com https://hooks.stripe.com`,
"img-src 'self' data: https: blob:",
"font-src 'self' data:",
"object-src 'none'",
"base-uri 'self'",
"form-action 'self'",
"frame-ancestors 'none'"
].join('; ');
// Apply security headers
response.headers.set('Content-Security-Policy', csp);
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('X-Frame-Options', 'DENY');
// HSTS only in production
if (process.env.NODE_ENV === 'production') {
response.headers.set(
'Strict-Transport-Security',
'max-age=31536000; includeSubDomains'
);
}
// Prevent indexing of protected routes
if (req.nextUrl.pathname.startsWith('/dashboard')) {
response.headers.set('X-Robots-Tag', 'noindex, nofollow');
}
return response;
});
export const config = {
matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
};typescript
import { clerkMiddleware } from '@clerk/nextjs/server';
import { NextResponse } from 'next/server';
export default clerkMiddleware((auth, req) => {
const response = NextResponse.next();
// Get dynamic domains from environment
const clerkDomain = process.env.NEXT_PUBLIC_CLERK_FRONTEND_API_URL
? new URL(process.env.NEXT_PUBLIC_CLERK_FRONTEND_API_URL).origin
: '';
const convexDomain = process.env.NEXT_PUBLIC_CONVEX_URL
? new URL(process.env.NEXT_PUBLIC_CONVEX_URL).origin
: '';
// Build CSP dynamically
const csp = [
"default-src 'self'",
`script-src 'self' 'unsafe-inline' 'unsafe-eval' ${clerkDomain} https://js.stripe.com`,
`style-src 'self' 'unsafe-inline' ${clerkDomain}`,
`connect-src 'self' ${clerkDomain} ${convexDomain} https://api.stripe.com`,
`frame-src 'self' ${clerkDomain} https://js.stripe.com https://hooks.stripe.com`,
"img-src 'self' data: https: blob:",
"font-src 'self' data:",
"object-src 'none'",
"base-uri 'self'",
"form-action 'self'",
"frame-ancestors 'none'"
].join('; ');
// Apply security headers
response.headers.set('Content-Security-Policy', csp);
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('X-Frame-Options', 'DENY');
// HSTS only in production
if (process.env.NODE_ENV === 'production') {
response.headers.set(
'Strict-Transport-Security',
'max-age=31536000; includeSubDomains'
);
}
// Prevent indexing of protected routes
if (req.nextUrl.pathname.startsWith('/dashboard')) {
response.headers.set('X-Robots-Tag', 'noindex, nofollow');
}
return response;
});
export const config = {
matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
};Testing Security Headers
安全头测试
Test All Headers
测试所有安全头
bash
curl -I http://localhost:3000bash
curl -I http://localhost:3000Expected headers:
Expected headers:
X-Frame-Options: DENY
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
X-Content-Type-Options: nosniff
Content-Security-Policy: default-src 'self'; ...
Content-Security-Policy: default-src 'self'; ...
undefinedundefinedTest Production HSTS
测试生产环境HSTS
bash
undefinedbash
undefinedIn production
In production
curl -I https://yourapp.com
curl -I https://yourapp.com
Should include:
Should include:
Strict-Transport-Security: max-age=31536000; includeSubDomains
Strict-Transport-Security: max-age=31536000; includeSubDomains
undefinedundefinedTest Protected Route Headers
测试受保护路由的头
bash
curl -I http://localhost:3000/dashboardbash
curl -I http://localhost:3000/dashboardShould include:
Should include:
X-Robots-Tag: noindex, nofollow
X-Robots-Tag: noindex, nofollow
undefinedundefinedOnline Header Testing
在线安全头测试工具
Use these online tools to test your deployed site:
- Security Headers: https://securityheaders.com/
- Mozilla Observatory: https://observatory.mozilla.org/
- SSL Labs: https://www.ssllabs.com/ssltest/
你可以使用以下在线工具测试已部署站点的安全头:
- Security Headers: https://securityheaders.com/
- Mozilla Observatory: https://observatory.mozilla.org/
- SSL Labs: https://www.ssllabs.com/ssltest/
Customizing CSP for New Integrations
为新集成自定义CSP
When adding new third-party services:
当你接入新的第三方服务时:
Example: Adding Google Analytics
示例:接入谷歌分析
typescript
// middleware.ts
// Add Google Analytics domain to CSP
const csp = [
"default-src 'self'",
`script-src 'self' 'unsafe-inline' 'unsafe-eval' ${clerkDomain} https://js.stripe.com https://www.googletagmanager.com https://www.google-analytics.com`,
`style-src 'self' 'unsafe-inline' ${clerkDomain}`,
`connect-src 'self' ${clerkDomain} ${convexDomain} https://api.stripe.com https://www.google-analytics.com https://analytics.google.com`,
// ... rest of CSP
].join('; ');typescript
// middleware.ts
// Add Google Analytics domain to CSP
const csp = [
"default-src 'self'",
`script-src 'self' 'unsafe-inline' 'unsafe-eval' ${clerkDomain} https://js.stripe.com https://www.googletagmanager.com https://www.google-analytics.com`,
`style-src 'self' 'unsafe-inline' ${clerkDomain}`,
`connect-src 'self' ${clerkDomain} ${convexDomain} https://api.stripe.com https://www.google-analytics.com https://analytics.google.com`,
// ... rest of CSP
].join('; ');Example: Adding Custom CDN
示例:接入自定义CDN
typescript
const cdnDomain = process.env.NEXT_PUBLIC_CDN_URL
? new URL(process.env.NEXT_PUBLIC_CDN_URL).origin
: '';
const csp = [
"default-src 'self'",
`script-src 'self' ${cdnDomain}`,
`style-src 'self' ${cdnDomain}`,
`img-src 'self' ${cdnDomain} https:`,
// ... rest of CSP
].join('; ');typescript
const cdnDomain = process.env.NEXT_PUBLIC_CDN_URL
? new URL(process.env.NEXT_PUBLIC_CDN_URL).origin
: '';
const csp = [
"default-src 'self'",
`script-src 'self' ${cdnDomain}`,
`style-src 'self' ${cdnDomain}`,
`img-src 'self' ${cdnDomain} https:`,
// ... rest of CSP
].join('; ');Common CSP Issues & Solutions
常见CSP问题与解决方案
Issue 1: Inline Scripts Blocked
问题1:内联脚本被拦截
Symptom: Scripts in tags don't execute
<script>Solution: Next.js requires and . Already configured in our CSP.
unsafe-inlineunsafe-evalBetter alternative: Use nonces (requires SSR changes):
typescript
const nonce = crypto.randomBytes(16).toString('base64');
script-src 'self' 'nonce-${nonce}'症状: 标签中的脚本无法执行
<script>解决方案: Next.js需要和配置,我们的CSP已经默认配置了这两个选项。
unsafe-inlineunsafe-eval更优方案: 使用nonce(需要修改SSR配置):
typescript
const nonce = crypto.randomBytes(16).toString('base64');
script-src 'self' 'nonce-${nonce}'Issue 2: External Images Not Loading
问题2:外部图片无法加载
Symptom: User avatars from Gravatar/etc don't show
Current solution: allows all HTTPS images
img-src 'self' data: https: blob:Stricter alternative:
typescript
`img-src 'self' data: https://gravatar.com https://images.yourapp.com`症状: Gravatar等来源的用户头像无法显示
当前解决方案: 配置允许所有HTTPS来源的图片加载
img-src 'self' data: https: blob:更严格的替代方案:
typescript
`img-src 'self' data: https://gravatar.com https://images.yourapp.com`Issue 3: WebSocket Connections Failing
问题3:WebSocket连接失败
Symptom: Convex real-time updates don't work
Solution: Ensure includes Convex domain:
connect-srctypescript
`connect-src 'self' ${convexDomain}`症状: Convex实时更新不生效
解决方案: 确保包含Convex域名:
connect-srctypescript
`connect-src 'self' ${convexDomain}`What Security Headers Prevent
安全头的防护范围
✅ Clickjacking (X-Frame-Options)
✅ XSS amplification (CSP)
✅ MIME confusion (X-Content-Type-Options)
✅ SSL stripping (HSTS)
✅ Search engine exposure of private data (X-Robots-Tag)
✅ Data exfiltration (CSP connect-src)
✅ Unauthorized resource loading (CSP default-src)
✅ 点击劫持(X-Frame-Options)
✅ XSS攻击放大(CSP)
✅ MIME混淆(X-Content-Type-Options)
✅ SSL剥离(HSTS)
✅ 私有数据被搜索引擎收录(X-Robots-Tag)
✅ 数据窃取(CSP connect-src)
✅ 未授权资源加载(CSP default-src)
Common Mistakes to Avoid
需要避免的常见错误
❌ DON'T hardcode domains in CSP - Use environment variables
❌ DON'T enable HSTS in development - Breaks localhost
❌ DON'T use X-Frame-Options: ALLOW-FROM - Deprecated
❌ DON'T forget to test headers after deployment
❌ DON'T set overly permissive CSP (like wildcards)
*✅ DO use dynamic CSP with environment variables
✅ DO test headers with online tools after deployment
✅ DO update CSP when adding new third-party services
✅ DO keep HSTS production-only
✅ DO protect dashboard routes from indexing
❌ 不要在CSP中硬编码域名 - 使用环境变量
❌ 不要在开发环境启用HSTS - 会破坏localhost访问
❌ 不要使用X-Frame-Options: ALLOW-FROM - 已废弃
❌ 部署后不要忘记测试安全头
❌ 不要配置过于宽松的CSP(比如使用通配符)
*✅ 务必使用基于环境变量的动态CSP
✅ 部署后务必使用在线工具测试安全头
✅ 接入新的第三方服务时务必更新CSP
✅ 务必仅在生产环境启用HSTS
✅ 务必保护dashboard路由不被搜索引擎索引
References
参考资料
- Mozilla Security Headers Guide: https://infosec.mozilla.org/guidelines/web_security
- OWASP Secure Headers Project: https://owasp.org/www-project-secure-headers/
- Content Security Policy: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
- HSTS Specification: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
- Security Headers Checker: https://securityheaders.com/
- Mozilla安全头指南: https://infosec.mozilla.org/guidelines/web_security
- OWASP安全头项目: https://owasp.org/www-project-secure-headers/
- Content Security Policy说明: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
- HSTS规范: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
- 安全头检测工具: https://securityheaders.com/
Next Steps
后续步骤
- For XSS prevention: Use skill
input-validation - For clickjacking tests: Use skill
security-testing - Headers are automatic - check for implementation
middleware.ts:1 - For adding new integrations: Update CSP in middleware.ts
- XSS防护:使用技能
input-validation - 点击劫持测试:使用技能
security-testing - 安全头已自动配置:查看了解实现
middleware.ts:1 - 接入新集成:更新middleware.ts中的CSP配置