Security Headers

Security Headers

Security headers are set inline via the securityMiddleware in src/start.ts. There is no separate middleware file.

Headers set

HeaderValueNotes
Strict-Transport-Securitymax-age=31536000; includeSubDomains; preloadProduction only (HSTS)
X-Content-Type-Optionsnosniff
X-Frame-OptionsDENYProduction only
Referrer-Policystrict-origin-when-cross-origin
Permissions-PolicyDisables camera, microphone, geolocation, payment
Cross-Origin-Opener-Policysame-origin
Content-Security-PolicyConfigured per appIncludes Cloudflare Turnstile, Crisp, Umami, Sentry

Headers intentionally NOT set

X-XSS-Protection is not set. This header is deprecated by modern browsers (Chrome removed it, Firefox never supported it). Browsers that still support it default to 1; mode=block anyway. CSP provides superior XSS protection.

Customizing

Edit src/start.ts — the securityMiddleware handler. Adjust or remove headers as needed, except CSP (see below).

Content-Security-Policy

CSP directives live in src/server/csp.ts, not inline in start.ts — a first-class file specifically so adding a third-party origin is a one-line diff to a directive array instead of a change buried in header-setting boilerplate. buildCsp(isProduction) returns the joined header string; securityMiddleware in start.ts just calls it.

The base directives allow-list the specific third-party origins this template ships with integrations for: Cloudflare Turnstile (challenges.cloudflare.com) and Cloudflare's own edge-injected analytics beacon (static.cloudflareinsights.com, cloudflareinsights.com), Microsoft Clarity (www.clarity.ms, scripts.clarity.ms, plus its e.clarity.ms/n.clarity.ms report endpoints in connect-src), Crisp chat (*.crisp.chat, client.crisp.chat, game.crisp.chat), Umami analytics (cloud.umami.is), and Sentry (*.sentry.io). 'unsafe-eval' and worker-src blob: are added only outside production (Vite dev server needs them). frame-ancestors 'none' is added only in production, matching X-Frame-Options: DENY.

Adding a new third-party script/embed requires adding its origin to the relevant directive array in src/server/csp.ts — a CSP violation shows up as a silent browser-console block, not a visible error. src/server/csp.test.ts covers the prod/non-prod branching; it does not (and can't) catch a missing origin, since that's a product decision, not a logic bug.

Request IDs

Every request gets a unique X-Request-Id header via requestIdMiddleware in src/start.ts. The ID is available in route handlers via event.context.requestId and included in structured logs.

CORS

No CORS headers are set anywhere in the codebase, and none are configured. The SSR app serves the API from the same origin as the frontend, so cross-origin requests never need to succeed , if you add a public API consumed from another origin, add explicit CORS headers for that route rather than a blanket policy.