Files
team-tryouts/security_scan.py
cedrick2711 666673fa8f demander IA de faire tous les modifications pour que le webapp soit pret au déploiement.
Force connection HTTPS, proxy-inversé, WSGI de production, reset cookie de conncection à chaque reconnection, limite sur les mdp, fichiers et One on One par minute, verification d'injection de SQL dans les champs d'entrées. renommage des fichiers lors du téléchargement, fichier de backup quotidien pour la bd et j'ai oublié quelque chose :(
2026-07-25 18:47:27 -04:00

372 lines
12 KiB
Python

"""Security validation script for the Team Tryouts application.
This script performs pre-deployment security checks to validate:
- HTTP security headers
- Cookie security attributes
- Debug mode status
- HTTPS configuration
- Dependency vulnerabilities
- Database connectivity
Usage:
python security_scan.py [--url http://localhost:5000]
"""
import os
import sys
import json
import subprocess
import urllib.request
import ssl
from datetime import datetime
def check_environment():
"""Check required environment variables are set.
Returns:
bool: True if all critical variables are set.
"""
print('=' * 60)
print('1. ENVIRONMENT VARIABLES CHECK')
print('=' * 60)
critical_vars = ['SECRET_KEY']
recommended_vars = ['DATABASE_URL', 'CORS_ALLOWED_ORIGINS']
all_ok = True
for var in critical_vars:
value = os.getenv(var)
if value:
# Check SECRET_KEY is not a default/weak value
if var == 'SECRET_KEY' and len(value) < 32:
print(f'[WARN] {var} is set but too short (less than 32 chars)')
all_ok = False
else:
print(f'[OK] {var} is set')
else:
print(f'[FAIL] {var} is not set!')
all_ok = False
for var in recommended_vars:
value = os.getenv(var)
if value:
print(f'[OK] {var} is set')
else:
print(f'[INFO] {var} is not set (using default)')
# Check FLASK_DEBUG
debug = os.getenv('FLASK_DEBUG', 'false').lower()
if debug == 'true':
print('[WARN] FLASK_DEBUG is enabled! Should be disabled in production.')
else:
print('[OK] FLASK_DEBUG is disabled')
return all_ok
def check_https_headers(url):
"""Check HTTP security headers from a running application.
Args:
url: The base URL of the application to check.
Returns:
bool: True if all critical headers are present.
"""
print('\n' + '=' * 60)
print('2. HTTP SECURITY HEADERS CHECK')
print('=' * 60)
required_headers = {
'Strict-Transport-Security': 'HSTS enabled',
'X-Content-Type-Options': 'Prevents MIME sniffing',
'X-Frame-Options': 'Prevents clickjacking',
'Content-Security-Policy': 'CSP configured',
'Referrer-Policy': 'Referrer control',
'Permissions-Policy': 'Permissions control',
'Cross-Origin-Opener-Policy': 'Cross-origin isolation',
}
all_ok = True
try:
# Create a context that doesn't verify SSL (for local testing)
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
req = urllib.request.Request(url, method='HEAD')
try:
with urllib.request.urlopen(req, context=ctx, timeout=10) as response:
headers = response.headers
status = response.status
print(f'[INFO] Response status: {status}')
for header, description in required_headers.items():
if header in headers:
print(f'[OK] {header}: {description}')
else:
print(f'[FAIL] {header} is missing: {description}')
all_ok = False
# Check cookie attributes if any set-cookie headers exist
if 'Set-Cookie' in headers:
cookie = headers['Set-Cookie']
if 'Secure' in cookie:
print('[OK] Cookies have Secure flag')
else:
print('[WARN] Cookies missing Secure flag')
all_ok = False
if 'HttpOnly' in cookie:
print('[OK] Cookies have HttpOnly flag')
else:
print('[WARN] Cookies missing HttpOnly flag')
all_ok = False
if 'SameSite' in cookie:
print(f'[OK] Cookies have SameSite={cookie.split("SameSite=")[1].split(";")[0] if "SameSite=" in cookie else "?"}')
else:
print('[WARN] Cookies missing SameSite attribute')
all_ok = False
else:
print('[INFO] No Set-Cookie headers in response')
except urllib.error.HTTPError as e:
print(f'[INFO] Got HTTP {e.code} (may need authentication)')
# Still check headers even on error responses
for header, description in required_headers.items():
if header in e.headers:
print(f'[OK] {header}: {description}')
else:
print(f'[FAIL] {header} is missing: {description}')
all_ok = False
except urllib.error.URLError as e:
print(f'[SKIP] Cannot connect to {url}: {e.reason}')
print('[SKIP] Run with --url <application_url> to check headers')
return True # Not a failure, just can't check
return all_ok
def check_dependencies():
"""Run pip-audit to check for known vulnerabilities.
Returns:
bool: True if no critical vulnerabilities found.
"""
print('\n' + '=' * 60)
print('3. DEPENDENCY VULNERABILITY SCAN')
print('=' * 60)
try:
result = subprocess.run(
[sys.executable, '-m', 'pip_audit', '--format', 'json'],
capture_output=True,
text=True,
timeout=60
)
if result.returncode == 0:
print('[OK] No known vulnerabilities found')
return True
else:
try:
data = json.loads(result.stdout)
vulns = data.get('dependencies', [])
if vulns:
for vuln in vulns:
print(f'[FAIL] {vuln["name"]}=={vuln["version"]}: {vuln.get("description", "Vulnerability found")}')
return False
else:
print('[OK] No vulnerabilities found')
return True
except json.JSONDecodeError:
if result.stdout:
print(f'[INFO] {result.stdout.strip()}')
if result.stderr:
print(f'[WARN] {result.stderr.strip()}')
return True
except FileNotFoundError:
print('[SKIP] pip-audit not installed. Run: pip install pip-audit')
return True
except subprocess.TimeoutExpired:
print('[WARN] pip-audit timed out')
return True
def check_file_permissions():
"""Check for common security issues in the project structure.
Returns:
bool: True if no critical issues found.
"""
print('\n' + '=' * 60)
print('4. PROJECT FILES CHECK')
print('=' * 60)
all_ok = True
# Check .gitignore exists and contains important patterns
gitignore_path = os.path.join(os.getcwd(), '.gitignore')
if os.path.exists(gitignore_path):
required_patterns = ['.env', 'instance/', '*.db', '*.log']
with open(gitignore_path, 'r') as f:
content = f.read()
for pattern in required_patterns:
if pattern in content:
print(f'[OK] .gitignore contains: {pattern}')
else:
print(f'[WARN] .gitignore missing: {pattern}')
all_ok = False
else:
print('[FAIL] .gitignore file not found!')
all_ok = False
# Check for .env in working directory (should NOT be committed)
env_path = os.path.join(os.getcwd(), '.env')
if os.path.exists(env_path):
print('[INFO] .env file exists (ensure it is NOT committed)')
else:
print('[WARN] No .env file found')
# Check for leftover .pyc or __pycache__
pycache_count = 0
for root, dirs, files in os.walk(os.getcwd()):
if '__pycache__' in dirs:
pycache_count += 1
for f in files:
if f.endswith('.pyc'):
pycache_count += 1
if pycache_count == 0:
print('[OK] No __pycache__ or .pyc files found')
else:
print(f'[INFO] Found {pycache_count} cache files/dirs (should be in .gitignore)')
return all_ok
def check_flask_config():
"""Check Flask application configuration for security.
Returns:
bool: True if configuration looks secure.
"""
print('\n' + '=' * 60)
print('5. FLASK CONFIGURATION CHECK')
print('=' * 60)
all_ok = True
try:
from app import create_app
app = create_app()
# Check session cookie settings
cookie_checks = [
('SESSION_COOKIE_SECURE', True, 'Secure cookies'),
('SESSION_COOKIE_HTTPONLY', True, 'HttpOnly cookies'),
('PERMANENT_SESSION_LIFETIME', 3600, 'Session timeout'),
]
for config_key, expected, description in cookie_checks:
value = app.config.get(config_key)
if config_key == 'PERMANENT_SESSION_LIFETIME':
if value and value <= 3600:
print(f'[OK] {description}: {value}s')
else:
print(f'[WARN] {description}: {value}s (should be <= 1 hour)')
all_ok = False
elif value == expected:
print(f'[OK] {description}: enabled')
else:
print(f'[FAIL] {description}: {value}')
all_ok = False
# Check MAX_CONTENT_LENGTH
max_content = app.config.get('MAX_CONTENT_LENGTH')
if max_content:
mb = max_content / (1024 * 1024)
print(f'[OK] MAX_CONTENT_LENGTH: {mb}MB')
else:
print('[WARN] MAX_CONTENT_LENGTH not set (unlimited uploads)')
all_ok = False
# Check CSRF
csrf_enabled = app.config.get('WTF_CSRF_ENABLED')
if csrf_enabled:
print('[OK] CSRF protection: enabled')
else:
print('[FAIL] CSRF protection: disabled')
all_ok = False
# Check if app is in DEBUG mode
if app.debug:
print('[FAIL] DEBUG mode is enabled!')
all_ok = False
else:
print('[OK] DEBUG mode: disabled')
except Exception as e:
print(f'[SKIP] Cannot check Flask config: {e}')
return all_ok
def main():
"""Run all security checks and produce a summary report.
Returns:
int: 0 if all checks pass, 1 if any fail.
"""
import argparse
parser = argparse.ArgumentParser(description='Security validation scanner')
parser.add_argument('--url', default='http://localhost:5000',
help='Application URL to check headers (default: http://localhost:5000)')
args = parser.parse_args()
print('╔══════════════════════════════════════════════════════════╗')
print('║ TEAM TRYOUTS - SECURITY VALIDATION SCANNER ║')
print('╠══════════════════════════════════════════════════════════╣')
print(f'║ Time: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
print('╚══════════════════════════════════════════════════════════╝')
checks = [
check_environment,
lambda: check_https_headers(args.url),
check_dependencies,
check_file_permissions,
check_flask_config,
]
results = []
for check in checks:
results.append(check())
print('\n' + '=' * 60)
print('SUMMARY')
print('=' * 60)
passed = sum(1 for r in results if r)
failed = sum(1 for r in results if not r)
total = len(results)
print(f'Passed: {passed}/{total}')
print(f'Failed: {failed}/{total}')
if failed == 0:
print('\n[OK] All security checks passed!')
return 0
else:
print(f'\n[WARN] {failed} check(s) failed. Review the output above.')
return 1
if __name__ == '__main__':
sys.exit(main())