security-headers.test.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. import { describe, expect, test } from "vitest";
  2. import {
  3. buildSecurityHeaders,
  4. DEFAULT_SECURITY_HEADERS_CONFIG,
  5. } from "../../src/lib/security/security-headers";
  6. describe("buildSecurityHeaders", () => {
  7. test("默认配置应生成预期安全头", () => {
  8. const headers = buildSecurityHeaders();
  9. expect(headers["X-Content-Type-Options"]).toBe("nosniff");
  10. expect(headers["X-Frame-Options"]).toBe(DEFAULT_SECURITY_HEADERS_CONFIG.frameOptions);
  11. expect(headers["Referrer-Policy"]).toBe("strict-origin-when-cross-origin");
  12. expect(headers["X-DNS-Prefetch-Control"]).toBe("off");
  13. expect(headers["Strict-Transport-Security"]).toBeUndefined();
  14. expect(headers["Content-Security-Policy"]).toBeUndefined();
  15. expect(headers["Content-Security-Policy-Report-Only"]).toContain("default-src 'self'");
  16. });
  17. test("enableHsts=true 时应包含 HSTS 头", () => {
  18. const headers = buildSecurityHeaders({ enableHsts: true });
  19. expect(headers["Strict-Transport-Security"]).toBe(
  20. `max-age=${DEFAULT_SECURITY_HEADERS_CONFIG.hstsMaxAge}; includeSubDomains`
  21. );
  22. });
  23. test("enableHsts=false 时不应包含 HSTS 头", () => {
  24. const headers = buildSecurityHeaders({ enableHsts: false });
  25. expect(headers["Strict-Transport-Security"]).toBeUndefined();
  26. });
  27. test("CSP report-only 模式应使用 Report-Only 头", () => {
  28. const headers = buildSecurityHeaders({ cspMode: "report-only" });
  29. expect(headers["Content-Security-Policy-Report-Only"]).toContain("default-src 'self'");
  30. expect(headers["Content-Security-Policy"]).toBeUndefined();
  31. });
  32. test("CSP enforce 模式应使用强制策略头", () => {
  33. const headers = buildSecurityHeaders({ cspMode: "enforce" });
  34. expect(headers["Content-Security-Policy"]).toContain("default-src 'self'");
  35. expect(headers["Content-Security-Policy-Report-Only"]).toBeUndefined();
  36. });
  37. test("CSP disabled 模式不应输出任何 CSP 头", () => {
  38. const headers = buildSecurityHeaders({ cspMode: "disabled" });
  39. expect(headers["Content-Security-Policy"]).toBeUndefined();
  40. expect(headers["Content-Security-Policy-Report-Only"]).toBeUndefined();
  41. });
  42. test("X-Content-Type-Options 始终为 nosniff", () => {
  43. const defaultHeaders = buildSecurityHeaders();
  44. const disabledCspHeaders = buildSecurityHeaders({ cspMode: "disabled" });
  45. const enforceCspHeaders = buildSecurityHeaders({ cspMode: "enforce", enableHsts: true });
  46. expect(defaultHeaders["X-Content-Type-Options"]).toBe("nosniff");
  47. expect(disabledCspHeaders["X-Content-Type-Options"]).toBe("nosniff");
  48. expect(enforceCspHeaders["X-Content-Type-Options"]).toBe("nosniff");
  49. });
  50. test("X-Frame-Options 应与配置一致", () => {
  51. const denyHeaders = buildSecurityHeaders({ frameOptions: "DENY" });
  52. const sameOriginHeaders = buildSecurityHeaders({ frameOptions: "SAMEORIGIN" });
  53. expect(denyHeaders["X-Frame-Options"]).toBe("DENY");
  54. expect(sameOriginHeaders["X-Frame-Options"]).toBe("SAMEORIGIN");
  55. });
  56. test("cspReportUri with valid URL appends report-uri directive", () => {
  57. const headers = buildSecurityHeaders({
  58. cspMode: "report-only",
  59. cspReportUri: "https://csp.example.com/report",
  60. });
  61. expect(headers["Content-Security-Policy-Report-Only"]).toContain(
  62. "; report-uri https://csp.example.com/report"
  63. );
  64. });
  65. test("cspReportUri with semicolons is rejected to prevent directive injection", () => {
  66. const headers = buildSecurityHeaders({
  67. cspMode: "enforce",
  68. cspReportUri: "https://evil.com; script-src 'unsafe-eval'",
  69. });
  70. expect(headers["Content-Security-Policy"]).not.toContain("report-uri");
  71. expect(headers["Content-Security-Policy"]).not.toContain("evil.com");
  72. });
  73. test("cspReportUri with non-URL value is rejected", () => {
  74. const headers = buildSecurityHeaders({
  75. cspMode: "enforce",
  76. cspReportUri: "not a url",
  77. });
  78. expect(headers["Content-Security-Policy"]).not.toContain("report-uri");
  79. });
  80. test("cspReportUri with empty string is rejected", () => {
  81. const headers = buildSecurityHeaders({
  82. cspMode: "enforce",
  83. cspReportUri: "",
  84. });
  85. expect(headers["Content-Security-Policy"]).not.toContain("report-uri");
  86. });
  87. });