export const CONTENT_SECURITY_POLICY = [ "default-src 'self'", "base-uri 'self'", "object-src 'none'", "frame-ancestors 'none'", "frame-src 'none'", "form-action 'self'", "script-src 'self' 'unsafe-inline'", "style-src 'self' 'unsafe-inline'", "img-src 'self' data: blob:", "font-src 'self'", "connect-src 'self'", "manifest-src 'self'", "worker-src 'self' blob:", "media-src 'none'", ].join("; "); export const PERMISSIONS_POLICY = [ "accelerometer=()", "autoplay=()", "camera=()", "display-capture=()", "encrypted-media=()", "geolocation=()", "gyroscope=()", "magnetometer=()", "microphone=()", "midi=()", "payment=()", "picture-in-picture=()", "screen-wake-lock=()", "serial=()", "usb=()", ].join(", "); const SECURITY_HEADERS = { "Content-Security-Policy": CONTENT_SECURITY_POLICY, "Permissions-Policy": PERMISSIONS_POLICY, "Referrer-Policy": "no-referrer", "X-Content-Type-Options": "nosniff", "X-DNS-Prefetch-Control": "off", "X-Frame-Options": "DENY", "X-Permitted-Cross-Domain-Policies": "none", } as const; export function withSecurityHeaders( request: Request, response: Response, ): Response { // A WebSocket upgrade response cannot be reconstructed with the standard // Response constructor. The current Cloud control plane has no WS route; // preserve future upgrades rather than accidentally stripping the socket. if (response.status === 101) return response; const url = new URL(request.url); const headers = new Headers(response.headers); for (const [name, value] of Object.entries(SECURITY_HEADERS)) { headers.set(name, value); } if ( url.pathname === "/api" || url.pathname.startsWith("/api/") || url.pathname === "/dashboard" || url.pathname.startsWith("/dashboard/") || url.pathname === "/admin" || url.pathname.startsWith("/admin/") ) { headers.set("Cache-Control", "private, no-store"); } if (url.protocol === "https:") { headers.set("Strict-Transport-Security", "max-age=31536000"); } else { headers.delete("Strict-Transport-Security"); } return new Response(response.body, { status: response.status, statusText: response.statusText, headers, }); }