使用 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‑2Report-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 カスタムスキルを提供しています。

查看 Security Pack →

0 浏览
Back to Blog

相关文章

阅读更多 »