diff --git a/components/frontend/next.config.js b/components/frontend/next.config.js index bbab259ea..9e8fe5203 100644 --- a/components/frontend/next.config.js +++ b/components/frontend/next.config.js @@ -1,6 +1,57 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - output: 'standalone' + output: 'standalone', + + async headers() { + return [ + { + // Apply security headers to all routes + source: '/:path*', + headers: [ + // Content Security Policy + // - script-src: Allow 'self' scripts only (next-themes handles dark mode without inline scripts) + // - style-src: Allow 'self' and 'unsafe-inline' (required for Tailwind CSS) + // - connect-src: Allow 'self' and WebSocket connections (ws:/wss:) for backend communication + // - default-src: Restrict all other resources to same-origin only + { + key: 'Content-Security-Policy', + value: [ + "default-src 'self'", + "script-src 'self'", + "style-src 'self' 'unsafe-inline'", + "connect-src 'self' ws: wss:", + "img-src 'self' data: blob:", + "font-src 'self' data:", + "object-src 'none'", + "base-uri 'self'", + "form-action 'self'", + "frame-ancestors 'none'", + ].join('; '), + }, + // Prevent clickjacking attacks by disallowing iframe embedding + { + key: 'X-Frame-Options', + value: 'DENY', + }, + // Prevent MIME type sniffing + { + key: 'X-Content-Type-Options', + value: 'nosniff', + }, + // Control referrer information sent with requests + { + key: 'Referrer-Policy', + value: 'strict-origin-when-cross-origin', + }, + // Restrict browser features and APIs + { + key: 'Permissions-Policy', + value: 'camera=(), microphone=(), geolocation=(), interest-cohort=()', + }, + ], + }, + ]; + }, } module.exports = nextConfig diff --git a/components/frontend/package.json b/components/frontend/package.json index f5931698c..0befba125 100644 --- a/components/frontend/package.json +++ b/components/frontend/package.json @@ -29,6 +29,7 @@ "highlight.js": "^11.11.1", "lucide-react": "^0.542.0", "next": "15.5.2", + "next-themes": "^0.4.4", "react": "19.1.0", "react-dom": "19.1.0", "react-hook-form": "^7.62.0", diff --git a/components/frontend/src/app/layout.tsx b/components/frontend/src/app/layout.tsx index ed7e98006..5c90a7ff3 100644 --- a/components/frontend/src/app/layout.tsx +++ b/components/frontend/src/app/layout.tsx @@ -3,6 +3,7 @@ import { Inter } from "next/font/google"; import "./globals.css"; import { Navigation } from "@/components/navigation"; import { QueryProvider } from "@/components/providers/query-provider"; +import { ThemeProvider } from "@/components/providers/theme-provider"; import { Toaster } from "@/components/ui/toaster"; import { env } from "@/lib/env"; @@ -27,11 +28,18 @@ export default function RootLayout({
-