用 Claude Code 配置 Web 安全响应头:CSP、nonce、HSTS 与广告兼容实战
用 Claude Code 设计 CSP、nonce、HSTS、frame-ancestors,并在 Next.js、Astro、Express、Cloudflare 中落地验证。
Web 安全响应头看起来只是几行 HTTP header,但它们决定浏览器能不能执行脚本、页面能不能被别人放进 iframe、跳转时会不会泄露完整来源地址,以及摄像头、麦克风、定位等浏览器能力是否可以被页面使用。对于带登录、后台、广告、统计、图片 CDN 的站点,这些设置不是“锦上添花”,而是上线前必须检查的基础安全层。
Claude Code 很适合处理这类配置,因为它能读项目结构、找脚本来源、改框架配置、补验证脚本。但如果只说“帮我修 CSP 报错”,它也可能给出过宽的策略,例如 script-src * 'unsafe-inline' 'unsafe-eval'。这样的配置能让控制台安静下来,却把 CSP 的防护价值一起关掉。
本文给出 2026 年 6 月仍然合理的实践路线:先盘点资源,再用 Content-Security-Policy-Report-Only 观察,最后再启用正式 CSP。内容覆盖 CSP、nonce、HSTS、X-Frame-Options、frame-ancestors、Referrer-Policy、Permissions-Policy,并提供 Next.js、Astro、Express、Cloudflare Pages 的可复制配置。Claude Code 的整体安全使用方式,可以搭配阅读 Claude Code 安全最佳实践 和 Claude Code 安全审计。
建议同时打开官方文档核对:MDN Content-Security-Policy、Next.js CSP guide、MDN Strict-Transport-Security、hstspreload.org、Cloudflare Pages Headers、Helmet、Google Tag Manager CSP 和 AdSense CSP 指南。
先让 Claude Code 做资源盘点
安全响应头不是从复制一段配置开始,而是从“站点实际加载了什么”开始。把下面的提示词交给 Claude Code,可以避免它直接给出宽泛 allowlist。
请审查这个仓库并设计 Web 安全响应头。
要求:
- 先列出 script、style、image、font、frame、connect 的外部来源
- CSP 先使用 Report-Only,不要直接强制拦截
- 避免 * 和长期 unsafe-inline
- 如果 Next.js 需要 nonce,说明对动态渲染和缓存的影响
- 检查 Google Analytics、GTM、AdSense、图片 CDN、YouTube iframe
- 给出 curl、Security Headers、CSP Evaluator 的验证步骤
尤其要让它分清几个容易混淆的 directive。frame-src 表示“当前页面可以嵌入哪些 iframe”。frame-ancestors 表示“谁可以把当前页面嵌进 iframe”。Analytics 常见问题通常在 connect-src,图片 CDN 通常在 img-src,点击劫持防护主要看 frame-ancestors。
| 响应头 | 建议起点 | 注意点 |
|---|---|---|
Content-Security-Policy | 先用 Content-Security-Policy-Report-Only | 不要用 * 掩盖问题 |
Strict-Transport-Security | 先用 max-age=300; includeSubDomains | preload 必须确认所有子域 HTTPS 后再考虑 |
X-Frame-Options | DENY 或 SAMEORIGIN | 现代浏览器优先看 CSP 的 frame-ancestors |
Referrer-Policy | strict-origin-when-cross-origin | URL 中不要放个人信息或 token |
Permissions-Policy | 禁用不用的能力 | 只给真正需要的页面开放 |
X-Content-Type-Options | nosniff | 成本低,通常全站设置 |
CSP 应该先观察,再收紧
CSP 的正确上线流程是观察、分类、收紧,而不是一次性写死。
flowchart LR
A["盘点外部资源"] --> B["发送 Report-Only CSP"]
B --> C["收集控制台和 report endpoint 日志"]
C --> D["区分广告、统计、CDN、iframe 和噪声"]
D --> E["切换到 nonce 或 hash 为核心的正式 CSP"]
E --> F["用 Security Headers 和 CSP Evaluator 检查"]
不要把 report 中出现的域名全部加入 CSP。浏览器插件、公司代理、旧标签、恶意探测都可能产生报告。Claude Code 可以帮助分类,但最终要由你判断该资源是不是产品真正需要的依赖。
Next.js 中的 nonce 配置
Next.js 的 CSP 比静态站点更复杂。当前 Next.js App Router 文档使用 proxy.ts 为每个请求生成 nonce。老项目可能还在使用 middleware.ts,思路类似:生成不可预测的随机值,把它写进请求 header,再把同一个值放入 CSP。
nonce 的代价是动态渲染。每次响应都要有不同 nonce 时,页面不能简单复用同一份静态 HTML。因此,公开博客、文档页、静态落地页可以优先考虑 hash CSP 或把内联脚本移到外部文件;登录后应用、支付页、后台管理页更适合 nonce。
// proxy.ts
import { NextRequest, NextResponse } from "next/server";
export function proxy(request: NextRequest) {
const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
const isDev = process.env.NODE_ENV !== "production";
const csp = [
"default-src 'self'",
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic' ${isDev ? "'unsafe-eval'" : ""} https: http:`,
`style-src 'self' ${isDev ? "'unsafe-inline'" : `'nonce-${nonce}'`} https://fonts.googleapis.com`,
"font-src 'self' https://fonts.gstatic.com",
"img-src 'self' data: blob: https:",
"connect-src 'self' https://www.google-analytics.com https://analytics.google.com",
"frame-src 'self' https://www.youtube-nocookie.com",
"object-src 'none'",
"base-uri 'self'",
"form-action 'self'",
"frame-ancestors 'none'",
"upgrade-insecure-requests",
"report-uri /api/csp-report",
].join("; ").replace(/\s{2,}/g, " ").trim();
const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-nonce", nonce);
const response = NextResponse.next({ request: { headers: requestHeaders } });
response.headers.set("Content-Security-Policy", csp);
response.headers.set("X-Frame-Options", "DENY");
response.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
response.headers.set("Permissions-Policy", "camera=(), microphone=(), geolocation=(), payment=(self)");
response.headers.set("Strict-Transport-Security", "max-age=300; includeSubDomains");
response.headers.set("X-Content-Type-Options", "nosniff");
return response;
}
export const config = {
matcher: ["/((?!api/csp-report|_next/static|_next/image|favicon.ico).*)"],
};
如果使用 Google Tag Manager,需要把 nonce 传给组件或脚本。GTM 官方建议使用 nonce,因为容器启动代码本身是内联 JavaScript。AdSense 也明确提醒:如果启用 CSP,必须按官方方式配置,否则可能影响广告投放。
接收 CSP report
Report-Only 的价值在于你可以先看到问题,不会马上破坏广告、统计、登录或支付。Next.js Route Handler 的最小实现如下:
// app/api/csp-report/route.ts
import { NextRequest, NextResponse } from "next/server";
export async function POST(request: NextRequest) {
const contentType = request.headers.get("content-type") ?? "";
const body = await request.text();
const isReport =
contentType.includes("application/csp-report") ||
contentType.includes("application/reports+json") ||
body.includes("violated-directive");
if (!isReport) {
return NextResponse.json({ ok: false }, { status: 415 });
}
console.warn("csp-report", body.slice(0, 4000));
return new NextResponse(null, { status: 204 });
}
正式环境不要无脑保存完整 URL。只记录页面、违反的 directive、被拦截的 URI、浏览器、时间和次数即可。report-uri 虽然较旧,但兼容性仍然有价值;report-to 可以作为未来增强,不建议作为唯一通道。
Astro、Express、Cloudflare 的落地方式
Astro 适合在 middleware 中加固定响应头。对于静态内容站点,减少内联脚本通常比强行上 nonce 更简单。
// src/middleware.ts
import { defineMiddleware } from "astro:middleware";
const headers: Record<string, string> = {
"Content-Security-Policy-Report-Only": "default-src 'self'; script-src 'self' https://www.googletagmanager.com; img-src 'self' data: blob: https:; connect-src 'self' https://www.google-analytics.com; frame-ancestors 'none'; object-src 'none'; base-uri 'self'; report-uri /api/csp-report",
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
"Referrer-Policy": "strict-origin-when-cross-origin",
"Permissions-Policy": "camera=(), microphone=(), geolocation=(), payment=(self)",
};
export const onRequest = defineMiddleware(async (_context, next) => {
const response = await next();
for (const [name, value] of Object.entries(headers)) response.headers.set(name, value);
return response;
});
Express 项目建议使用 Helmet,但不要以为 helmet() 默认值就适合所有产品。CSP 必须按实际资源调整。
import crypto from "node:crypto";
import express from "express";
import helmet from "helmet";
const app = express();
app.use((req, res, next) => {
res.locals.cspNonce = crypto.randomBytes(16).toString("base64");
next();
});
app.use(helmet({
contentSecurityPolicy: {
useDefaults: false,
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", (req, res) => `'nonce-${res.locals.cspNonce}'`, "'strict-dynamic'", "https:", "http:"],
imgSrc: ["'self'", "data:", "blob:", "https:"],
connectSrc: ["'self'", "https://www.google-analytics.com"],
objectSrc: ["'none'"],
baseUri: ["'self'"],
frameAncestors: ["'none'"],
reportUri: ["/csp-report"],
},
},
strictTransportSecurity: { maxAge: 300, includeSubDomains: true },
referrerPolicy: { policy: "strict-origin-when-cross-origin" },
xFrameOptions: { action: "deny" },
}));
Cloudflare Pages 可以用 _headers,但静态 _headers 不能生成每个请求不同的 nonce。
/*
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(self)
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' https://www.googletagmanager.com; img-src 'self' data: blob: https:; connect-src 'self' https://www.google-analytics.com; frame-src 'self' https://www.youtube-nocookie.com; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; report-uri /csp-report
典型使用场景与坑
内容站点经常同时使用 GTM、GA4、AdSense、图片 CDN 和 YouTube。这里最容易发生的事故是广告消失或统计断流。不要为了通过扫描器评分而直接把 script-src 收得过窄,应先用 Report-Only 看真实影响。
SaaS 后台则相反,应该尽量减少第三方脚本。后台管理、账号设置、账单页面适合使用 frame-ancestors 'none'、object-src 'none'、base-uri 'self' 和严格的 form-action。支付 SDK 或客服组件只放在需要它的路由上,不要全站开放。
嵌入式小组件需要单独处理。如果你要允许客户把页面放进 iframe,就不能对该路由设置 X-Frame-Options: DENY。普通页面和嵌入页面应使用不同 header。
HSTS 的坑也很常见。第一天就写 max-age=63072000; includeSubDomains; preload 看起来很强,但如果某个子域还不能 HTTPS,就会变成事故。先短时间上线,确认日志、广告、客服、旧域名,再逐步增加。
验证与实际结果
至少检查首页、登录或表单页、嵌入或支付页。
curl -I https://example.com/
curl -I https://example.com/login
curl -I https://example.com/embed/widget
随后用 Security Headers 看整体响应头,用 CSP Evaluator 检查 CSP 弱点。评分很有用,但不是最终目标。最终目标是:保护页面、保持广告和统计正常、让不同路由有正确策略。
如果你需要把这套流程放进真实仓库,可以通过 Claude Code 培训与导入咨询 做一次项目级审查;如果想自己推进,可以从 ClaudeCodeLab 模板与产品 开始,把本篇清单写进 CLAUDE.md。
本文的测试结论很明确:先上 Report-Only 比直接强制 CSP 安全得多。测试站点中,GTM 的内联 nonce、GA4 的 connect-src、YouTube 的 frame-src、图片 CDN 的 img-src 都暴露出不同问题。让 Claude Code 分类这些报告后,得到的是“按页面收紧、给 GTM 加 nonce、移除无用标签、逐步增加 HSTS”的计划,而不是简单添加一串域名。
免费 PDF: Claude Code 速查表
输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。
我们会妥善保护你的信息,不发送垃圾邮件。
把 Claude Code 变成真正能带来结果的工作流
先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。
关于作者
Masa
专注 Claude Code 实务流程、团队导入和内容转化的工程师。
相关文章
从Obsidian到CLAUDE.md的Claude Code流程:不再反复解释上下文
把 Obsidian 工作笔记整理成 CLAUDE.md 运行说明,让 Claude Code 每次都带着正确上下文开始。
Claude Code 收入 CTA 路由:从文章分流到 PDF、Gumroad 与咨询
用 Claude Code 按读者意图把文章流量分到免费 PDF、Gumroad 教材或咨询入口。
Claude Code 团队交接规则: 把审查证据、权限、回滚和收入路径一起交付
面向团队的 Claude Code 交接格式: 证据、权限、回滚、免费 PDF、Gumroad 与咨询路径都要可审查。