import { describe, expect, mock, test } from "bun:test"; mock.module("cloudflare:workers", () => ({ env: {} })); const { pageCsp, withSecurityHeaders } = await import("./security-headers"); describe("withSecurityHeaders (DEPLOY-P7)", () => { test("adds nosniff, referrer policy, frame denial and HSTS on https", () => { const response = withSecurityHeaders(new Response("ok"), new Request("https://gitfor.dev/")); expect(response.headers.get("x-content-type-options")).toBe("nosniff"); expect(response.headers.get("referrer-policy")).toBe("strict-origin-when-cross-origin"); expect(response.headers.get("x-frame-options")).toBe("DENY"); expect(response.headers.get("strict-transport-security")).toContain("max-age="); }); test("no HSTS over http or on loopback", () => { expect( withSecurityHeaders(new Response("ok"), new Request("http://localhost:3000/")).headers.has( "strict-transport-security", ), ).toBe(false); }); test("a route's own values win and immutable redirects still get headers", () => { const own = new Response("ok", { headers: { "referrer-policy": "no-referrer" } }); expect(withSecurityHeaders(own, new Request("https://gitfor.dev/")).headers.get("referrer-policy")).toBe( "no-referrer", ); const redirect = withSecurityHeaders( Response.redirect("https://gitfor.dev/x", 302), new Request("https://gitfor.dev/"), ); expect(redirect.status).toBe(302); expect(redirect.headers.get("x-content-type-options")).toBe("nosniff"); }); }); describe("pageCsp", () => { test("app pages forbid framing, plugins and off-origin forms and base", () => { const csp = pageCsp("https://gitforusercontent.dev", null); expect(csp).toBe( "img-src 'self' data: blob: https://gitforusercontent.dev; frame-ancestors 'none'; base-uri 'self'; object-src 'none'; form-action 'self'", ); }); test("the PostHog host stays allowed by every fetch/script/img directive the page CSP defines", () => { const host = "https://us.i.posthog.com"; const directives = pageCsp("https://gitforusercontent.dev", host) .split(";") .map((part) => part.trim().split(/\s+/)); const guarded = ["img-src", "connect-src", "script-src", "script-src-elem", "default-src"]; const present = directives.filter(([name]) => guarded.includes(name ?? "")); expect(present.length).toBeGreaterThan(0); for (const [name, ...sources] of present) { expect({ name, allowed: sources.includes(host) || sources.includes("'self'") }).toEqual({ name, allowed: true }); expect({ name, host: sources.includes(host) }).toEqual({ name, host: true }); } }); });