import { createHash } from "node:crypto"; import { describe, expect, it } from "vitest"; import { buildControlUiCspHeader, computeInlineScriptHashes } from "./control-ui-csp.js"; describe("buildControlUiCspHeader", () => { it("blocks inline scripts while allowing inline styles", () => { const csp = buildControlUiCspHeader(); expect(csp).toContain("frame-ancestors 'none'"); expect(csp).toContain("script-src 'self'"); expect(csp).not.toContain("script-src 'self' 'unsafe-inline'"); expect(csp).toContain("style-src 'self' 'unsafe-inline' https://fonts.googleapis.com"); }); it("allows Google Fonts for style and font loading", () => { const csp = buildControlUiCspHeader(); expect(csp).toContain("https://fonts.googleapis.com"); expect(csp).toContain("font-src 'self' https://fonts.gstatic.com"); }); it("includes inline script hashes in script-src when provided", () => { const csp = buildControlUiCspHeader({ inlineScriptHashes: ["sha256-abc123"], }); expect(csp).toContain("script-src 'self' 'sha256-abc123'"); expect(csp).not.toMatch(/script-src[^;]*'unsafe-inline'/); }); it("includes multiple inline script hashes", () => { const csp = buildControlUiCspHeader({ inlineScriptHashes: ["sha256-aaa", "sha256-bbb"], }); expect(csp).toContain("script-src 'self' 'sha256-aaa' 'sha256-bbb'"); }); it("falls back to plain script-src self when hashes array is empty", () => { const csp = buildControlUiCspHeader({ inlineScriptHashes: [] }); expect(csp).toMatch(/script-src 'self'(?:;|$)/); }); }); describe("computeInlineScriptHashes", () => { it("returns empty for HTML without scripts", () => { expect(computeInlineScriptHashes("hi")).toEqual([]); }); it("hashes inline script content", () => { const content = "alert(1)"; const expected = createHash("sha256").update(content, "utf8").digest("base64"); const hashes = computeInlineScriptHashes(``); expect(hashes).toEqual([`sha256-${expected}`]); }); it("skips scripts with src attribute", () => { const hashes = computeInlineScriptHashes(''); expect(hashes).toEqual([]); }); it("does not treat data-src as an external script attribute", () => { const content = "console.log('inline')"; const expected = createHash("sha256").update(content, "utf8").digest("base64"); const hashes = computeInlineScriptHashes( ``, ); expect(hashes).toEqual([`sha256-${expected}`]); }); it("hashes only inline scripts when mixed with external", () => { const inlineContent = "console.log('init')"; const expected = createHash("sha256").update(inlineContent, "utf8").digest("base64"); const html = [ "", ``, '', "", ].join(""); const hashes = computeInlineScriptHashes(html); expect(hashes).toEqual([`sha256-${expected}`]); }); it("handles multiline inline scripts", () => { const content = "\n var x = 1;\n console.log(x);\n"; const expected = createHash("sha256").update(content, "utf8").digest("base64"); const hashes = computeInlineScriptHashes(``); expect(hashes).toEqual([`sha256-${expected}`]); }); it("skips empty inline scripts", () => { expect(computeInlineScriptHashes("")).toEqual([]); }); });