使用 Claude Code 设计 Content Security Policy:防止 XSS、nonce、Report-Only 迁移
发布: (2026年3月11日 GMT+8 14:52)
4 分钟阅读
原文: Dev.to
Source: Dev.to
前言
Content Security Policy(CSP)是从根本上防止 XSS 的浏览器端安全机制。由于配置错误可能导致整个站点崩溃,本文将介绍如何从 基于 nonce 的 CSP 设计 安全、分阶段地迁移。
基于 nonce 的 CSP 中间件(Express)
import { randomBytes } from 'crypto';
import { RequestHandler } from 'express';
// リクエスト毎に 16 バイトの nonce を生成
const generateNonce = (): string => randomBytes(16).toString('base64');
export const cspMiddleware: RequestHandler = (req, res, next) => {
const nonce = generateNonce();
// テンプレートエンジンから参照できるように
res.locals.nonce = nonce;
const cspDirectives = [
`script-src 'nonce-${nonce}' 'strict-dynamic'`,
"default-src 'self'",
"style-src 'self' 'unsafe-inline'", // CSS 移行は別途対応
"img-src 'self' data: https:",
"connect-src 'self'",
"font-src 'self'",
"object-src 'none'",
"base-uri 'self'",
"form-action 'self'",
`report-uri /csp-report`,
].join('; ');
// まず Report-Only で様子を見る
res.setHeader('Content-Security-Policy-Report-Only', cspDirectives);
next();
};要点
nonce是在每个请求中生成的、不可预测的 16 字节随机值。- 同时指定
'strict-dynamic',可以让带 nonce 的脚本动态加载的脚本也被信任(现代 SPA 必不可少)。
HTML 模板端(EJS 示例)
">
// このスクリプトから動的に読み込まれるものも信頼される(strict-dynamic)
import('/app.js');
">
…script-src 配置示例
const scriptSrc = [
`'nonce-${nonce}'`, // この nonce 付きスクリプトを信頼
`'strict-dynamic'`, // そこから動的ロードされるものも信頼
// 'unsafe-inline' は strict-dynamic があれば古いブラウザのフォールバック
].join(' ');分阶段迁移流程
const CSP_MODE = process.env.CSP_MODE ?? 'report-only'; // 'enforce' or 'report-only'
export const cspMiddleware: RequestHandler = (req, res, next) => {
const nonce = generateNonce();
res.locals.nonce = nonce;
const directives = buildCspDirectives(nonce);
if (CSP_MODE === 'enforce') {
// 2 週間後にこちらへ切り替え
res.setHeader('Content-Security-Policy', directives);
} else {
// まず Report-Only で違反を収集
res.setHeader('Content-Security-Policy-Report-Only', directives);
}
next();
};迁移时间表示例
| 期间 | 内容 |
|---|---|
| Week 1‑2 | Report-Only 收集并分析违规报告 |
| Week 3 | 确认违规为零 → 将 CSP_MODE=enforce 更改 |
| Week 4+ | 在 Enforce 模式下运行 |
CSP 违规报告的处理与噪声过滤
interface CspReport {
'csp-report': {
'document-uri': string;
'blocked-uri': string;
'violated-directive': string;
'source-file'?: string;
'script-sample'?: string;
};
}
const EXTENSION_PATTERNS = [
/^chrome-extension:/,
/^moz-extension:/,
/^safari-extension:/,
/^ms-browser-extension:/,
];
export const cspReportHandler: RequestHandler = (req, res) => {
const report = req.body as CspReport;
const csp = report['csp-report'];
const blockedUri = csp['blocked-uri'] ?? '';
const sourceFile = csp['source-file'] ?? '';
// ブラウザ拡張機能による違反はスキップ(ノイズ除去)
const isExtension = EXTENSION_PATTERNS.some(
(p) => p.test(blockedUri) || p.test(sourceFile)
);
if (!isExtension) {
// 本物の違反のみログ記録
console.warn('CSP violation:', {
blockedUri,
violatedDirective: csp['violated-directive'],
documentUri: csp['document-uri'],
});
// Datadog や Sentry に送る例
metrics.increment('csp.violation', {
directive: csp['violated-directive'],
});
}
res.status(204).end();
};总结
- 每个请求的 nonce:使用
crypto.randomBytes(16)生成不可预测的值,严格控制内联脚本。 - strict-dynamic:让带 nonce 的脚本动态加载的资源也被信任,使现代 SPA 的 CSP 实现更为实际。
- Report-Only 两周:先收集报告,再安全切换到强制执行。
- 扩展插件过滤:剔除来自浏览器扩展的噪声,只捕获真实违规。
Security Pack 介绍
みょうがの Security Pack(¥1,480)では、CSP・OWASP Top 10 対応のセキュリティレビュー用 Claude Code カスタムスキルを提供しています。