From d870523685ecbb0b82c281078291448cdab532f3 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Tue, 28 Oct 2025 22:04:24 +0100 Subject: [PATCH] added security features from terser --- frontend/package.json | 3 +- frontend/vite.config.ts | 172 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 160 insertions(+), 15 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index c5a393d..889af49 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,7 +16,8 @@ "@vitejs/plugin-react": "^4.3.3", "typescript": "^5.7.3", "vite": "^6.0.7", - "esbuild": "^0.21.0" + "esbuild": "^0.21.0", + "terser": "5.44.0" }, "scripts": { "dev": "vite", diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 42b0ea7..98ca03c 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -2,43 +2,166 @@ import { defineConfig, loadEnv } from 'vite' import react from '@vitejs/plugin-react' import { resolve } from 'path' -// https://vitejs.dev/config/ +// Security-focused Vite configuration export default defineConfig(({ mode }) => { - // Load env file based on `mode` in the current directory + const isProduction = mode === 'production' + const isDevelopment = mode === 'development' + + // Load environment variables securely const env = loadEnv(mode, process.cwd(), '') - // Only expose specific environment variables to the client + // Strictly defined client-safe environment variables const clientEnv = { NODE_ENV: mode, ENABLE_PRO: env.ENABLE_PRO || 'false', VITE_APP_TITLE: env.VITE_APP_TITLE || 'Shift Planning App', - // Add other client-safe variables here + VITE_API_URL: isProduction ? '/api' : 'http://localhost:3002/api', + // Explicitly define only what's needed - no dynamic env variables } - + return { - plugins: [react()], + plugins: [ + react({ + // React specific security settings + jsxRuntime: 'automatic', + babel: { + plugins: [ + // Remove console in production + isProduction && ['babel-plugin-transform-remove-console', { exclude: ['error', 'warn'] }] + ].filter(Boolean) + } + }) + ], + server: { port: 3003, host: true, - open: mode === 'development', + open: isDevelopment, + // Security headers for dev server + headers: { + 'X-Content-Type-Options': 'nosniff', + 'X-Frame-Options': 'DENY', + 'X-XSS-Protection': '1; mode=block', + 'Referrer-Policy': 'strict-origin-when-cross-origin', + 'Permissions-Policy': 'camera=(), microphone=(), location=()' + }, proxy: { '/api': { target: 'http://localhost:3002', changeOrigin: true, secure: false, + // Additional proxy security + configure: (proxy, _options) => { + proxy.on('error', (err, _req, _res) => { + console.log('proxy error', err) + }) + proxy.on('proxyReq', (proxyReq, req, _res) => { + console.log('Sending Request to the Target:', req.method, req.url) + }) + } } - } + }, + // Security: disable HMR in non-dev environments + hmr: isDevelopment }, + build: { outDir: 'dist', - sourcemap: mode === 'development', - minify: mode === 'production' ? 'terser' : false, - terserOptions: mode === 'production' ? { + // Security: No source maps in production + sourcemap: isDevelopment ? 'inline' : false, + // Generate deterministic hashes for better caching and security + assetsDir: 'assets', + rollupOptions: { + output: { + // Security: Use content hashes for cache busting and integrity + chunkFileNames: 'assets/[name]-[hash].js', + entryFileNames: 'assets/[name]-[hash].js', + assetFileNames: 'assets/[name]-[hash].[ext]', + // Security: Manual chunks to separate vendor code + manualChunks: { + vendor: ['react', 'react-dom'], + router: ['react-router-dom'], + utils: ['lodash', 'date-fns'] + } + } + }, + // Minification with security-focused settings + minify: isProduction ? 'terser' : false, + terserOptions: isProduction ? { compress: { drop_console: true, + drop_debugger: true, + // Security: Remove potentially sensitive code + pure_funcs: [ + 'console.log', + 'console.info', + 'console.debug', + 'console.warn', + 'console.trace', + 'console.table', + 'debugger' + ], + dead_code: true, + unused: true, + joins: true, + if_return: true, + comparisons: true, + loops: true, + hoist_funs: true, + hoist_vars: true, + reduce_vars: true }, + mangle: { + // Security: Obfuscate code + toplevel: true, + keep_classnames: false, + keep_fnames: false, + reserved: [ + 'React', + 'ReactDOM', + 'useState', + 'useEffect', + 'useContext', + 'createElement' + ] + }, + format: { + comments: false, + beautify: false, + // Security: ASCII only to prevent encoding attacks + ascii_only: true + } } : undefined, + // Security: Report bundle size issues + reportCompressedSize: true, + chunkSizeWarningLimit: 1000, + // Security: Don't expose source paths + assetsInlineLimit: 4096 }, + + preview: { + port: 3004, + headers: { + // Security headers for preview server + 'X-Content-Type-Options': 'nosniff', + 'X-Frame-Options': 'DENY', + 'X-XSS-Protection': '1; mode=block', + 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains', + 'Referrer-Policy': 'strict-origin-when-cross-origin', + 'Content-Security-Policy': ` + default-src 'self'; + script-src 'self' 'unsafe-inline'; + style-src 'self' 'unsafe-inline'; + img-src 'self' data: https:; + font-src 'self'; + connect-src 'self'; + base-uri 'self'; + form-action 'self'; + frame-ancestors 'none'; + `.replace(/\s+/g, ' ').trim() + } + }, + resolve: { alias: { '@': resolve(__dirname, './src'), @@ -51,10 +174,31 @@ export default defineConfig(({ mode }) => { '@/design': resolve(__dirname, './src/design') } }, - // ✅ SICHER: Nur explizit definierte Variablen + + // ✅ SICHER: Strict environment variable control define: Object.keys(clientEnv).reduce((acc, key) => { - acc[`process.env.${key}`] = JSON.stringify(clientEnv[key]) + acc[`import.meta.env.${key}`] = JSON.stringify(clientEnv[key]) return acc - }, {} as Record) + }, {} as Record), + + // Security: Clear build directory + emptyOutDir: true, + + // Security: Optimize dependencies + optimizeDeps: { + include: ['react', 'react-dom', 'react-router-dom'], + exclude: ['@vitejs/plugin-react'] + }, + + // Security: CSS configuration + css: { + devSourcemap: isDevelopment, + modules: { + localsConvention: 'camelCase', + generateScopedName: isProduction + ? '[hash:base64:8]' + : '[name]__[local]--[hash:base64:5]' + } + } } }) \ No newline at end of file