Self-hosting security hardening for AI Coach.
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"Set in config.yaml or env:
export SECRET_KEY="your-generated-key"openssl rand -base64 32Set in web/.env.local:
NEXTAUTH_SECRET=your-generated-secret
NEXTAUTH_URL=https://your-domain.com
- Generate new key
- Update env var / config
- Restart services — active sessions will be invalidated
- For Fernet-encrypted data, decrypt with old key then re-encrypt with new key
UFW:
ufw default deny incoming
ufw allow ssh
ufw allow 443/tcp
ufw enablefirewalld:
firewall-cmd --permanent --add-service=https
firewall-cmd --permanent --remove-service=http
firewall-cmd --reloadyour-domain.com {
reverse_proxy localhost:3000
header {
Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
X-Frame-Options "DENY"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
Permissions-Policy "camera=(), microphone=(), geolocation=()"
}
}
Caddy handles TLS certs automatically via Let's Encrypt. Internal API (port 8000) should NOT be exposed externally.
chmod 700 ~/coach
chmod 600 ~/coach/config.yaml
chmod 600 ~/coach/intel.db
chmod 600 ~/coach/journal/*# SQLite safe backup (while running)
sqlite3 ~/coach/intel.db ".backup ~/coach/backups/intel-$(date +%F).db"
# Journal + config
tar czf ~/coach/backups/coach-$(date +%F).tar.gz \
~/coach/journal/ ~/coach/config.yaml ~/coach/recommendations/- Store in env vars or config.yaml (chmod 600), never in code
- Keys are redacted from structlog output automatically
- Use separate keys for dev/prod if provider supports it
- Dev:
http://localhost:3000/api/auth/callback/<provider> - Prod:
https://your-domain.com/api/auth/callback/<provider>
Create separate OAuth apps for dev and prod. Never reuse client secrets across environments.
- Private (recommended): VPN/tailnet access only. OAuth optional, can use simple token auth
- Public: OAuth required. Ensure callback URLs are HTTPS. Enable CSRF protection
NEXTAUTH_SECRETmust be cryptographically random (32+ bytes)- Sessions expire after 30 days by default (configurable in
[...nextauth].ts) - Cookies set with
httpOnly,secure,sameSite=lax
Structlog is configured to redact patterns matching API keys. Verify with:
coach daemon 2>&1 | grep -i "key\|secret\|token"Should show [REDACTED] for any sensitive values.
GET /api/intel/health returns scraper status (auth-protected). Monitor for:
consecutive_errors >= 3— source may be down or rate-limitedlast_success_atolder than 72h — check network/configbackoff_untilin future — automatic recovery pending
Use external monitoring (e.g., uptime checker on /api/intel/health) for alerting.