feat: sécurité HIGH — token Bearer, validation, rate limiting, headers

- Token Bearer auth sur le streaming server (auto-généré ou env var)
- Validation actions replay (types, longueurs, coordonnées 0-1)
- Rate limiting in-memory (10 replays/min, 200 images/min)
- Security headers Flask (nosniff, SAMEORIGIN, XSS)
- Validation uploads (50MB max, MIME type)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dom
2026-03-19 00:29:54 +01:00
parent 24a947b51d
commit fe5e0ba83d
6 changed files with 96 additions and 12 deletions

View File

@@ -79,8 +79,22 @@ logger = logging.getLogger(__name__)
app = Flask(__name__)
import secrets as _secrets
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', _secrets.token_hex(32))
app.config['MAX_CONTENT_LENGTH'] = 50 * 1024 * 1024 # 50 MB max upload (sécurité HIGH)
socketio = SocketIO(app, cors_allowed_origins="*")
# ============================================================
# Headers de sécurité (sécurité HIGH)
# ============================================================
@app.after_request
def set_security_headers(response):
"""Ajouter les headers de sécurité à toutes les réponses."""
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
response.headers['X-XSS-Protection'] = '1; mode=block'
return response
# Global state
matcher: Optional[SemanticMatcher] = None
gpu_manager = None
@@ -1365,6 +1379,11 @@ def api_chat_upload():
Upload d'un fichier Excel/CSV via le chat.
Le fichier est stocké dans data/uploads/ et un aperçu est retourné.
Validations de sécurité (HIGH) :
- Taille max : 50 MB (via MAX_CONTENT_LENGTH Flask)
- Extension autorisée : .xlsx, .xls, .csv
- Type MIME vérifié (pas juste l'extension)
- Nom de fichier assaini via secure_filename
"""
if 'file' not in request.files:
return jsonify({"error": "Aucun fichier reçu."}), 400
@@ -1381,9 +1400,32 @@ def api_chat_upload():
"error": f"Format '{ext}' non supporté. Formats acceptés : {', '.join(allowed_ext)}"
}), 400
# Sauvegarder le fichier
# Validation du type MIME (sécurité HIGH — pas juste l'extension)
_ALLOWED_MIMES = {
'.xlsx': {'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/octet-stream'},
'.xls': {'application/vnd.ms-excel', 'application/octet-stream'},
'.csv': {'text/csv', 'text/plain', 'application/csv', 'application/octet-stream'},
}
file_mime = file.content_type or ''
allowed_mimes = _ALLOWED_MIMES.get(ext, set())
if file_mime and file_mime not in allowed_mimes:
logger.warning(
f"Upload rejeté : MIME '{file_mime}' invalide pour extension '{ext}' "
f"(fichier: {file.filename})"
)
return jsonify({
"error": f"Type MIME '{file_mime}' invalide pour un fichier {ext}. "
f"Types attendus : {', '.join(allowed_mimes)}"
}), 400
# Assainir le nom de fichier (sécurité HIGH — empêcher path traversal)
UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
filename = secure_filename(file.filename)
if not filename:
# secure_filename peut retourner une chaîne vide pour des noms exotiques
filename = f"upload_{datetime.now().strftime('%Y%m%d_%H%M%S')}{ext}"
# Ajouter un timestamp pour éviter les collisions
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
safe_name = f"{Path(filename).stem}_{ts}{ext}"