- Archiver 131 .md dans docs/archive/root-md/ - Archiver 22 .json dans docs/archive/root-json/ - Conserver 7 .md utiles (README, CONTRIBUTING, CHANGELOG, etc.) - Conserver package.json, package-lock.json, turbo.json - Ajouter README d'index dans chaque archive
22 KiB
🔍 AUDIT DE STABILITÉ — PROJET VEZA
Date : 2025-01-27
Objectif : Identifier toutes les faiblesses potentielles dans la robustesse, cohérence, performances et résilience du système
Phase : Zero-Bug / Launch-Ready
📋 TABLE DES MATIÈRES
1. BACKEND GO
1.1 Handlers HTTP
✅ P0 - Erreurs JSON non traitées silencieusement — RÉSOLU
Localisation : internal/handlers/common.go:280-287
Status : ✅ RÉSOLU — Phase 4 JSON Hardening complétée
Solution implémentée :
- Création de
BindAndValidateJSONdansCommonHandleravec :- Vérification de la taille du body (10MB max)
- Gestion robuste des erreurs JSON (syntaxe, type, body vide, etc.)
- Validation automatique avec le validator centralisé
- Retour d'
AppErrorau lieu d'erreurs génériques
- Tous les handlers dans
internal/handlers/refactorisés pour utiliserBindAndValidateJSON+RespondWithAppError - Handlers critiques refactorisés : auth, social, marketplace, playlists, profile, comment, role, analytics, bitrate, settings, room, webhook, config_reload, password_reset
Impact : Plus aucune erreur JSON ne passe silencieusement. Toutes les erreurs de parsing/validation sont renvoyées avec un format unifié et des codes HTTP appropriés.
Note : Il reste ~26 occurrences dans internal/api/ (handlers dans des packages différents utilisant des patterns différents). À refactoriser dans une phase ultérieure si nécessaire.
⚠️ P1 - Erreurs silencieuses dans les handlers
Localisation : internal/handlers/auth.go, internal/handlers/social.go
Problème : Certains handlers retournent des erreurs génériques sans contexte suffisant. Exemple :
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
return
}
Impact : Difficile de diagnostiquer les problèmes en production.
Recommandation : Utiliser systématiquement RespondWithAppError avec contexte enrichi.
⚠️ P1 - Validation d'input incomplète
Localisation : Tous les handlers
Problème : Certains handlers n'utilisent pas ValidateRequest avant de traiter les données.
Impact : Risque d'injection SQL, XSS, ou corruption de données.
Recommandation : Middleware de validation automatique pour toutes les routes POST/PUT.
1.2 Base de données
❌ P0 - Absence de transactions dans certaines opérations critiques
Localisation : internal/core/marketplace/service.go:134-136
Problème : CreateOrder utilise une transaction, mais d'autres opérations multi-étapes non :
// Exemple problématique (si non transactionnel)
func (s *Service) UpdateUserProfile(ctx context.Context, userID uuid.UUID, profile *Profile) error {
// Étape 1: Mise à jour user
s.db.Update(&user)
// Étape 2: Mise à jour profile
s.db.Update(&profile)
// Si étape 2 échoue, étape 1 reste appliquée → INCOHÉRENCE
}
Impact : Incohérence DB en cas d'erreur partielle.
Recommandation : Audit complet des opérations multi-étapes, wrapper dans transactions.
⚠️ P1 - Erreurs DB non wrap
Localisation : Plusieurs services
Problème : Certaines erreurs DB sont retournées directement sans contexte :
if err := s.db.First(&user, "id = ?", id).Error; err != nil {
return nil, err // Pas de contexte
}
Impact : Debugging difficile, pas de traçabilité.
Recommandation : Toujours wrapper avec fmt.Errorf("failed to find user %s: %w", id, err).
⚠️ P1 - Pas de retry automatique pour les erreurs transitoires
Localisation : Tous les appels DB
Problème : Pas de retry automatique pour database/sql errors (timeouts, connection pool exhausted).
Impact : Échecs temporaires non récupérés automatiquement.
Recommandation : Wrapper DB avec retry logic (exponential backoff) pour erreurs transitoires.
1.3 Workers
⚠️ P1 - Race condition potentielle lors des retries
Localisation : internal/workers/job_worker.go:127-135
if job.Retries < w.maxRetries {
job.Retries++
delay := time.Duration(job.Retries) * 5 * time.Second
time.Sleep(delay) // ⚠️ Bloque le worker
w.Enqueue(job) // ⚠️ Pas de lock sur job
}
Problème : Si plusieurs workers tentent de retry le même job simultanément, Retries peut être incrémenté plusieurs fois.
Impact : Jobs retry plus que maxRetries, ou jobs dupliqués dans la queue.
Recommandation : Utiliser un mutex ou atomic operations pour job.Retries, ou marquer le job comme "retrying" en DB avant ré-enqueue.
⚠️ P1 - Pas de timeout explicite pour les jobs
Localisation : internal/workers/job_worker.go:116
jobCtx, cancel := context.WithTimeout(ctx, 5*time.Minute)
defer cancel()
Problème : Timeout hardcodé, pas configurable. Si un job prend plus de 5 minutes, il est annulé brutalement.
Impact : Jobs longs (ex: transcodage) peuvent être interrompus.
Recommandation : Timeout configurable par type de job.
⚠️ P2 - Queue in-memory sans persistance
Localisation : internal/workers/job_worker.go
Problème : La queue est en mémoire (chan Job). Si le serveur crash, les jobs en attente sont perdus.
Impact : Perte de jobs non traités lors d'un crash.
Recommandation : Utiliser une queue persistante (Redis, RabbitMQ) pour les jobs critiques.
1.4 Password Reset
✅ Bien protégé contre l'énumération
Localisation : internal/core/auth/service.go:372-379
if err == gorm.ErrRecordNotFound {
return nil // Toujours retourner succès
}
Status : ✅ Implémentation correcte — toujours retourner succès même si email n'existe pas.
⚠️ P1 - Timing attack potentiel
Localisation : internal/services/password_reset_service.go:70-125
Problème : Le temps de traitement peut différer entre :
- Email existe → Génération token + Hash + DB write
- Email n'existe pas → Simple DB query
Impact : Attaquant peut détecter si un email existe via timing.
Recommandation : Ajouter un délai artificiel pour égaliser les temps de réponse.
1.5 Health Check
✅ Robuste si DB en panne
Localisation : internal/handlers/health.go:70-77, internal/handlers/status_handler.go
Status : ✅ /health est stateless (toujours OK). /status gère correctement les erreurs DB et retourne degraded.
⚠️ P2 - Pas de circuit breaker
Localisation : Health checks
Problème : Si DB est down, chaque health check tente une connexion (timeout 5s). Pas de circuit breaker pour éviter de surcharger DB.
Impact : Si DB est down, health checks continuent à tenter des connexions.
Recommandation : Implémenter un circuit breaker pour les dépendances externes.
2. CHAT SERVER (RUST)
2.1 Race Conditions
❌ P0 - Race condition dans TypingIndicatorManager
Localisation : src/typing_indicator.rs:34-48
pub async fn user_started_typing(&self, user_id: Uuid, conversation_id: Uuid) {
let mut typing = self.typing_users.write().await;
let conversation_typing = typing
.entry(conversation_id)
.or_insert_with(HashMap::new);
conversation_typing.insert(user_id, Utc::now());
}
Problème : Le RwLock protège la HashMap, mais si deux utilisateurs tapent simultanément dans la même conversation, l'ordre d'insertion peut varier.
Impact : Timestamps peuvent être inversés, causant des broadcasts dans le mauvais ordre.
Recommandation : Utiliser un Mutex au lieu de RwLock pour garantir l'ordre, ou utiliser un canal sérialisé.
⚠️ P1 - Race condition dans DeliveredStatusManager
Localisation : src/delivered_status.rs
Problème : Si plusieurs messages sont marqués comme "delivered" simultanément, les updates DB peuvent se chevaucher.
Impact : Statuts de livraison incohérents.
Recommandation : Utiliser une queue sérialisée pour les updates de statut.
⚠️ P1 - Race condition dans ReadReceiptManager
Localisation : src/read_receipts.rs
Problème : Même problème que DeliveredStatusManager.
Recommandation : Queue sérialisée ou transaction DB.
2.2 Panics Potentiels
❌ P0 - Panics dans WebSocket handler
Localisation : src/websocket/handler.rs:175-176
let incoming: IncomingMessage = serde_json::from_str(text)
.map_err(|e| ChatError::serialization_error("IncomingMessage", text, e))?;
Status : ✅ Bien géré — erreur retournée, pas de panic.
⚠️ P1 - .unwrap() dans plusieurs fichiers
Localisation : 31 fichiers identifiés avec unwrap() ou expect()
Exemples :
src/config.rs:unwrap()sur variables d'environnementsrc/database/pool.rs:unwrap()sur connexions DBsrc/jwt_manager.rs:expect()sur parsing JWT
Impact : Panics possibles si données inattendues.
Recommandation : Remplacer tous les unwrap() par ? ou gestion d'erreur explicite.
⚠️ P1 - Pas de panic boundary dans handle_socket
Localisation : src/websocket/handler.rs:77-163
Problème : Si une panic survient dans handle_incoming_message, elle peut faire crasher toute la task Tokio.
Impact : Un client malveillant peut faire crasher le serveur.
Recommandation : Wrapper handle_incoming_message dans std::panic::catch_unwind ou utiliser tokio::spawn avec supervision.
2.3 Gestion des Tasks
⚠️ P1 - Tasks orphelins possibles
Localisation : src/typing_indicator.rs (task de monitoring)
Problème : La task de monitoring des timeouts est spawnée au démarrage mais n'a pas de mécanisme de shutdown propre.
Impact : Task continue à tourner même après arrêt du serveur.
Recommandation : Utiliser un CancellationToken pour arrêter proprement les tasks.
⚠️ P1 - Pas de timeout explicite pour les opérations DB
Localisation : Tous les appels DB
Problème : Pas de timeout sur les queries SQLx. Si DB est lente, les requêtes peuvent bloquer indéfiniment.
Impact : Deadlock ou timeout très long.
Recommandation : Ajouter des timeouts sur tous les appels DB (via sqlx::query().fetch_timeout()).
2.4 Robustesse WebSocket
✅ Bien géré — déconnexions propres
Localisation : src/websocket/handler.rs:134-137
Ok(Message::Close(_)) => {
info!("👋 Connexion WebSocket fermée par le client");
break;
}
Status : ✅ Déconnexions gérées proprement.
⚠️ P1 - Pas de heartbeat timeout
Localisation : src/websocket/handler.rs
Problème : Pas de mécanisme pour détecter les connexions "zombies" (client déconnecté mais serveur ne le sait pas).
Impact : Connexions mortes occupent des ressources.
Recommandation : Implémenter un heartbeat (ping/pong) avec timeout.
2.5 Permissions
✅ Bien implémenté — PermissionService
Localisation : src/security/permission.rs
Status : ✅ Vérifications de permissions présentes avant chaque action.
⚠️ P1 - Risque de bypass si PermissionService échoue
Localisation : src/websocket/handler.rs:194-200
state
.permission_service
.can_send_message(sender_uuid, conversation_id)
.await
.map_err(|e| {
warn!(...);
// ⚠️ Que se passe-t-il si l'erreur est ignorée ?
})?;
Problème : Si can_send_message retourne une erreur, elle est loggée mais le handler peut continuer selon l'implémentation.
Impact : Bypass de permissions si erreur DB.
Recommandation : Toujours refuser l'action si permission check échoue (fail-secure).
3. STREAM SERVER (RUST)
3.1 StreamProcessor
❌ P0 - Tasks non cancellées proprement en cas d'erreur
Localisation : src/core/processing/processor.rs:168-169
monitor_handle.abort();
event_handle.abort();
Problème : abort() tue brutalement les tasks. Si elles étaient en train d'écrire en DB, la transaction peut rester ouverte.
Impact : Handles orphelins, transactions DB non commitées.
Recommandation : Utiliser CancellationToken pour arrêter proprement, attendre la fin des tasks avant abort().
⚠️ P1 - Erreurs FFmpeg non propagées correctement
Localisation : src/core/processing/processor.rs:154-156
FFmpegEvent::Error(msg) => {
tracing::warn!("⚠️ Erreur FFmpeg détectée: {}", msg);
}
Problème : Les erreurs FFmpeg sont loggées mais ne causent pas l'arrêt du traitement. Le job continue même si FFmpeg a une erreur fatale.
Impact : Jobs peuvent se terminer en "succès" alors que FFmpeg a échoué.
Recommandation : Détecter les erreurs fatales FFmpeg et arrêter le traitement immédiatement.
⚠️ P1 - DB pas toujours sync en cas de crash
Localisation : src/core/processing/processor.rs:238-243
async fn finalize(&self, tracker: Arc<SegmentTracker>) -> Result<(), AppError> {
tracker.persist_all().await?;
// ...
}
Problème : Si le serveur crash avant finalize(), les segments détectés mais non persistés sont perdus.
Impact : Incohérence entre fichiers segments et DB.
Recommandation : Persister immédiatement chaque segment (déjà fait dans SegmentTracker::register), mais vérifier que c'est bien transactionnel.
3.2 SegmentTracker
⚠️ P1 - Corruption d'état concurrent possible
Localisation : src/core/processing/segment_tracker.rs:59-78
pub async fn register(&self, segment: SegmentInfo) -> Result<(), AppError> {
{
let mut segments = self.segments.write().await;
segments.push(segment.clone());
}
self.persist_segment(&segment).await?;
}
Problème : Si deux segments sont enregistrés simultanément, l'ordre d'insertion dans le vecteur peut varier, mais la persistance DB se fait séquentiellement.
Impact : Segments peuvent être persistés dans le mauvais ordre.
Recommandation : Utiliser un canal sérialisé pour les registrations, ou un mutex global.
3.3 FFmpegMonitor
⚠️ P1 - Regex non robustes
Localisation : src/core/processing/ffmpeg_monitor.rs:22-24
static ref OPENING_SEGMENT_REGEX: Regex = Regex::new(
r"Opening '([^']+)' for writing"
).unwrap();
Problème : Si FFmpeg change son format de log, la regex ne matchera plus. Pas de fallback.
Impact : Segments non détectés, job échoue silencieusement.
Recommandation : Ajouter un fallback : détecter les segments depuis le répertoire de sortie si regex échoue.
⚠️ P1 - Gestion des IO errors incomplète
Localisation : src/core/processing/ffmpeg_monitor.rs:90-94
while let Ok(Some(line)) = lines.next_line().await {
self.process_line(&line).await?;
}
Problème : Si next_line() retourne une erreur (ex: stderr fermé), la boucle s'arrête silencieusement.
Impact : Monitoring s'arrête sans notification, job continue mais plus de tracking.
Recommandation : Logger l'erreur et propager pour arrêter le job.
3.4 API HLS
✅ Path traversal protégé
Localisation : src/routes/encoding.rs:128-133, internal/services/hls_service.go:137-151
Status : ✅ Vérification du chemin absolu avec HasPrefix pour éviter path traversal.
⚠️ P1 - Erreurs HTTP silencieuses
Localisation : src/routes/encoding.rs:144-148
if !segment_path.exists() {
return Err(AppError::NotFound { ... });
}
Problème : Si le fichier existe mais n'est pas lisible (permissions), l'erreur sera générique.
Impact : Debugging difficile.
Recommandation : Différencier "not found" vs "permission denied" vs "IO error".
4. GLOBAL PROJECT
4.1 Cohérence Inter-Services
❌ P0 - Pas de transaction distribuée
Localisation : Tous les services
Problème : Si un message est créé dans le chat server mais que le backend Go échoue à créer une notification, les deux DB sont incohérentes.
Impact : Données incohérentes entre services.
Recommandation : Implémenter un pattern Saga ou Event Sourcing pour garantir la cohérence.
⚠️ P1 - Pas de validation croisée des IDs
Localisation : Communication inter-services
Problème : Le chat server accepte des conversation_id sans vérifier qu'ils existent dans le backend Go.
Impact : Messages peuvent être créés pour des conversations inexistantes.
Recommandation : Validation croisée via API ou cache partagé.
4.2 Tests
❌ P0 - Manque de tests unitaires critiques
Localisation : Tous les services
Problème : Beaucoup de tests sont #[ignore] car nécessitent une DB de test.
Impact : Pas de validation automatique des corrections.
Recommandation : Utiliser des mocks (ex: sqlx::test) ou des containers Docker pour les tests.
⚠️ P1 - Pas de tests de charge
Localisation : Aucun
Problème : Pas de validation que le système supporte 100+ clients simultanés.
Impact : Problèmes de performance non détectés.
Recommandation : Tests de charge avec k6 ou locust.
4.3 Fuites Goroutine / Tokio Task
⚠️ P1 - Goroutines sans mécanisme de shutdown
Localisation : internal/jobs/cleanup_sessions.go:33-45
go func() {
for range ticker.C {
// ...
}
}()
Problème : Pas de moyen d'arrêter cette goroutine proprement.
Impact : Goroutine continue après arrêt du serveur.
Recommandation : Utiliser context.Context avec cancellation.
⚠️ P1 - Tokio tasks spawnées sans supervision
Localisation : veza-chat-server/src/optimized_persistence.rs:264-285
tokio::spawn(async move {
engine_clone.batch_processing_loop().await;
});
Problème : Si la task panic, elle n'est pas relancée.
Impact : Service peut s'arrêter silencieusement.
Recommandation : Utiliser un supervisor task qui relance les tasks en cas de panic.
4.4 Logging Contextuel
⚠️ P1 - Pas de correlation-id systématique
Localisation : Tous les services
Problème : Pas de correlation-id ou trace-id pour suivre une requête à travers les services.
Impact : Debugging difficile en production.
Recommandation : Implémenter OpenTelemetry ou un système de tracing distribué.
⚠️ P2 - Logs non structurés dans certains endroits
Localisation : Quelques handlers
Problème : Certains logs utilisent fmt.Printf au lieu de tracing ou zap.
Impact : Logs non queryables.
Recommandation : Standardiser sur tracing (Rust) et zap (Go).
4.5 Risques d'Incohérence DB
❌ P0 - Jobs, messages, segments peuvent être incohérents
Localisation : Tous les services
Problème : Si un job de transcodage échoue après avoir créé des segments en DB, les segments restent orphelins.
Impact : DB contient des données incohérentes.
Recommandation : Jobs de cleanup périodiques pour supprimer les données orphelines.
⚠️ P1 - Pas de vérification d'intégrité
Localisation : Aucun
Problème : Pas de job qui vérifie que les fichiers segments correspondent aux enregistrements DB.
Impact : Incohérences non détectées.
Recommandation : Job de vérification d'intégrité quotidien.
5. RÉSUMÉ DES RISQUES
🔴 P0 — Must-Fix avant déploiement
- Backend Go : Erreurs JSON non traitées silencieusement
- Backend Go : Absence de transactions dans opérations critiques
- Chat Server : Race condition dans TypingIndicatorManager
- Chat Server : Panics possibles (31 fichiers avec
unwrap()) - Stream Server : Tasks non cancellées proprement
- Global : Pas de transaction distribuée
- Global : Manque de tests unitaires critiques
- Global : Jobs/messages/segments peuvent être incohérents
🟠 P1 — Production-grade minimal
- Backend Go : Erreurs silencieuses, validation input incomplète
- Backend Go : Race condition dans workers retries
- Backend Go : Timing attack password reset
- Chat Server : Race conditions dans DeliveredStatusManager/ReadReceiptManager
- Chat Server : Pas de panic boundary dans WebSocket handler
- Chat Server : Tasks orphelins, pas de heartbeat timeout
- Stream Server : Erreurs FFmpeg non propagées, DB pas toujours sync
- Stream Server : Corruption d'état concurrent dans SegmentTracker
- Stream Server : Regex non robustes, IO errors incomplètes
- Global : Pas de validation croisée IDs, pas de tests de charge
- Global : Fuites goroutine/task, pas de correlation-id
🟡 P2 — Qualité continue
- Backend Go : Pas de circuit breaker health check
- Backend Go : Queue in-memory sans persistance
- Global : Logs non structurés, pas de vérification d'intégrité
📊 STATISTIQUES
- P0 (Critique) : 8 problèmes
- P1 (Important) : 11 problèmes
- P2 (Amélioration) : 3 problèmes
- Total : 22 problèmes identifiés
🔗 LIENS AVEC TRIAGE ACTUEL
Voir TRIAGE.md pour l'état fonctionnel des features. Cet audit se concentre sur la robustesse et la stabilité, pas sur les features manquantes.
Prochaines étapes : Générer HARDENING_PLAN.md avec plan de correction priorisé.