82 lines
2.2 KiB
TypeScript
82 lines
2.2 KiB
TypeScript
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,
|
|
});
|
|
}
|