secure-headers-csp-builder

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Secure Headers & CSP Builder

安全标头与CSP构建工具

Add security headers safely without breaking functionality.
安全添加安全标头,不会破坏现有功能。

Essential Security Headers

核心安全标头

typescript
// middleware/security-headers.ts
import { Request, Response, NextFunction } from "express";

export function securityHeaders(
  req: Request,
  res: Response,
  next: NextFunction
) {
  // Prevent clickjacking
  res.setHeader("X-Frame-Options", "DENY");

  // Prevent MIME sniffing
  res.setHeader("X-Content-Type-Options", "nosniff");

  // XSS Protection (legacy browsers)
  res.setHeader("X-XSS-Protection", "1; mode=block");

  // Referrer Policy
  res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");

  // Permissions Policy (replaces Feature-Policy)
  res.setHeader(
    "Permissions-Policy",
    "camera=(), microphone=(), geolocation=(self), payment=()"
  );

  // HSTS - Force HTTPS (only in production)
  if (process.env.NODE_ENV === "production") {
    res.setHeader(
      "Strict-Transport-Security",
      "max-age=31536000; includeSubDomains; preload"
    );
  }

  next();
}
typescript
// middleware/security-headers.ts
import { Request, Response, NextFunction } from "express";

export function securityHeaders(
  req: Request,
  res: Response,
  next: NextFunction
) {
  // Prevent clickjacking
  res.setHeader("X-Frame-Options", "DENY");

  // Prevent MIME sniffing
  res.setHeader("X-Content-Type-Options", "nosniff");

  // XSS Protection (legacy browsers)
  res.setHeader("X-XSS-Protection", "1; mode=block");

  // Referrer Policy
  res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");

  // Permissions Policy (replaces Feature-Policy)
  res.setHeader(
    "Permissions-Policy",
    "camera=(), microphone=(), geolocation=(self), payment=()"
  );

  // HSTS - Force HTTPS (only in production)
  if (process.env.NODE_ENV === "production") {
    res.setHeader(
      "Strict-Transport-Security",
      "max-age=31536000; includeSubDomains; preload"
    );
  }

  next();
}

Content Security Policy (CSP)

内容安全策略(CSP)

Phase 1: Report-Only Mode

阶段1:仅报告模式

typescript
// config/csp-report-only.ts
export const cspReportOnly = {
  "default-src": ["'self'"],
  "script-src": [
    "'self'",
    "'report-sample'",
    "https://cdn.jsdelivr.net",
    "https://www.googletagmanager.com",
  ],
  "style-src": ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
  "img-src": ["'self'", "data:", "https:"],
  "font-src": ["'self'", "https://fonts.gstatic.com"],
  "connect-src": ["'self'", "https://api.example.com"],
  "frame-ancestors": ["'none'"],
  "base-uri": ["'self'"],
  "form-action": ["'self'"],
  "report-uri": ["/api/csp-report"],
};

function formatCSP(policy: Record<string, string[]>): string {
  return Object.entries(policy)
    .map(([key, values]) => `${key} ${values.join(" ")}`)
    .join("; ");
}

// Apply report-only header
app.use((req, res, next) => {
  res.setHeader(
    "Content-Security-Policy-Report-Only",
    formatCSP(cspReportOnly)
  );
  next();
});
typescript
// config/csp-report-only.ts
export const cspReportOnly = {
  "default-src": ["'self'"],
  "script-src": [
    "'self'",
    "'report-sample'",
    "https://cdn.jsdelivr.net",
    "https://www.googletagmanager.com",
  ],
  "style-src": ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
  "img-src": ["'self'", "data:", "https:"],
  "font-src": ["'self'", "https://fonts.gstatic.com"],
  "connect-src": ["'self'", "https://api.example.com"],
  "frame-ancestors": ["'none'"],
  "base-uri": ["'self'"],
  "form-action": ["'self'"],
  "report-uri": ["/api/csp-report"],
};

function formatCSP(policy: Record<string, string[]>): string {
  return Object.entries(policy)
    .map(([key, values]) => `${key} ${values.join(" ")}`)
    .join("; ");
}

// Apply report-only header
app.use((req, res, next) => {
  res.setHeader(
    "Content-Security-Policy-Report-Only",
    formatCSP(cspReportOnly)
  );
  next();
});

CSP Violation Reporter

CSP违规报告端点

typescript
// routes/csp-report.ts
app.post(
  "/api/csp-report",
  express.json({ type: "application/csp-report" }),
  (req, res) => {
    const violation = req.body["csp-report"];

    console.error("CSP Violation:", {
      documentUri: violation["document-uri"],
      violatedDirective: violation["violated-directive"],
      blockedUri: violation["blocked-uri"],
      sourceFile: violation["source-file"],
      lineNumber: violation["line-number"],
    });

    // Store in monitoring system
    trackCSPViolation({
      directive: violation["violated-directive"],
      blockedUri: violation["blocked-uri"],
      userAgent: req.headers["user-agent"],
      timestamp: new Date(),
    });

    res.status(204).send();
  }
);
typescript
// routes/csp-report.ts
app.post(
  "/api/csp-report",
  express.json({ type: "application/csp-report" }),
  (req, res) => {
    const violation = req.body["csp-report"];

    console.error("CSP Violation:", {
      documentUri: violation["document-uri"],
      violatedDirective: violation["violated-directive"],
      blockedUri: violation["blocked-uri"],
      sourceFile: violation["source-file"],
      lineNumber: violation["line-number"],
    });

    // Store in monitoring system
    trackCSPViolation({
      directive: violation["violated-directive"],
      blockedUri: violation["blocked-uri"],
      userAgent: req.headers["user-agent"],
      timestamp: new Date(),
    });

    res.status(204).send();
  }
);

Phase 2: Enforce Mode

阶段2:强制实施模式

typescript
// config/csp-enforce.ts
export const cspEnforce = {
  "default-src": ["'self'"],
  "script-src": [
    "'self'",
    // Add nonces for inline scripts
    "'nonce-{NONCE}'",
    "https://cdn.jsdelivr.net",
    "https://www.googletagmanager.com",
  ],
  "style-src": [
    "'self'",
    // Replace unsafe-inline with nonces
    "'nonce-{NONCE}'",
    "https://fonts.googleapis.com",
  ],
  "img-src": ["'self'", "data:", "https:"],
  "font-src": ["'self'", "https://fonts.gstatic.com"],
  "connect-src": ["'self'", "https://api.example.com"],
  "frame-ancestors": ["'none'"],
  "base-uri": ["'self'"],
  "form-action": ["'self'"],
  "upgrade-insecure-requests": [],
};

// Generate nonce for each request
app.use((req, res, next) => {
  const nonce = crypto.randomBytes(16).toString("base64");
  res.locals.cspNonce = nonce;

  const policy = formatCSP(cspEnforce).replace(/{NONCE}/g, nonce);

  res.setHeader("Content-Security-Policy", policy);
  next();
});
typescript
// config/csp-enforce.ts
export const cspEnforce = {
  "default-src": ["'self'"],
  "script-src": [
    "'self'",
    // Add nonces for inline scripts
    "'nonce-{NONCE}'",
    "https://cdn.jsdelivr.net",
    "https://www.googletagmanager.com",
  ],
  "style-src": [
    "'self'",
    // Replace unsafe-inline with nonces
    "'nonce-{NONCE}'",
    "https://fonts.googleapis.com",
  ],
  "img-src": ["'self'", "data:", "https:"],
  "font-src": ["'self'", "https://fonts.gstatic.com"],
  "connect-src": ["'self'", "https://api.example.com"],
  "frame-ancestors": ["'none'"],
  "base-uri": ["'self'"],
  "form-action": ["'self'"],
  "upgrade-insecure-requests": [],
};

// Generate nonce for each request
app.use((req, res, next) => {
  const nonce = crypto.randomBytes(16).toString("base64");
  res.locals.cspNonce = nonce;

  const policy = formatCSP(cspEnforce).replace(/{NONCE}/g, nonce);

  res.setHeader("Content-Security-Policy", policy);
  next();
});

Nonce Implementation

Nonce实现

typescript
// views/index.ejs
<!DOCTYPE html>
<html>
<head>
  <!-- Inline script with nonce -->
  <script nonce="<%= cspNonce %>">
    console.log('This script is allowed by CSP');
  </script>

  <!-- Inline style with nonce -->
  <style nonce="<%= cspNonce %>">
    body { background: white; }
  </style>
</head>
<body>
  <h1>Secure Page</h1>
</body>
</html>
typescript
// views/index.ejs
<!DOCTYPE html>
<html>
<head>
  <!-- Inline script with nonce -->
  <script nonce="<%= cspNonce %>">
    console.log('This script is allowed by CSP');
  </script>

  <!-- Inline style with nonce -->
  <style nonce="<%= cspNonce %>">
    body { background: white; }
  </style>
</head>
<body>
  <h1>Secure Page</h1>
</body>
</html>

Helmet.js Integration

Helmet.js集成

typescript
// Using Helmet for comprehensive security headers
import helmet from "helmet";

app.use(
  helmet({
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        scriptSrc: ["'self'", "'nonce-{NONCE}'"],
        styleSrc: ["'self'", "'nonce-{NONCE}'"],
        imgSrc: ["'self'", "data:", "https:"],
        connectSrc: ["'self'", "https://api.example.com"],
        fontSrc: ["'self'", "https://fonts.gstatic.com"],
        objectSrc: ["'none'"],
        mediaSrc: ["'self'"],
        frameSrc: ["'none'"],
      },
    },
    hsts: {
      maxAge: 31536000,
      includeSubDomains: true,
      preload: true,
    },
    frameguard: {
      action: "deny",
    },
    xssFilter: true,
    noSniff: true,
    referrerPolicy: {
      policy: "strict-origin-when-cross-origin",
    },
  })
);
typescript
// Using Helmet for comprehensive security headers
import helmet from "helmet";

app.use(
  helmet({
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        scriptSrc: ["'self'", "'nonce-{NONCE}'"],
        styleSrc: ["'self'", "'nonce-{NONCE}'"],
        imgSrc: ["'self'", "data:", "https:"],
        connectSrc: ["'self'", "https://api.example.com"],
        fontSrc: ["'self'", "https://fonts.gstatic.com"],
        objectSrc: ["'none'"],
        mediaSrc: ["'self'"],
        frameSrc: ["'none'"],
      },
    },
    hsts: {
      maxAge: 31536000,
      includeSubDomains: true,
      preload: true,
    },
    frameguard: {
      action: "deny",
    },
    xssFilter: true,
    noSniff: true,
    referrerPolicy: {
      policy: "strict-origin-when-cross-origin",
    },
  })
);

Rollout Plan

发布计划

markdown
undefined
markdown
undefined

CSP Rollout Plan

CSP发布计划

Week 1: Report-Only Mode

第1周:仅报告模式

  • Deploy CSP in report-only mode
  • Monitor violation reports
  • Identify problematic resources
  • Whitelist legitimate sources
  • 部署仅报告模式的CSP
  • 监控违规报告
  • 识别有问题的资源
  • 将合法来源加入白名单

Week 2: Analysis

第2周:分析阶段

  • Analyze 1 week of violations
  • Update CSP policy based on reports
  • Fix inline scripts/styles
  • Test on staging
  • 分析1周的违规数据
  • 根据报告更新CSP策略
  • 修复内联脚本/样式
  • 在预发布环境测试

Week 3: Staged Rollout

第3周:分阶段发布

  • Enable enforcement for 10% of traffic
  • Monitor error rates
  • Check user reports
  • Adjust policy if needed
  • 为10%流量启用强制实施
  • 监控错误率
  • 检查用户反馈
  • 如有需要调整策略

Week 4: Full Enforcement

第4周:全面强制实施

  • Enable for 50% of traffic
  • Verify no issues
  • Enable for 100% of traffic
  • Keep report-only header for monitoring
undefined
  • 为50%流量启用强制实施
  • 确认无问题
  • 为100%流量启用强制实施
  • 保留仅报告标头用于监控
undefined

Testing CSP

CSP测试

typescript
// tests/csp.test.ts
import { describe, it, expect } from "vitest";
import request from "supertest";
import { app } from "../src/app";

describe("Content Security Policy", () => {
  it("should set CSP header", async () => {
    const response = await request(app).get("/");

    expect(response.headers["content-security-policy"]).toBeDefined();
    expect(response.headers["content-security-policy"]).toContain(
      "default-src 'self'"
    );
  });

  it("should block inline scripts without nonce", async () => {
    const html = `
      <!DOCTYPE html>
      <html>
      <head>
        <script>alert('blocked')</script>
      </head>
      </html>
    `;

    // This would be blocked by CSP
    // Verify in browser console or automated tests
  });

  it("should allow scripts with valid nonce", async () => {
    const response = await request(app).get("/");

    // Extract nonce from response
    const nonceMatch = response.text.match(/nonce="([^"]+)"/);
    expect(nonceMatch).toBeDefined();
  });
});
typescript
// tests/csp.test.ts
import { describe, it, expect } from "vitest";
import request from "supertest";
import { app } from "../src/app";

describe("Content Security Policy", () => {
  it("should set CSP header", async () => {
    const response = await request(app).get("/");

    expect(response.headers["content-security-policy"]).toBeDefined();
    expect(response.headers["content-security-policy"]).toContain(
      "default-src 'self'"
    );
  });

  it("should block inline scripts without nonce", async () => {
    const html = `
      <!DOCTYPE html>
      <html>
      <head>
        <script>alert('blocked')</script>
      </head>
      </html>
    `;

    // This would be blocked by CSP
    // Verify in browser console or automated tests
  });

  it("should allow scripts with valid nonce", async () => {
    const response = await request(app).get("/");

    // Extract nonce from response
    const nonceMatch = response.text.match(/nonce="([^"]+)"/);
    expect(nonceMatch).toBeDefined();
  });
});

Common CSP Issues & Fixes

常见CSP问题与修复方案

typescript
// Issue 1: Inline event handlers
// ❌ Bad
<button onclick="handleClick()">Click</button>

// ✅ Good
<button id="myButton">Click</button>
<script nonce="<%= cspNonce %>">
  document.getElementById('myButton').addEventListener('click', handleClick);
</script>

// Issue 2: Inline styles
// ❌ Bad
<div style="color: red;">Text</div>

// ✅ Good
<style nonce="<%= cspNonce %>">
  .red-text { color: red; }
</style>
<div class="red-text">Text</div>

// Issue 3: eval() usage
// ❌ Bad
eval('console.log("test")');

// ✅ Good
// Don't use eval - refactor code

// Issue 4: Third-party scripts
// ❌ Bad - no CSP entry
<script src="https://cdn.example.com/script.js"></script>

// ✅ Good - whitelisted in CSP
script-src: ['self', 'https://cdn.example.com']
typescript
// Issue 1: Inline event handlers
// ❌ Bad
<button onclick="handleClick()">Click</button>

// ✅ Good
<button id="myButton">Click</button>
<script nonce="<%= cspNonce %>">
  document.getElementById('myButton').addEventListener('click', handleClick);
</script>

// Issue 2: Inline styles
// ❌ Bad
<div style="color: red;">Text</div>

// ✅ Good
<style nonce="<%= cspNonce %>">
  .red-text { color: red; }
</style>
<div class="red-text">Text</div>

// Issue 3: eval() usage
// ❌ Bad
eval('console.log("test")');

// ✅ Good
// Don't use eval - refactor code

// Issue 4: Third-party scripts
// ❌ Bad - no CSP entry
<script src="https://cdn.example.com/script.js"></script>

// ✅ Good - whitelisted in CSP
script-src: ['self', 'https://cdn.example.com']

Monitoring & Alerts

监控与告警

typescript
// monitoring/csp-violations.ts
import { CloudWatch } from "@aws-sdk/client-cloudwatch";

const cloudwatch = new CloudWatch();

export async function trackCSPViolation(violation: {
  directive: string;
  blockedUri: string;
  userAgent: string;
  timestamp: Date;
}) {
  await cloudwatch.putMetricData({
    Namespace: "Security/CSP",
    MetricData: [
      {
        MetricName: "Violations",
        Value: 1,
        Unit: "Count",
        Timestamp: violation.timestamp,
        Dimensions: [
          {
            Name: "Directive",
            Value: violation.directive,
          },
          {
            Name: "BlockedUri",
            Value: violation.blockedUri,
          },
        ],
      },
    ],
  });

  // Alert if violations spike
  if (await isViolationSpike()) {
    await sendAlert({
      title: "CSP Violation Spike Detected",
      message: `High number of violations for ${violation.directive}`,
    });
  }
}
typescript
// monitoring/csp-violations.ts
import { CloudWatch } from "@aws-sdk/client-cloudwatch";

const cloudwatch = new CloudWatch();

export async function trackCSPViolation(violation: {
  directive: string;
  blockedUri: string;
  userAgent: string;
  timestamp: Date;
}) {
  await cloudwatch.putMetricData({
    Namespace: "Security/CSP",
    MetricData: [
      {
        MetricName: "Violations",
        Value: 1,
        Unit: "Count",
        Timestamp: violation.timestamp,
        Dimensions: [
          {
            Name: "Directive",
            Value: violation.directive,
          },
          {
            Name: "BlockedUri",
            Value: violation.blockedUri,
          },
        ],
      },
    ],
  });

  // Alert if violations spike
  if (await isViolationSpike()) {
    await sendAlert({
      title: "CSP Violation Spike Detected",
      message: `High number of violations for ${violation.directive}`,
    });
  }
}

Best Practices

最佳实践

  1. Start report-only: Don't break production
  2. Gradual rollout: 10% → 50% → 100%
  3. Use nonces: Better than unsafe-inline
  4. Monitor violations: Track and analyze
  5. Test thoroughly: All pages and features
  6. Document exceptions: Why resources whitelisted
  7. Regular audits: Quarterly CSP review
  1. 从仅报告模式开始:不要影响生产环境
  2. 逐步发布:10% → 50% → 100%
  3. 使用Nonce:比unsafe-inline更安全
  4. 监控违规情况:跟踪并分析数据
  5. 全面测试:覆盖所有页面和功能
  6. 记录例外情况:说明为何将资源加入白名单
  7. 定期审计:每季度审查CSP策略

Output Checklist

输出检查清单

  • Security headers implemented
  • CSP policy defined (report-only)
  • CSP violation reporter endpoint
  • Nonce generation for inline scripts
  • Helmet.js configured
  • Rollout plan documented
  • Testing strategy implemented
  • Monitoring and alerts configured
  • Team trained on CSP
  • Staged rollout completed
  • 已实现安全标头
  • 已定义CSP策略(仅报告模式)
  • 已配置CSP违规报告端点
  • 已为内联脚本生成Nonce
  • 已配置Helmet.js
  • 已记录发布计划
  • 已实施测试策略
  • 已配置监控与告警
  • 已为团队提供CSP培训
  • 已完成分阶段发布