server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; # ── Security Headers ───────────────────────────────────────────── add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "DENY" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; add_header X-XSS-Protection "0" always; # HSTS — uncomment when using HTTPS (recommended in production) # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # CSP: allow self + inline styles (React build) + data: URIs for fonts/images add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self';" always; # Hide Nginx version server_tokens off; # ── Gzip compression ───────────────────────────────────────────── gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml; # ── SPA routing ────────────────────────────────────────────────── location / { try_files $uri $uri/ /index.html; } # ── Cache static assets ────────────────────────────────────────── location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ { expires 1y; add_header Cache-Control "public, immutable"; } # ── Proxy API requests to backend ──────────────────────────────── location /api/ { proxy_pass http://backend:8000/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # Long-running operations (MITRE sync, data source imports) need more time proxy_read_timeout 300s; proxy_connect_timeout 10s; proxy_send_timeout 300s; } # ── Health endpoint proxy (internal only) ──────────────────────── location /health { # Only allow health checks from Docker internal network and localhost allow 127.0.0.1; allow 10.0.0.0/8; allow 172.16.0.0/12; allow 192.168.0.0/16; deny all; proxy_pass http://backend:8000/health; } }