Files

74 lines
3.4 KiB
TypeScript

// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
// The security headers of spec §6.4, and the two things a page may not do: load a script inline
// or reach an origin that is not this app's.
import { describe, expect, it } from "vitest"
import { contentSecurityPolicy, securityHeaders } from "../apps/web/src/server/headers"
const policy = contentSecurityPolicy({ nonce: "test-nonce" })
describe("the content security policy", () => {
it("allows a script only with this request's nonce", () => {
expect(policy).toContain("script-src 'self' 'nonce-test-nonce' 'strict-dynamic'")
expect(policy).not.toContain("script-src 'self' 'unsafe-inline'")
expect(policy).not.toContain("unsafe-eval")
})
it("refuses framing, a base tag and a plugin outright", () => {
expect(policy).toContain("frame-ancestors 'none'")
expect(policy).toContain("base-uri 'none'")
expect(policy).toContain("object-src 'none'")
})
it("names no origin but this app's", () => {
const sources = policy
.split("; ")
.flatMap((directive) => directive.split(" ").slice(1))
.filter((source) => source.includes("."))
expect(sources).toEqual([])
})
it("keeps fonts and connections on this origin, so no CDN or font host is reachable", () => {
expect(policy).toContain("font-src 'self'")
expect(policy).toContain("connect-src 'self'")
})
})
describe("the content security policy of a development run", () => {
it("allows eval for React's development build, and only there", () => {
expect(contentSecurityPolicy({ nonce: "n", development: true })).toContain("'strict-dynamic' 'unsafe-eval'")
expect(contentSecurityPolicy({ nonce: "n" })).not.toContain("unsafe-eval")
expect(contentSecurityPolicy({ nonce: "n", development: false })).not.toContain("unsafe-eval")
expect(securityHeaders({ nonce: "n", secure: false, development: true })["content-security-policy"]).toContain(
"unsafe-eval",
)
expect(securityHeaders({ nonce: "n", secure: true })["content-security-policy"]).not.toContain("unsafe-eval")
})
})
describe("the response headers", () => {
it("pin the transport only where there is one to pin", () => {
expect(securityHeaders({ nonce: "n", secure: true })["strict-transport-security"]).toContain("max-age=63072000")
expect(securityHeaders({ nonce: "n", secure: false })["strict-transport-security"]).toBeUndefined()
})
it("carry the rest of the baseline", () => {
const headers = securityHeaders({ nonce: "n", secure: true })
expect(headers["x-content-type-options"]).toBe("nosniff")
expect(headers["referrer-policy"]).toBe("strict-origin-when-cross-origin")
expect(headers["cross-origin-opener-policy"]).toBe("same-origin")
expect(headers["permissions-policy"]).toContain("camera=()")
})
})
describe("the browser's half of the API path", () => {
it("types a call by its contract's own schemas", async () => {
// A compile-time property, checked here by construction: `callContract` takes the contract's
// input type and answers its output type. If either drifted, this file would not typecheck.
const { callContract } = await import("../apps/web/src/ui/client")
const contract = { name: "notes.note.create", input: { _input: { title: "" } }, output: { _output: { id: "" } } }
expect(typeof callContract).toBe("function")
expect(contract.name).toBe("notes.note.create")
})
})