### Security & Privacy Audit (EU/GDPR) Scope: Current Astro site; no server endpoints yet; React islands used. Aim: strict security headers, EU analytics, consent only if needed, secrets hygiene, safe forms, and payment integration readiness. --- ### Executive summary - **Strengths**: Static-first Astro; no server auth; minimal third parties. - **Gaps**: External Google Fonts (tracking and CSP issues), inline scripts/styles, no security headers configured, forms lack server validation/rate-limiting/CSRF, no cookie/consent model, no analytics choice, donation flow not integrated with EU processor. --- ### Action checklist (priority) 1) Remove Google Fonts; self-host Inter variable - File: `src/styles/global.css` - Replace `@import` from Google with self-hosted font. Steps: - Add `public/fonts/inter/Inter-Variable.woff2`. - Add to `src/styles/global.css`: ```css @font-face { font-family: 'Inter'; src: url('/fonts/inter/Inter-Variable.woff2') format('woff2-variations'); font-weight: 100 900; font-style: normal; font-display: swap; } ``` - Rationale: Privacy, performance, CSP friendliness. 2) Security headers - Option A: Static hosting `_headers` (e.g., Netlify/Vercel): ```text /* Strict-Transport-Security: max-age=31536000; includeSubDomains; preload X-Content-Type-Options: nosniff Referrer-Policy: strict-origin-when-cross-origin Permissions-Policy: geolocation=(), microphone=(), camera=(), payment=() Cross-Origin-Opener-Policy: same-origin Cross-Origin-Embedder-Policy: require-corp; report-to="default" Cross-Origin-Resource-Policy: same-origin Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-PLACEHOLDER'; style-src 'self' 'nonce-PLACEHOLDER'; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self' ``` - Option B: Nginx snippet (`nginx.conf.example`): ```nginx add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; add_header X-Content-Type-Options nosniff; add_header Referrer-Policy "strict-origin-when-cross-origin"; add_header Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=()"; add_header Cross-Origin-Opener-Policy "same-origin"; add_header Cross-Origin-Embedder-Policy "require-corp"; add_header Cross-Origin-Resource-Policy "same-origin"; add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-$request_id'; style-src 'self' 'nonce-$request_id'; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"; ``` - Rationale: Baseline browser protections; strict CSP with nonces for inline modules. 3) Inline → external scripts/styles to satisfy CSP - Files: `src/layouts/BaseLayout.astro` (inline `