File
Blob: tests/worker/lib/security-headers.test.ts
| 1 | import { describe, expect, it } from "vitest"; |
| 2 | import { |
| 3 | applyDocumentSecurityHeaders, |
| 4 | applySitesSecurityHeaders, |
| 5 | buildDocumentCsp, |
| 6 | } from "@/worker/lib/security-headers"; |
| 7 | |
| 8 | describe("document security headers", () => { |
| 9 | it("includes the Sentry origin in connect-src when a DSN is configured", () => { |
| 10 | const csp = buildDocumentCsp({ |
| 11 | nonce: "nonce-test", |
| 12 | requestUrl: "https://bland.tools/acme/page-1", |
| 13 | sentryDsn: "https://public@example.ingest.sentry.io/1", |
| 14 | }); |
| 15 | |
| 16 | expect(csp).toContain("connect-src 'self' https://cloudflareinsights.com https://example.ingest.sentry.io"); |
| 17 | expect(csp).toContain("script-src 'self' 'nonce-nonce-test' https://static.cloudflareinsights.com"); |
| 18 | expect(csp).not.toContain("challenges.cloudflare.com"); |
| 19 | expect(csp).toContain("upgrade-insecure-requests"); |
| 20 | }); |
| 21 | |
| 22 | it("omits the Sentry origin when no DSN is configured", () => { |
| 23 | const csp = buildDocumentCsp({ |
| 24 | nonce: "nonce-test", |
| 25 | requestUrl: "https://bland.tools/acme/page-1", |
| 26 | sentryDsn: null, |
| 27 | }); |
| 28 | |
| 29 | expect(csp).not.toContain("ingest.sentry.io"); |
| 30 | expect(csp).not.toContain("fonts.googleapis.com"); |
| 31 | expect(csp).not.toContain("fonts.gstatic.com"); |
| 32 | expect(csp).toContain("style-src 'self' 'unsafe-inline'"); |
| 33 | expect(csp).toContain("font-src 'self' https://esm.sh"); |
| 34 | }); |
| 35 | |
| 36 | it("relaxes connect-src for localhost without forcing insecure upgrades", () => { |
| 37 | const csp = buildDocumentCsp({ |
| 38 | nonce: "nonce-test", |
| 39 | requestUrl: "http://localhost:8787/acme/page-1", |
| 40 | sentryDsn: null, |
| 41 | }); |
| 42 | |
| 43 | expect(csp).toContain("connect-src 'self' https://cloudflareinsights.com http: https: ws: wss:"); |
| 44 | expect(csp).toContain("script-src 'self' https://static.cloudflareinsights.com 'unsafe-inline' 'unsafe-eval'"); |
| 45 | expect(csp).not.toContain("upgrade-insecure-requests"); |
| 46 | }); |
| 47 | |
| 48 | it("applies CSP and baseline headers to document responses", async () => { |
| 49 | const response = applyDocumentSecurityHeaders(new Response("<html></html>"), { |
| 50 | nonce: "nonce-test", |
| 51 | requestUrl: "https://bland.tools/acme/page-1", |
| 52 | sentryDsn: null, |
| 53 | }); |
| 54 | |
| 55 | expect(response.headers.get("Content-Security-Policy")).toContain("'nonce-nonce-test'"); |
| 56 | expect(response.headers.get("X-Frame-Options")).toBe("DENY"); |
| 57 | expect(response.headers.get("Referrer-Policy")).toBe("strict-origin-when-cross-origin"); |
| 58 | expect(response.headers.get("X-Content-Type-Options")).toBe("nosniff"); |
| 59 | expect(await response.text()).toBe("<html></html>"); |
| 60 | }); |
| 61 | |
| 62 | it("allows same-origin scripts and Cloudflare Insights on Sites responses", () => { |
| 63 | const response = applySitesSecurityHeaders(new Response("<html></html>")); |
| 64 | const csp = response.headers.get("Content-Security-Policy"); |
| 65 | |
| 66 | expect(csp).toContain("script-src 'self' https://static.cloudflareinsights.com"); |
| 67 | expect(csp).toContain("connect-src 'self' https://cloudflareinsights.com"); |
| 68 | expect(csp).not.toContain("script-src 'self' 'unsafe-inline'"); |
| 69 | expect(csp).not.toContain("'unsafe-eval'"); |
| 70 | }); |
| 71 | }); |