Skip to content
85 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor1/**
2 * What every sudo response carries, and the page shown to whoever is
3 * turned away. No Workers imports, so it can be tested under Node.
4 */
5
6/**
7 * The pages ship no JavaScript, so no script may run at all; styles and
8 * images and fonts come only from sudo itself, and forms post only back
9 * to sudo.
10 */
11export const CONTENT_SECURITY_POLICY = [
12 "default-src 'none'",
13 "script-src 'none'",
14 "style-src 'self'",
15 "font-src 'self'",
16 "img-src 'self' data:",
17 "form-action 'self'",
18 "frame-ancestors 'none'",
19 "base-uri 'none'",
20 "upgrade-insecure-requests",
21].join("; ");
22
23const HEADERS: Record<string, string> = {
24 "cache-control": "no-store",
25 "x-robots-tag": "noindex, nofollow, noarchive",
26 "x-frame-options": "DENY",
27 "x-content-type-options": "nosniff",
28 // Same-origin keeps the Referer that the same-origin check falls back on.
29 "referrer-policy": "same-origin",
30 "strict-transport-security": "max-age=63072000; includeSubDomains",
31 "cross-origin-opener-policy": "same-origin",
32 "cross-origin-resource-policy": "same-origin",
33 "permissions-policy": "camera=(), microphone=(), geolocation=(), payment=(), usb=(), interest-cohort=()",
34};
35
36/** The response with sudo's headers; a policy it already set is kept. */
37export function secure(response: Response): Response {
38 const secured = new Response(response.body, response);
39 for (const [name, value] of Object.entries(HEADERS)) secured.headers.set(name, value);
40 if (!secured.headers.has("content-security-policy")) {
41 secured.headers.set("content-security-policy", CONTENT_SECURITY_POLICY);
42 }
43 return secured;
44}
45
46const DENIED_STYLE = `
47:root{color-scheme:dark}
48body{margin:0;min-height:100vh;display:grid;place-items:center;background:#0f0f11;color:#ededef;
49font:15px/1.6 'Hanken Grotesk',ui-sans-serif,system-ui,sans-serif;-webkit-font-smoothing:antialiased}
50main{max-width:28rem;padding:2rem 1rem;text-align:center}
51.badge{display:inline-block;border:1px solid #b6a8ff66;color:#b6a8ff;background:#b6a8ff1a;border-radius:999px;
52padding:.1rem .6rem;font:600 12px/1.6 ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.02em}
53h1{margin:1rem 0 .5rem;font-size:1.25rem;letter-spacing:-.01em}
54p{margin:0;color:#a0a0a8}code{color:#ededef;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.9em}`;
55
56let styleHash: Promise<string> | null = null;
57
58function hashOfStyle(): Promise<string> {
59 styleHash ??= crypto.subtle
60 .digest("SHA-256", new TextEncoder().encode(DENIED_STYLE))
61 .then((digest) => btoa(String.fromCharCode(...new Uint8Array(digest))));
62 return styleHash;
63}
64
65function escapeHtml(text: string): string {
66 return text.replace(/[&<>"']/g, (char) => `&#${char.charCodeAt(0)};`);
67}
68
69/**
70 * A self-contained page for a refusal: its one inline stylesheet is
71 * allowed by its hash, and nothing else is.
72 */
73export async function denied(status: number, title: string, message: string): Promise<Response> {
74 const html = `<!doctype html><html lang="en"><head><meta charset="utf-8">
75<meta name="viewport" content="width=device-width, initial-scale=1"><meta name="robots" content="noindex, nofollow">
76<title>${escapeHtml(title)} · sudo</title><style>${DENIED_STYLE}</style></head>
77<body><main><span class="badge">sudo</span><h1>${escapeHtml(title)}</h1><p>${escapeHtml(message)}</p></main></body></html>`;
78 return new Response(html, {
79 status,
80 headers: {
81 "content-type": "text/html; charset=utf-8",
82 "content-security-policy": `default-src 'none'; style-src 'sha256-${await hashOfStyle()}'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'`,
83 },
84 });
85}

This file's history is long; its oldest lines are credited to the oldest commit read.