Comprehensive security guide for the Networth Tracker application, covering data protection, privacy, and security best practices.
- Security Overview
- Data Encryption
- Authentication and Access Control
- File System Security
- Network Security
- Privacy Protection
- Security Best Practices
- Threat Model
- Security Monitoring
The Networth Tracker is designed with a security-first, privacy-first approach:
- Local-First: All data stored locally, no cloud dependencies
- Encryption at Rest: All sensitive data encrypted in the database
- Zero-Knowledge: No external services have access to financial data
- Minimal Attack Surface: Localhost-only operation
- Defense in Depth: Multiple layers of security controls
┌─────────────────────────────────────────────────────────────┐
│ Browser Interface │
├─────────────────────────────────────────────────────────────┤
│ Flask Application │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Authentication │ │ Session Mgmt │ │
│ └─────────────────┘ └─────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Encryption Layer │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Data Encryption│ │ Key Management │ │
│ └─────────────────┘ └─────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Encrypted SQLite Database │
├─────────────────────────────────────────────────────────────┤
│ File System Security │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ File Permissions│ │ Access Control │ │
│ └─────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────┘
All sensitive financial data is encrypted before storage using industry-standard encryption:
- Algorithm: Fernet (AES-128 in CBC mode with HMAC-SHA256)
- Key Derivation: PBKDF2 with SHA-256
- Iterations: 100,000 (configurable)
- Salt: 16-byte random salt per database
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
class EncryptionService:
def derive_key(self, password: str, salt: bytes) -> bytes:
"""Derive encryption key from master password."""
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=100000, # OWASP recommended minimum
)
key = base64.urlsafe_b64encode(kdf.derive(password.encode()))
return key- Account Information: Names, institutions, balances, values
- Stock Positions: Symbols, shares, prices, purchase dates
- Historical Data: All historical snapshots and performance data
- Configuration Data: Sensitive application settings
- Database Schema: Table structures and indexes
- Application Logs: System logs (no financial data included)
- Stock Symbols: Only symbols sent to APIs (no quantities or values)
- Purpose: Derives the database encryption key
- Requirements: Minimum 12 characters, complexity recommended
- Storage: Never stored in plaintext, only used for key derivation
- Recovery: No password recovery mechanism (by design for security)
- User enters master password
- Random salt retrieved from database
- PBKDF2 derives encryption key (100,000 iterations)
- Key used to encrypt/decrypt data
- Key cleared from memory after use
- Memory Protection: Keys cleared from memory after use
- No Key Storage: Encryption keys never stored on disk
- Salt Uniqueness: Each database has a unique random salt
- Iteration Count: Configurable, minimum 100,000 iterations
# Configurable password policy
MIN_PASSWORD_LENGTH = 12
REQUIRE_UPPERCASE = True
REQUIRE_LOWERCASE = True
REQUIRE_NUMBERS = True
REQUIRE_SPECIAL_CHARS = False # Optional but recommended- Length: Minimum 12 characters (configurable)
- Complexity: Mixed case, numbers recommended
- Common Passwords: Checked against common password lists
- Strength Meter: Visual feedback during setup
- User enters master password
- Password hashed and compared with stored hash
- If valid, encryption key derived for session
- Session established with timeout
- Automatic logout after inactivity
# Flask session configuration
SESSION_COOKIE_SECURE = False # HTTP for localhost
SESSION_COOKIE_HTTPONLY = True # No JavaScript access
SESSION_COOKIE_SAMESITE = 'Lax' # CSRF protection
PERMANENT_SESSION_LIFETIME = timedelta(hours=2) # Auto-logout- Automatic Timeout: Configurable inactivity timeout
- Secure Cookies: HTTPOnly and SameSite protection
- Session Invalidation: Logout clears all session data
- Concurrent Sessions: Only one active session per instance
# Database files (owner read/write only)
chmod 600 *.db
# Log files (owner read/write, others read)
chmod 644 logs/*.log
# Backup files (owner read/write only)
chmod 600 backups/*
# Configuration files (owner read/write only)
chmod 600 .env*
# Application directory (owner full access)
chmod 750 /opt/networth-tracker# Remove inheritance and set explicit permissions
icacls "networth-tracker" /inheritance:r
icacls "networth-tracker" /grant:r "%USERNAME%:(OI)(CI)F"
# Restrict database file access
icacls "*.db" /grant:r "%USERNAME%:F"networth-tracker/
├── data/ # 700 - Database files (owner only)
├── logs/ # 755 - Log files (readable by others)
├── backups/ # 700 - Backup files (owner only)
├── temp/ # 700 - Temporary files (owner only)
└── config/ # 700 - Configuration files (owner only)
The application automatically sets secure permissions on startup:
def set_secure_permissions():
"""Set secure file permissions on startup."""
if os.name != 'nt': # Unix-like systems
# Database files
for db_file in glob.glob('*.db'):
os.chmod(db_file, 0o600)
# Backup files
for backup_file in glob.glob('backups/*'):
os.chmod(backup_file, 0o600)
# Configuration files
for config_file in glob.glob('.env*'):
os.chmod(config_file, 0o600)- Host: 127.0.0.1 (localhost only)
- Port: 5000 (configurable)
- External Access: Explicitly disabled
- Firewall: No inbound rules required
# Flask application binding
app.run(
host='127.0.0.1', # Localhost only
port=5000,
debug=False
)- Data Sent: Only stock symbols (e.g., "AAPL", "GOOGL")
- Data NOT Sent: No quantities, values, or personal information
- Rate Limiting: Respects API rate limits
- Timeout: Configurable request timeouts
- Error Handling: Graceful degradation if API unavailable
class StockPriceService:
def get_stock_price(self, symbol: str) -> float:
"""Get stock price - only symbol is transmitted."""
# Validate symbol format (letters only)
if not re.match(r'^[A-Z]{1,5}$', symbol):
raise ValueError("Invalid stock symbol")
# Make API request with timeout
response = requests.get(
f"https://api.example.com/quote/{symbol}",
timeout=30,
headers={'User-Agent': 'NetworthTracker/1.0'}
)
# No financial data in request or logs
return response.json()['price']- Financial Data: Only what you enter for tracking
- Usage Data: None (no analytics or telemetry)
- Personal Data: None (no names, addresses, SSNs)
- System Data: Only local logs for debugging
- Browsing History: No tracking of web activity
- Location Data: No GPS or location tracking
- Device Information: No device fingerprinting
- Network Data: No network traffic analysis
- Location: Local machine only
- Cloud Sync: None (by design)
- Backups: User-controlled local backups only
- Sharing: No automatic sharing or synchronization
- No Cloud Exposure: Data never leaves your machine
- No Third-Party Access: No external services access your data
- User Control: Complete control over your data
- Offline Operation: Full functionality without internet
- Synthetic Data: Realistic but fake financial data
- No Real Information: No actual financial institutions or accounts
- Safe Exploration: Learn features without privacy risk
- Importable Data: Can be imported through standard import functionality
- Strong Master Password: Use a unique, complex password
- Password Manager: Consider using a password manager
- Regular Changes: Change password if compromised
- No Sharing: Never share your master password
- Keep Updated: Update operating system and Python
- Antivirus: Use reputable antivirus software
- Firewall: Enable system firewall
- Physical Security: Secure physical access to computer
- Regular Backups: Export data regularly
- Secure Storage: Store backups in secure locations
- Multiple Copies: Keep backups in multiple locations
- Test Restores: Periodically test backup restoration
- Secure Installation: Follow deployment security guidelines
- File Permissions: Verify correct file permissions
- Network Isolation: Ensure localhost-only binding
- Process Security: Run with minimal privileges
- Log Monitoring: Monitor application logs for issues
- File Integrity: Monitor database file integrity
- Access Monitoring: Monitor file access patterns
- Performance Monitoring: Monitor for unusual activity
- Physical Access: Database encryption protects against unauthorized access
- Malware: File permissions and encryption provide protection
- Data Theft: Encrypted storage prevents data extraction
- Privilege Escalation: Minimal privileges and sandboxing
- Man-in-the-Middle: Localhost-only operation eliminates network exposure
- Eavesdropping: No sensitive data transmitted over network
- Remote Access: No remote access capabilities
- API Attacks: Only stock symbols transmitted to external APIs
- Sophisticated Malware: Advanced malware with keyloggers
- State-Level Actors: Nation-state level attacks
- Hardware Attacks: Physical hardware tampering
- Social Engineering: Attacks targeting the user directly
- Defense in Depth: Multiple security layers
- User Education: Security awareness training
- Regular Updates: Keep system and application updated
- Monitoring: Implement security monitoring
# Authentication events
logger.info(f"Login attempt from {request.remote_addr}")
logger.warning(f"Failed login attempt: {failed_attempts}")
logger.info(f"User logged out: session_duration={duration}")
# Data access events
logger.info(f"Database accessed: operation={operation}")
logger.warning(f"Database error: {error_message}")
# File system events
logger.info(f"File permissions set: {file_path}")
logger.warning(f"Permission denied: {file_path}")#!/bin/bash
# Monitor security events in logs
# Check for failed login attempts
grep "Failed login" logs/networth_tracker.log | tail -10
# Check for permission errors
grep "Permission denied" logs/networth_tracker_errors.log
# Check for database errors
grep "Database error" logs/networth_tracker_errors.logdef security_audit():
"""Perform automated security audit."""
issues = []
# Check file permissions
for db_file in glob.glob('*.db'):
if oct(os.stat(db_file).st_mode)[-3:] != '600':
issues.append(f"Insecure database permissions: {db_file}")
# Check for weak passwords (if configurable)
if len(config.SECRET_KEY) < 32:
issues.append("Weak secret key detected")
# Check for outdated dependencies
# Implementation depends on requirements
return issues- Immediate Response: Stop application if compromise suspected
- Assessment: Determine scope and impact of incident
- Containment: Isolate affected systems
- Recovery: Restore from clean backups if necessary
- Lessons Learned: Update security measures
# Emergency shutdown
./scripts/start.sh --stop
# Backup current state
python scripts/init_db.py --backup
# Restore from clean backup
python scripts/init_db.py --restore backup_file.db
# Verify integrity
python scripts/init_db.py --verify
# Restart with enhanced monitoring
./scripts/start.sh --env production- A01 - Broken Access Control: Strong authentication and session management
- A02 - Cryptographic Failures: Industry-standard encryption
- A03 - Injection: Parameterized queries and input validation
- A04 - Insecure Design: Security-first architecture
- A05 - Security Misconfiguration: Secure defaults and configuration
- GDPR Compliance: Local storage, user control, data minimization
- CCPA Compliance: No data collection or sharing
- PIPEDA Compliance: Privacy by design principles
This security guide should be reviewed and updated regularly to address:
- New threats and vulnerabilities
- Changes in security best practices
- Updates to encryption standards
- Regulatory requirement changes
Security Review Date: 2025-01-09 Next Review Due: 2025-07-09 Security Contact: See main documentation for support