Skip to content
54 linesCodeBlameRaw
1/**
2 * The headers every answer from the site carries, and the policy its pages
3 * run under. No Workers imports, so it can be tested under Node.
4 *
5 * - Nothing is sniffed: a download or a data request is only the type it says.
6 * - A link to another site sends the origin, never the path.
7 * - No other site may put g1t's pages in a frame.
8 * - A page runs only the scripts the site served it: its own files, and the
9 * inline scripts React and React Router write, each carrying the page's
10 * nonce. Styles may be inline (highlighting and layout set them); images
11 * may come from any HTTPS address (pictures in a README); requests may go
12 * to any HTTPS address (the status page's summary).
13 */
14
15/** A fresh nonce for one page: 128 random bits, base64. */
16export function makeNonce(): string {
17 const bytes = crypto.getRandomValues(new Uint8Array(16));
18 return btoa(String.fromCharCode(...bytes));
19}
20
21/** The Content-Security-Policy of a page rendered with `nonce`. */
22export function pagePolicy(nonce: string): string {
23 return [
24 "default-src 'self'",
25 // Cloudflare's Web Analytics beacon, when the zone turns it on.
26 `script-src 'self' 'nonce-${nonce}' https://static.cloudflareinsights.com`,
27 "style-src 'self' 'unsafe-inline'",
28 "img-src 'self' https: data: blob:",
29 "media-src 'self' https:",
30 "font-src 'self' data:",
31 "connect-src 'self' https:",
32 "frame-src 'none'",
33 "object-src 'none'",
34 "base-uri 'self'",
35 "frame-ancestors 'none'",
36 ].join("; ");
37}
38
39/**
40 * The answer with the site's headers added. A header the answer already
41 * has is kept. An upgrade to a WebSocket is passed on as it is.
42 */
43export function withSiteHeaders(response: Response): Response {
44 if (response.status === 101 || (response as Response & { webSocket?: unknown }).webSocket) return response;
45 // A redirect's headers cannot be changed, so the answer is copied.
46 const answer = new Response(response.body, response);
47 const headers = answer.headers;
48 if (!headers.has("x-content-type-options")) headers.set("x-content-type-options", "nosniff");
49 if (!headers.has("referrer-policy")) headers.set("referrer-policy", "strict-origin-when-cross-origin");
50 if (/^text\/html\b/i.test(headers.get("content-type") ?? "") && !headers.has("x-frame-options")) {
51 headers.set("x-frame-options", "DENY");
52 }
53 return answer;
54}