berrypod/docs/plans/security-hardening.md
jamey 0c2d4ac406
Some checks failed
deploy / deploy (push) Failing after 8m33s
add rate limiting and HSTS for security hardening
- Add Hammer library for rate limiting with ETS backend
- Rate limit login (5/min), magic link (3/min), newsletter (10/min), API (60/min)
- Add themed 429 error page using bare shop styling
- Enable HSTS in production with rewrite_on for Fly proxy
- Add security hardening plan to docs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-08 08:58:43 +00:00

8.6 KiB

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:

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 <script> tags
  • Remove on* event handlers (onclick, onload, etc.)
  • Remove <foreignObject> (can contain HTML)
  • Remove javascript: URLs in href/xlink:href

Use a simple regex-based sanitiser (SVGs are simple enough that a full parser is overkill). Run on upload, store sanitised version.

2.3 Comprehensive audit logging

Extend activity log to capture all admin actions:

Event Data
Admin login IP, user agent, success/failure
Settings changed Which setting, old/new value (secrets masked)
Theme changed Which property
Page edited Page slug, blocks changed
Order status changed Order number, old/new status
Provider credentials updated Provider name
Image uploaded/deleted Image ID, filename

Store in existing activity_log table. Add retention policy (90 days default).

2.4 Session encryption

Add encryption to session cookies:

@session_options [
  store: :cookie,
  key: "_berrypod_key",
  signing_salt: "JNwRcD7y",
  encryption_salt: "zKp4Q1vE",  # Add this
  same_site: "Lax",
  max_age: 604_800
]

Sessions will be both signed (integrity) and encrypted (confidentiality).

Phase 3: Advanced (future)

Not in this plan but noted for future:

  • WebAuthn/passkey support
  • Automated security scanning in CI
  • Penetration testing
  • Database encryption at rest
  • Login anomaly detection (geo, device fingerprint)

Changes

Phase 1

File Change
mix.exs Add hammer, hammer_ets, nimble_totp deps
lib/berrypod/application.ex Start Hammer supervisor
lib/berrypod_web/plugs/rate_limit.ex New — rate limit plug
lib/berrypod_web/router.ex Add rate limit plug to pipelines
config/runtime.exs Enable force_ssl with HSTS
lib/berrypod/accounts/user_totp.ex New — TOTP schema
lib/berrypod/accounts.ex Add TOTP functions
lib/berrypod_web/live/auth/totp_setup.ex New — 2FA setup LiveView
lib/berrypod_web/live/auth/totp_verify.ex New — 2FA challenge LiveView
lib/berrypod_web/user_auth.ex Integrate TOTP verification in login flow
lib/berrypod_web/live/admin/settings.ex Add 2FA setup section
Migration Add user_totp table
Tests Rate limit plug, TOTP setup/verify, login with 2FA

Phase 2

File Change
lib/berrypod_web/plugs/content_security_policy.ex New — CSP plug
lib/berrypod_web/router.ex Add CSP plug to browser pipeline
lib/berrypod/media/svg_sanitiser.ex New — SVG sanitisation
lib/berrypod/media.ex Call sanitiser on SVG upload
lib/berrypod/activity_log.ex Add new event types
lib/berrypod_web/endpoint.ex Add encryption_salt to session options
Tests CSP headers, SVG sanitisation, audit events

Tasks

Phase 1: Essentials

# Task Est Status
1 Add hammer and hammer_ets deps, start supervisor 30m planned
2 Create rate limit plug with configurable limits 1h planned
3 Apply rate limiting to login, magic link, API, newsletter 1h planned
4 Enable HSTS in production config 15m planned
5 Add nimble_totp dep, create user_totp schema and migration 45m planned
6 Add TOTP functions to Accounts context 1h planned
7 Create TOTP setup LiveView (QR code, verification, backup codes) 2h planned
8 Create TOTP verification LiveView (login challenge) 1h planned
9 Integrate TOTP into login flow 1.5h planned
10 Add 2FA section to admin settings page 1h planned
11 Tests for rate limiting 1h planned
12 Tests for TOTP setup and verification 1.5h planned

Phase 2: Hardening

# Task Est Status
13 Create CSP plug with comprehensive policy 1h planned
14 Create SVG sanitiser module 1.5h planned
15 Integrate SVG sanitiser into upload flow 30m planned
16 Extend activity log with comprehensive admin events 2h planned
17 Add encryption_salt to session config 15m planned
18 Tests for CSP, SVG sanitiser, audit events 2h planned

Total estimate: ~18 hours

Verification

Phase 1

  1. Rate limiting: Rapid login attempts return 429 after limit exceeded
  2. HSTS: curl -I shows strict-transport-security header in production
  3. 2FA setup: QR code scans in authenticator app, codes verify
  4. 2FA login: After password, prompted for TOTP code
  5. Backup codes: Can disable 2FA using backup code
  6. mix precommit passes

Phase 2

  1. CSP: Browser dev tools show full CSP header on responses
  2. SVG sanitisation: Uploaded SVG with <script> has it stripped
  3. Audit log: Settings change shows in activity log with before/after
  4. Session encryption: Cookie is encrypted (not readable without secret)
  5. mix precommit passes

Security claims checklist

After completing this plan, Berrypod can legitimately claim:

  • No plugin vulnerabilities (architectural)
  • No filesystem code execution (architectural)
  • SQL injection prevented (Ecto)
  • CSRF protection built-in (Phoenix)
  • Bcrypt password hashing
  • Timing-attack-safe authentication
  • Encrypted secrets storage
  • Two-factor authentication (Phase 1)
  • Rate limiting on auth endpoints (Phase 1)
  • HSTS enforced (Phase 1)
  • Comprehensive CSP headers (Phase 2)
  • SVG upload sanitisation (Phase 2)
  • Full admin audit trail (Phase 2)
  • Encrypted session cookies (Phase 2)

This positions Berrypod as genuinely more secure than a typical WordPress/WooCommerce installation, with much lower ongoing maintenance burden.