This document outlines the security features implemented in the HSA/FSA Tracker and best practices for secure deployment.
- Security Features
- Installation & Setup
- Production Deployment
- Security Best Practices
- Reporting Security Issues
Features:
- Secure password hashing using Werkzeug's
generate_password_hash() - Session-based authentication with HttpOnly cookies
- SameSite cookie attribute for CSRF protection
- 24-hour session lifetime
- Secure session cookies in production (HTTPS-only)
Configuration:
app.config['SESSION_COOKIE_HTTPONLY'] = True # Prevents JavaScript access
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' # CSRF protection
app.config['SESSION_COOKIE_SECURE'] = True # HTTPS only (production)
app.config['PERMANENT_SESSION_LIFETIME'] = 86400 # 24 hoursImplementation: Flask-WTF
- All POST/PUT/DELETE/PATCH requests require CSRF tokens
- Tokens included via meta tag in HTML templates
- JavaScript helper function
addCSRFToken()for AJAX requests
Usage in Forms:
<form method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<!-- form fields -->
</form>Usage in JavaScript:
fetch('/api/endpoint', addCSRFToken({
method: 'POST',
body: formData
}))Implementation: Flask-Limiter
- Default limits: 200 requests/day, 50 requests/hour per IP
- Login endpoint: 5 attempts per minute
- Registration endpoint: 3 attempts per hour
Configuration:
limiter = Limiter(
app=app,
key_func=get_remote_address,
default_limits=["200 per day", "50 per hour"],
storage_uri="memory://",
strategy="fixed-window"
)Security:
- Required SECRET_KEY environment variable in production
- Random key generation for development
- Application refuses to start if SECRET_KEY missing in production
Setup:
# Generate a secure secret key
python -c 'import secrets; print(secrets.token_hex(32))'
# Add to .env file
echo "SECRET_KEY=<generated-key>" >> .envDatabase Security:
- All queries filtered by
user_id - Parameterized queries prevent SQL injection
- Foreign key constraints with CASCADE deletes
- User-specific file storage directories
Example:
expense = conn.execute('''
SELECT e.* FROM expenses e
WHERE e.id = ? AND e.user_id = ?
''', (expense_id, user_id)).fetchone()NEW: Enhanced Data Protection
The application now supports optional encryption for both database and file storage:
Features:
- Database Encryption: SQLCipher-based 256-bit AES encryption for the SQLite database
- File Encryption: Fernet (symmetric) encryption for all uploaded receipts and attachments
- Backward Compatible: Works with existing unencrypted installations
- Migration Support: Automated migration script for existing data
Benefits:
- Protects sensitive financial data at rest
- Encrypts all receipts and proof-of-reimbursement documents
- Makes stolen database/files useless without encryption keys
- Recommended for production deployments
Requirements:
- Python 3.11 or lower (SQLCipher not compatible with Python 3.12+)
- The Docker image is locked to Python 3.11 for compatibility
Setup:
- Generate encryption keys:
python app/utils/encryption.py- Add keys to
.env:
DB_ENCRYPTION_KEY=<your-db-key>
FILE_ENCRYPTION_KEY=<your-file-key>- Restart the application - encryption is automatic once keys are set
CRITICAL SECURITY NOTES:
⚠️ Backup encryption keys separately from data!⚠️ If keys are lost, encrypted data CANNOT be recovered!⚠️ Store keys in a secure secrets manager for production⚠️ Never commit encryption keys to version control
Docker Setup:
environment:
- DB_ENCRYPTION_KEY=${DB_ENCRYPTION_KEY}
- FILE_ENCRYPTION_KEY=${FILE_ENCRYPTION_KEY}Protections:
- Whitelist validation: Only PDF, JPG, JPEG, PNG allowed
- 16MB file size limit
- User-specific storage directories
- Ownership verification before file access
- Optional file encryption (see Encryption section)
Configuration:
ALLOWED_EXTENSIONS = {'pdf', 'jpg', 'jpeg', 'png'}
MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16MBpip install -r requirements.txt- Flask==3.0.0
- Werkzeug==3.0.1
- python-dotenv==1.0.0
- pytz==2024.1
- Flask-WTF==1.2.1 (CSRF protection)
- Flask-Limiter==3.5.0 (Rate limiting)
-
Copy the example environment file:
cp .env.example .env
-
Generate a secure secret key:
python -c 'import secrets; print(secrets.token_hex(32))' -
Update .env file:
FLASK_ENV=development SECRET_KEY=<your-generated-secret-key> PORT=5000
cd app
python database.pyRequired:
FLASK_ENV=production
SECRET_KEY=<strong-random-secret-key>Optional:
PORT=5000Option A: Using a Reverse Proxy (Recommended)
Example Nginx configuration:
server {
listen 443 ssl;
server_name yourdomain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://127.0.0.1:5000;
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;
}
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name yourdomain.com;
return 301 https://$server_name$request_uri;
}Option B: Using Flask-Talisman
from flask_talisman import Talisman
Talisman(app, force_https=True)- Set
FLASK_ENV=production - Generate and set strong
SECRET_KEY - Enable HTTPS/TLS
- Configure firewall rules
- Set up reverse proxy (Nginx/Apache)
- Enable automatic backups
- Configure logging
- Set up monitoring
- Review file permissions
- Disable debug mode (automatic when
FLASK_ENV=production)
Using Gunicorn (Recommended):
pip install gunicorn
gunicorn -w 4 -b 127.0.0.1:5000 app:appUsing systemd service:
[Unit]
Description=HSA/FSA Tracker
After=network.target
[Service]
User=www-data
WorkingDirectory=/path/to/hsa-tracker-v2/app
Environment="FLASK_ENV=production"
Environment="SECRET_KEY=<your-secret-key>"
ExecStart=/usr/bin/gunicorn -w 4 -b 127.0.0.1:5000 app:app
Restart=always
[Install]
WantedBy=multi-user.target-
Use Strong Passwords:
- Minimum 12 characters
- Mix of uppercase, lowercase, numbers, and symbols
- Unique password for this application
-
Regular Backups:
- Export your data regularly using the backup feature
- Store backups in a secure location
- Test restore process periodically
-
Secure Your Receipts:
- Only upload necessary documents
- Remove sensitive metadata before upload
- Regularly review stored files
-
Keep Software Updated:
- Regularly update Python packages
- Monitor security advisories
- Apply patches promptly
-
Monitor Logs:
- Review authentication logs
- Watch for suspicious activity
- Set up alerts for failed login attempts
-
Database Security:
- Regular backups
- Restrict file system permissions
- Consider encrypting database at rest
-
Network Security:
- Use firewall rules
- Limit exposed ports
- Enable fail2ban or similar
-
No Two-Factor Authentication (2FA)
- Planned: TOTP-based 2FA
-
No Audit Logging
- Planned: Comprehensive audit trail
-
Basic Password Policy
- Current: 6 character minimum
- Planned: Complexity requirements, strength meter
-
No Email Verification
- Planned: Email verification on registration
If you discover a security vulnerability, please:
- DO NOT open a public GitHub issue
- Email security concerns to: [[email protected]]
- Include:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if any)
We will respond within 48 hours and work with you to address the issue.
- Added optional database encryption (SQLCipher)
- Added optional file encryption (Fernet)
- OIDC/SSO authentication support
- Added CSRF protection (Flask-WTF)
- Implemented rate limiting (Flask-Limiter)
- Enforced SECRET_KEY requirement
- Added session security configuration
- Fixed debug mode exposure
- Basic authentication
- Password hashing
- User data isolation
- File upload security
Last Updated: 2025-12-10 Maintained By: HSA Tracker Development Team