security: Fix critical vulnerabilities from security audit

- Remove sensitive tokens from logs (show only 8-char preview)
- Enforce SECRET_KEY minimum 32 characters (no default value)
- Reduce login rate limit from 100/hour to 5/hour
- Remove exposed PageSpeed API key from CLAUDE.md

BREAKING: Application requires SECRET_KEY >= 32 chars in .env

Author: Maciej Pienczyn z wykorzystaniem AI i Claude Opus
This commit is contained in:
Maciej Pienczyn 2026-01-09 15:50:40 +01:00
parent 39a91b709a
commit 5af216c5e0
2 changed files with 21 additions and 17 deletions

View File

@ -617,12 +617,10 @@ Wykorzystuje Google PageSpeed Insights API do analizy wydajności i jakości str
- Limit: 25,000 zapytań/dzień (free tier)
- Endpoint: `https://www.googleapis.com/pagespeedonline/v5/runPagespeed`
**Aktualny klucz (2026-01-08):**
**Klucz API:**
- **Nazwa w Google Cloud:** `Page SPEED SEO Audit v2`
- **Wartość:**
```
GOOGLE_PAGESPEED_API_KEY=AIzaSyC9OAvPVCHsmPuMOv5gETyXXAdAe8J60Yw
```
- **Wartość:** Przechowywany w `.env` (GOOGLE_PAGESPEED_API_KEY)
- **UWAGA:** Nigdy nie commituj kluczy API do repozytorium!
### Metryki audytu

30
app.py
View File

@ -151,7 +151,13 @@ except ImportError as e:
# Initialize Flask app
app = Flask(__name__)
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'dev-secret-key-change-in-production')
# Security: Require strong SECRET_KEY (no default value allowed)
SECRET_KEY = os.getenv('SECRET_KEY')
if not SECRET_KEY or len(SECRET_KEY) < 32:
raise ValueError("SECRET_KEY must be set in environment variables and be at least 32 characters long")
app.config['SECRET_KEY'] = SECRET_KEY
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7)
# Security configurations
@ -2600,13 +2606,13 @@ def register():
logger.info(f"Verification email sent to {email}")
else:
logger.warning(f"Failed to send verification email to {email}")
logger.info(f"Verification URL (email failed): {verification_url}")
logger.info(f"Verification token (email failed) for {email}: {verification_token[:8]}...")
else:
logger.warning("Email service not configured")
logger.info(f"Verification URL (no email service): {verification_url}")
logger.info(f"Verification token (no email) for {email}: {verification_token[:8]}...")
except Exception as e:
logger.error(f"Error sending verification email: {e}")
logger.info(f"Verification URL (exception): {verification_url}")
logger.info(f"Verification token (exception) for {email}: {verification_token[:8]}...")
logger.info(f"New user registered: {email}")
flash('Rejestracja udana! Sprawdz email i kliknij link weryfikacyjny.', 'success')
@ -2623,7 +2629,7 @@ def register():
@app.route('/login', methods=['GET', 'POST'])
@limiter.limit("100 per hour") # Increased for testing
@limiter.limit("5 per hour") # Strict limit to prevent brute force attacks
def login():
"""User login"""
if current_user.is_authenticated:
@ -2730,14 +2736,14 @@ def forgot_password():
logger.info(f"Password reset email sent to {email}")
else:
logger.warning(f"Failed to send password reset email to {email}")
# Log URL for manual recovery
logger.info(f"Reset URL (email failed): {reset_url}")
# Log token preview for debugging (full token never logged for security)
logger.info(f"Reset token (email failed) for {email}: {reset_token[:8]}...")
else:
logger.warning("Email service not configured")
logger.info(f"Reset URL (no email service): {reset_url}")
logger.info(f"Reset token (no email) for {email}: {reset_token[:8]}...")
except Exception as e:
logger.error(f"Error sending reset email: {e}")
logger.info(f"Reset URL (exception): {reset_url}")
logger.info(f"Reset token (exception) for {email}: {reset_token[:8]}...")
# Always show same message to prevent email enumeration
flash('Jeśli email istnieje w systemie, instrukcje resetowania hasła zostały wysłane.', 'info')
@ -2886,13 +2892,13 @@ def resend_verification():
logger.info(f"Verification email resent to {email}")
else:
logger.warning(f"Failed to resend verification email to {email}")
logger.info(f"Verification URL (email failed): {verification_url}")
logger.info(f"Resend verification token (email failed) for {email}: {verification_token[:8]}...")
else:
logger.warning("Email service not configured")
logger.info(f"Verification URL (no email service): {verification_url}")
logger.info(f"Resend verification token (no email) for {email}: {verification_token[:8]}...")
except Exception as e:
logger.error(f"Error resending verification email: {e}")
logger.info(f"Verification URL (exception): {verification_url}")
logger.info(f"Resend verification token (exception) for {email}: {verification_token[:8]}...")
# Always show same message to prevent email enumeration
flash('Jesli konto istnieje i nie zostalo zweryfikowane, email weryfikacyjny zostal wyslany.', 'info')