# Security hardening Status: Planned ## Context Berrypod is designed as a simpler, safer alternative to WordPress/WooCommerce for print-on-demand sellers. Compared to WordPress, it has significant architectural advantages: - No plugin ecosystem = no plugin vulnerabilities - Compiled Elixir binary = no code injection - Image uploads stored as database BLOBs = no filesystem execution risk - Ecto changesets = parameterised queries, SQL injection structurally prevented - Built-in CSRF via Phoenix = consistent protection - Encrypted secrets in database = no plaintext credentials However, to make security a genuine selling point, several gaps need addressing. This plan covers three phases of hardening to bring Berrypod to best-in-class security. ## Current state (audit summary) | Area | Current | Gap | |------|---------|-----| | Password hashing | Bcrypt with timing attack prevention | OK | | Session management | Token-based, 7-day rotation, signed cookies | OK | | Magic links | SHA256 hashed, 15-min expiry | OK | | 2FA | None | **Critical gap** | | Rate limiting | None | **Critical gap** | | HSTS | Commented out in config | **Should enable** | | CSP | Basic (`frame-ancestors`, `base-uri` only) | Could be stronger | | SVG uploads | Stored safely, recolored via validated params | Minor XSS risk | | Audit logging | Activity log exists but not comprehensive | Enhancement | | Session encryption | Signed but not encrypted | Enhancement | ## Design ### Phase 1: Essentials **1.1 Rate limiting** Add rate limiting using the `hammer` library. Apply to: - Login attempts: 5 per minute per IP - Magic link requests: 3 per minute per email - API endpoints: 60 per minute per IP - Newsletter signup: 10 per minute per IP Implementation via a `RateLimitPlug` that checks `Hammer.check_rate/3` and returns 429 on breach. **1.2 HSTS headers** Enable `force_ssl` in production config with HSTS headers: ```elixir config :berrypod, BerrypodWeb.Endpoint, force_ssl: [hsts: true, rewrite_on: [:x_forwarded_proto]] ``` The `rewrite_on` handles Fly.io's SSL termination at the proxy. **1.3 Two-factor authentication (TOTP)** Add TOTP-based 2FA for the admin account: - New schema: `user_totp` (secret, enabled_at, backup_codes) - Setup flow: show QR code, verify code, generate backup codes - Login flow: after password, prompt for TOTP code - Backup codes: 8 single-use codes, stored hashed - Recovery: use backup code to disable 2FA and re-setup Libraries: `nimble_totp` for TOTP generation/verification. ### Phase 2: Hardening **2.1 Comprehensive CSP** Extend CSP headers to restrict script/style sources: ``` default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self'; connect-src 'self' wss:; frame-ancestors 'self'; base-uri 'self'; form-action 'self'; ``` Note: `'unsafe-inline'` for styles is required because we inject theme CSS variables dynamically. Could use nonces but adds complexity. **2.2 SVG sanitisation** When uploading SVGs, strip potentially dangerous elements: - Remove `