diff --git a/next.config.ts b/next.config.ts index 434135c..9880641 100644 --- a/next.config.ts +++ b/next.config.ts @@ -3,8 +3,36 @@ import createNextIntlPlugin from "next-intl/plugin"; const withNextIntl = createNextIntlPlugin(); +// Baseline security headers applied to every response. Deliberately no +// Content-Security-Policy here: a correct CSP for this app (inline theme +// script, Google Fonts, Analytics, Turnstile, map tiles) needs to be built +// and tested against the running app, so it is left as a dedicated follow-up +// rather than shipped as a guess that could break rendering. +const securityHeaders = [ + // Force HTTPS for two years, including subdomains, and allow preload-list + // submission. + { + key: "Strict-Transport-Security", + value: "max-age=63072000; includeSubDomains; preload", + }, + // Stop browsers from MIME-sniffing responses away from the declared type. + { key: "X-Content-Type-Options", value: "nosniff" }, + // Disallow framing by other origins (clickjacking protection). + { key: "X-Frame-Options", value: "SAMEORIGIN" }, + // Send only the origin on cross-origin navigations; full URL same-origin. + { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" }, + // Explicitly deny powerful features the site does not use. + { + key: "Permissions-Policy", + value: "camera=(), microphone=(), geolocation=(), browsing-topics=()", + }, +]; + const nextConfig: NextConfig = { output: "standalone", + async headers() { + return [{ source: "/:path*", headers: securityHeaders }]; + }, }; export default withNextIntl(nextConfig);