diff --git a/clients/web/src/lib/sandbox-csp.test.ts b/clients/web/src/lib/sandbox-csp.test.ts new file mode 100644 index 00000000..3cd471c2 --- /dev/null +++ b/clients/web/src/lib/sandbox-csp.test.ts @@ -0,0 +1,170 @@ +import { describe, expect, it, vi } from "vitest"; +import { + SAFE_CSP_SOURCE, + approveCspSources, + buildSandboxCspPolicy, + escapeHtmlAttr, + wrapSandboxedHtml, +} from "./sandbox-csp"; + +describe("SAFE_CSP_SOURCE", () => { + it.each([ + "*", + "https://api.example.com", + "https://api.example.com:8443", + "https://api.example.com/path/seg", + "wss://realtime.service.com", + "*.example.com", + "https://*.cloudflare.com", + "data:", + "blob:", + "example.com", + // Schemes are case-insensitive per the URL spec, so a capitalized or + // mixed-case scheme is still a valid source (over-rejecting it would + // silently drop a restriction a server asked for). + "HTTPS://api.example.com", + "WSS://realtime.service.com", + "Https://*.cloudflare.com", + ])("accepts %s", (s) => { + expect(SAFE_CSP_SOURCE.test(s)).toBe(true); + }); + + it.each([ + "https://a.com; script-src *", + 'https://a.com" onload=', + "https://a.com>", + "app`; + const wrapped = wrapSandboxedHtml(evil, "default-src 'none'"); + // The first in the output is OURS; the untrusted token only + // appears after . + const ourHead = wrapped.indexOf(""); + const body = wrapped.indexOf(""); + const evilHead = wrapped.indexOf("", ourHead + 1); + expect(ourHead).toBeLessThan(body); + expect(evilHead).toBeGreaterThan(body); + }); + + it("does not introduce a host-authored