Files
plainpages/src/http/security-headers.ts
T
lilleman a005acb93d
CI / full-gate (push) Successful in 2m38s
Cut non-essential prose from docs and comments, and require the same of every future change
README loses the competitor comparison, the personas and the repeated philosophy; the
five near-identical E2E command blocks become a table plus one command, and the file
map a clause per entry. AGENTS.md keeps every decision but drops the narrative around
them. todo.md's completed items collapse to their task line — git holds the rest.

Comments lose restatement, README duplication and history ("used to", "originally",
dated notes). AGENTS.md gains a Prose discipline section making this a standing pass on
every change rather than a one-off cleanup.

src/compose.test.ts now expects 6 documented E2E run commands, not 10, since the README
states the command once instead of per suite.
2026-08-05 23:41:12 +02:00

35 lines
1.4 KiB
TypeScript

// Set once per request in app.ts, so every response carries them (writeHead merges with setHeader).
// A plugin route may override any per-response via RouteResult.headers.
// The non-obvious parts of the CSP:
// - script-src 'self' with no 'unsafe-inline' ⇒ an injected <script> can't run. A plugin may still
// serve its own /public/<id>/*.js for opt-in progressive enhancement.
// - style-src adds 'unsafe-inline': a few partials carry inline style= attributes.
// - no form-action: the themed login form posts to Kratos' (often cross-origin) action URL.
const CSP = [
"base-uri 'self'",
"default-src 'self'",
"frame-ancestors 'none'",
"img-src 'self' data:",
"object-src 'none'",
"script-src 'self'",
"style-src 'self' 'unsafe-inline'",
].join("; ");
export interface SecurityHeaderOptions {
secure?: boolean; // https deployment (mirrors SECURE_COOKIES) → also emit HSTS
}
export function securityHeaders(options: SecurityHeaderOptions = {}): Record<string, string> {
const headers: Record<string, string> = {
"content-security-policy": CSP,
"cross-origin-opener-policy": "same-origin",
"referrer-policy": "strict-origin-when-cross-origin",
"x-content-type-options": "nosniff",
"x-frame-options": "DENY",
};
// HSTS only over https — ignored (and meaningless) on the dev http origin.
if (options.secure) headers["strict-transport-security"] = "max-age=31536000; includeSubDomains";
return headers;
}