Use Cases (更新: 2026/6/2)

用 Claude Code 配置 Web 安全响应头:CSP、nonce、HSTS 与广告兼容实战

用 Claude Code 设计 CSP、nonce、HSTS、frame-ancestors,并在 Next.js、Astro、Express、Cloudflare 中落地验证。

用 Claude Code 配置 Web 安全响应头:CSP、nonce、HSTS 与广告兼容实战

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-Optionsframe-ancestorsReferrer-PolicyPermissions-Policy,并提供 Next.js、Astro、Express、Cloudflare Pages 的可复制配置。Claude Code 的整体安全使用方式,可以搭配阅读 Claude Code 安全最佳实践Claude Code 安全审计

建议同时打开官方文档核对:MDN Content-Security-PolicyNext.js CSP guideMDN Strict-Transport-Securityhstspreload.orgCloudflare Pages HeadersHelmetGoogle Tag Manager CSPAdSense 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; includeSubDomainspreload 必须确认所有子域 HTTPS 后再考虑
X-Frame-OptionsDENYSAMEORIGIN现代浏览器优先看 CSP 的 frame-ancestors
Referrer-Policystrict-origin-when-cross-originURL 中不要放个人信息或 token
Permissions-Policy禁用不用的能力只给真正需要的页面开放
X-Content-Type-Optionsnosniff成本低,通常全站设置

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”的计划,而不是简单添加一串域名。

#Claude Code #security #HTTP headers #CSP #web development
免费

免费 PDF: Claude Code 速查表

输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。

我们会妥善保护你的信息,不发送垃圾邮件。

把 Claude Code 变成真正能带来结果的工作流

先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。

Masa

关于作者

Masa

专注 Claude Code 实务流程、团队导入和内容转化的工程师。