import { FastifyInstance } from "fastify"; import fp from "fastify-plugin"; async function securityPlugin(app: FastifyInstance) { app.addHook("onSend", async (req, reply) => { const isEmbed = req.url.startsWith("/api/v1/embed/") || req.url.startsWith("/embed/"); const isAsset = req.url.startsWith("/assets/") || req.url.startsWith("/favicon") || req.url.startsWith("/icon-") || req.url.endsWith(".js") || req.url.endsWith(".css"); if (isEmbed || isAsset) { // embed routes need to be frameable by third-party sites reply.header("Content-Security-Policy", [ "default-src 'self'", "script-src 'self'", "style-src 'self' 'unsafe-inline'", "img-src 'self' data:", "font-src 'self'", "connect-src 'self'", "frame-src 'none'", "object-src 'none'", "frame-ancestors *", "base-uri 'self'", "form-action 'none'", ].join("; ")); reply.header("Cross-Origin-Resource-Policy", "cross-origin"); reply.header("Access-Control-Allow-Origin", "*"); reply.header("Access-Control-Allow-Credentials", "false"); } else { reply.header("Content-Security-Policy", [ "default-src 'self'", "script-src 'self'", "style-src 'self' 'unsafe-inline'", "img-src 'self' data:", "font-src 'self'", "connect-src 'self'", "frame-src 'none'", "object-src 'none'", "frame-ancestors 'none'", "base-uri 'self'", "form-action 'self'", ].join("; ")); reply.header("X-Frame-Options", "DENY"); reply.header("Cross-Origin-Opener-Policy", "same-origin"); reply.header("Cross-Origin-Resource-Policy", "same-origin"); } reply.header("Referrer-Policy", "no-referrer"); reply.header("X-Content-Type-Options", "nosniff"); reply.header("Strict-Transport-Security", "max-age=63072000; includeSubDomains; preload"); reply.header("Permissions-Policy", "camera=(), microphone=(), geolocation=(), interest-cohort=()"); reply.header("X-DNS-Prefetch-Control", "off"); }); } export default fp(securityPlugin, { name: "security" });