# RAPPORT DE PENTEST — VEZA v0.12.6 | Champ | Valeur | |-------|--------| | **Date** | 2026-03-11 | | **Auditeur** | Claude Opus 4.6 — Audit de sécurité interne (remplace prestataire externe) | | **Version analysée** | v0.12.6 (branche `feat/v0.12.6-pentest-audit`) | | **Périmètre** | Backend Go, Stream Server Rust, Frontend React, Infrastructure Docker, CI/CD | | **Méthodologie** | OWASP Top 10 (2021), OWASP API Security Top 10 (2023), ASVS v4.0 Level 2 | | **Classification** | Confidentiel — Usage interne | --- ## EXECUTIVE SUMMARY ### Verdict global Le codebase VEZA est **globalement bien sécurisé** avec une architecture de sécurité mature (middleware chain, RBAC, token versioning, CSRF, rate limiting multi-couche). Les vulnérabilités critiques identifiées dans l'audit précédent (mars 2026) ont été corrigées (VEZA-SEC-001 JWT secret par défaut, VEZA-SEC-002 issuer/audience mismatch). ### Recommandation : **NO-GO — Corriger les CRITIQUES et HAUTS avant v1.0.0** L'audit approfondi (6 passes parallèles sur l'intégralité du codebase) identifie **2 findings CRITIQUES**, **10 findings HAUTS**, **12 findings MOYENS**, **6 findings BAS**, et **5 findings INFO**. Les findings CRITIQUES et HAUTS doivent être corrigés impérativement. ### Résumé des findings | Sévérité | Nombre | Détail | |----------|--------|--------| | CRITIQUE | 2 | IDOR chat rooms (lecture conversation privée sans membership), play_count/like_count exposés publiquement (violation éthique) | | HAUTE | 10 | Race conditions marketplace (downloads, promo codes, licence exclusive), production HS256, IP spoofing rate limiter (pas de TrustedProxies), RGPD hard delete incomplet, RTMP callback auth faible, co-écoute host hijack, modérateur self-strike, free trial illimité | | MOYENNE | 12 | Recovery codes math/rand, metrics IP spoofing, ClamAV latest, pagination sans limit max, WebSocket pas de re-auth, CSP Swagger, CI actions non-pinnées, RabbitMQ mgmt, password reset no rate limit, access token in response body, email dans logs, analytics sans k-anonymité | | BASSE | 6 | Password policy mismatch FE/BE, Hyperswitch version, dotenv crate, Elasticsearch sans auth, context.Background dans workers, Redis sans auth dev | | INFO | 5 | Bonnes pratiques confirmées (20+ contrôles), recommandations d'amélioration | --- ## FINDINGS DÉTAILLÉS --- ### [CRIT-001] : IDOR — Lecture de conversations privées sans vérification de membership **Sévérité** : CRITIQUE **CVSS v3.1** : 9.1 (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N) **CWE** : CWE-639 — Authorization Bypass Through User-Controlled Key **OWASP** : A01:2021 — Broken Access Control **OWASP API** : API1:2023 — Broken Object Level Authorization **Fichier(s)** : `veza-backend-api/internal/handlers/room_handler.go:134-158` (GetRoom), `:255-314` (GetRoomHistory) **Composant** : Chat — Conversations **Description** Les endpoints `GET /api/v1/conversations/:id` et `GET /api/v1/conversations/:id/history` ne vérifient **pas** que l'utilisateur authentifié est membre de la room avant de retourner les données. N'importe quel utilisateur authentifié peut lire l'intégralité des messages de n'importe quelle conversation en connaissant son UUID. **Preuve de concept** ```bash # L'utilisateur B (non membre) accède à la conversation privée de l'utilisateur A curl -H "Cookie: access_token=" \ https://veza.fr/api/v1/conversations//history # Retourne TOUS les messages de la conversation privée ``` **Impact** - Confidentialité : Lecture de messages privés, données financières échangées, informations personnelles - Compliance : Violation RGPD (accès non autorisé aux données personnelles) - Business : Perte de confiance des utilisateurs, risque juridique **Remédiation** ```go func (h *RoomHandler) GetRoom(c *gin.Context) { roomID, _ := uuid.Parse(c.Param("id")) userID := c.MustGet("user_id").(uuid.UUID) // SECURITY: Verify membership before returning room data isMember, err := h.roomService.IsRoomMember(c.Request.Context(), roomID, userID) if err != nil || !isMember { RespondWithAppError(c, apperrors.NewNotFoundError("Conversation")) return } // ... rest of handler } ``` **Priorité de correction** : Immédiate (bloquant v1.0.0) --- ### [CRIT-002] : Métriques de popularité (play_count, like_count) exposées publiquement dans l'API **Sévérité** : CRITIQUE (éthique VEZA) **CVSS v3.1** : 5.3 (AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N) **CWE** : CWE-497 — Exposure of Sensitive System Information **OWASP** : A01:2021 — Broken Access Control **Fichier(s)** : `veza-backend-api/internal/models/track.go:39-40` **Composant** : API — Tracks **Description** Le modèle `Track` expose `play_count` et `like_count` dans les réponses JSON via les tags `json:"play_count"` et `json:"like_count"`. Ces données sont retournées dans toutes les réponses de l'API publique (liste de tracks, détail de track, recherche). Cela viole la **règle immuable #4 du CLAUDE.md** : *"JAMAIS de métriques de popularité publiques — Les likes et play counts sont PRIVÉS (visibles uniquement par le créateur dans ses analytics)"*. **Impact** - Violation du principe éthique fondamental de VEZA (pas de dark patterns FOMO) - Crée un biais de popularité dans la découverte musicale - Discrimine les artistes émergents **Remédiation** ```go // Créer un type de réponse publique sans les métriques type TrackPublicResponse struct { ID uuid.UUID `json:"id"` Title string `json:"title"` ArtistID uuid.UUID `json:"artist_id"` // PAS de PlayCount ni LikeCount } // N'exposer play_count et like_count que dans /api/v1/me/tracks/:id/analytics (creator only) ``` **Priorité de correction** : Immédiate (bloquant v1.0.0) --- ### [HIGH-001] : Race condition TOCTOU sur le compteur de téléchargements marketplace **Sévérité** : HAUTE **CVSS v3.1** : 7.5 (AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N) **CWE** : CWE-367 — Time-of-check Time-of-use (TOCTOU) Race Condition **OWASP** : A04:2021 — Insecure Design **OWASP API** : API6:2023 — Unrestricted Access to Sensitive Business Flows **Fichier(s)** : `veza-backend-api/internal/core/marketplace/service.go:794-817` **Composant** : Marketplace — Downloads **Description** La fonction `GetDownloadURL` vérifie d'abord qu'une licence existe avec `downloads_left > 0` (ligne 794), puis décrémente `downloads_left` séparément (ligne 817). Ces deux opérations ne sont pas atomiques et ne sont pas dans une transaction avec verrouillage. **Preuve de concept** ``` # Deux requêtes simultanées avec downloads_left = 1 # T1: SELECT ... WHERE downloads_left > 0 → trouvé (downloads_left = 1) # T2: SELECT ... WHERE downloads_left > 0 → trouvé (downloads_left = 1) # T1: UPDATE downloads_left = downloads_left - 1 → downloads_left = 0 # T2: UPDATE downloads_left = downloads_left - 1 → downloads_left = -1 # Résultat: 2 téléchargements au lieu de 1 ``` **Impact** Un acheteur peut contourner la limite de téléchargements d'une licence en envoyant plusieurs requêtes simultanées. Impact financier direct pour les créateurs vendant des licences exclusives à téléchargements limités. **Remédiation** ```go // Utiliser une transaction avec SELECT FOR UPDATE tx := s.db.Begin() err := tx.Set("gorm:query_option", "FOR UPDATE"). Where("buyer_id = ? AND product_id = ? AND downloads_left > 0 AND revoked_at IS NULL", buyerID, productID). First(&license).Error if err != nil { tx.Rollback() return "", ErrNoLicense } // ... generate URL ... tx.Model(&license).Update("downloads_left", gorm.Expr("downloads_left - 1")) tx.Commit() ``` Alternativement : `UPDATE ... SET downloads_left = downloads_left - 1 WHERE downloads_left > 0 RETURNING *` **Priorité de correction** : Sprint suivant --- ### [HIGH-002] : Production déployée en HS256 au lieu de RS256 **Sévérité** : HAUTE **CVSS v3.1** : 7.4 (AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N) **CWE** : CWE-327 — Use of a Broken or Risky Cryptographic Algorithm **OWASP** : A02:2021 — Cryptographic Failures **Fichier(s)** : `docker-compose.prod.yml:158`, `veza-backend-api/internal/services/jwt_service.go:32-79` **Composant** : Authentication — JWT **Description** Le `docker-compose.prod.yml` configure `JWT_SECRET` (ligne 158) mais pas `JWT_PRIVATE_KEY_PATH` ni `JWT_PUBLIC_KEY_PATH`. Le code Go préfère RS256 si les clés sont fournies, sinon fallback HS256. En production, c'est HS256 qui est utilisé. HS256 utilise un secret partagé symétrique — si le secret est compromis (leak env var, backup, log), un attaquant peut forger des tokens valides. RS256 nécessite la clé privée pour signer mais la clé publique suffit pour vérifier — meilleure séparation des responsabilités. **Impact** Compromission du JWT_SECRET = compromission de tous les comptes utilisateurs. Avec RS256, seul le serveur ayant la clé privée peut signer des tokens. **Remédiation** 1. Générer une paire RSA 2048-bit pour la production 2. Ajouter dans `docker-compose.prod.yml` : ```yaml - JWT_PRIVATE_KEY_PATH=/secrets/jwt_private.pem - JWT_PUBLIC_KEY_PATH=/secrets/jwt_public.pem ``` 3. Monter les clés via Docker secrets ou volume sécurisé 4. Supprimer `JWT_SECRET` de la config production 5. Invalider tous les tokens existants (incrémenter token_version globalement) **Priorité de correction** : Immédiate (avant v1.0.0) --- ### [HIGH-003] : User repository bypass du context de requête **Sévérité** : HAUTE **CVSS v3.1** : 5.3 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L) **CWE** : CWE-400 — Uncontrolled Resource Consumption **OWASP** : A04:2021 — Insecure Design **Fichier(s)** : `veza-backend-api/internal/repositories/user_repository.go:125-150` **Composant** : Data Layer — User Repository **Description** Six méthodes du UserRepository (`GetByID`, `GetByEmail`, `GetByUsername`, `Create`, `Update`, `Delete`) utilisent `context.Background()` au lieu de propager le context de la requête HTTP. Cela signifie que : 1. Les timeouts de requête ne s'appliquent pas aux opérations DB sous-jacentes 2. L'annulation de la requête (client disconnect) ne cancelle pas la query DB 3. Un attaquant peut déclencher des requêtes DB longues qui ne seront pas annulées **Impact** Sous charge, des requêtes lentes peuvent s'accumuler car elles ne sont pas annulées quand le client se déconnecte. Cela peut mener à une saturation du pool de connexions DB (DoS). **Remédiation** ```go // Avant (ligne 125) func (r *UserRepository) GetByID(userID uuid.UUID) (*models.User, error) { return r.GetUserByID(context.Background(), userID) } // Après func (r *UserRepository) GetByID(ctx context.Context, userID uuid.UUID) (*models.User, error) { return r.GetUserByID(ctx, userID) } ``` Propager le context dans toute la chaîne d'appel (handler → service → repository). **Priorité de correction** : Sprint suivant --- ### [HIGH-004] : Race condition sur les codes promo (TOCTOU) **Sévérité** : HAUTE **CVSS v3.1** : 7.5 (AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N) **CWE** : CWE-362 — Concurrent Execution Using Shared Resource **Fichier(s)** : `veza-backend-api/internal/core/marketplace/service.go:463-464,753-776` **Composant** : Marketplace — Promo Codes **Description** La validation du code promo (`validatePromoCodeTx`) vérifie `UsedCount >= MaxUses` puis incrémente `used_count` séparément. Sans `SELECT FOR UPDATE`, deux requêtes concurrentes peuvent utiliser le même code promo au-delà de sa limite. **Remédiation** Ajouter `clause.Locking{Strength: "UPDATE"}` dans la requête de validation du code promo. **Priorité de correction** : Sprint suivant --- ### [HIGH-005] : Pas de protection race condition sur licences exclusives **Sévérité** : HAUTE **CVSS v3.1** : 7.5 (AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N) **CWE** : CWE-362 — Concurrent Execution Using Shared Resource **Fichier(s)** : `veza-backend-api/internal/core/marketplace/service.go:393-532` **Composant** : Marketplace — Licenses **Description** Aucune vérification atomique n'empêche deux acheteurs d'acheter simultanément une licence exclusive du même produit. Deux transactions peuvent créer deux licences exclusives pour le même track. **Remédiation** Ajouter un check `SELECT FOR UPDATE` dans la transaction de création de licence : ```go var existing License if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). Where("product_id = ? AND type = 'exclusive' AND revoked_at IS NULL", prod.ID). First(&existing).Error; err == nil { return ErrExclusiveLicenseAlreadySold } ``` **Priorité de correction** : Sprint suivant --- ### [HIGH-006] : Rate limiter bypass — TrustedProxies non configuré **Sévérité** : HAUTE **CVSS v3.1** : 7.5 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H) **CWE** : CWE-346 — Origin Validation Error **Fichier(s)** : `veza-backend-api/internal/middleware/rate_limiter.go:131,265,353,429` **Composant** : Infrastructure — Rate Limiting **Description** Gin's `c.ClientIP()` fait confiance à `X-Forwarded-For` automatiquement. Sans appel explicite à `engine.SetTrustedProxies()`, n'importe quel client peut forger son IP et bypasser tous les rate limiters (DDoS, brute force, upload). **Remédiation** ```go engine.SetTrustedProxies([]string{"127.0.0.1", "10.0.0.0/8"}) // Adapter au proxy réel ``` **Priorité de correction** : Immédiate --- ### [HIGH-007] : RGPD hard delete incomplet (Redis, Elasticsearch, audit logs) **Sévérité** : HAUTE **CVSS v3.1** : 6.5 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N) **CWE** : CWE-212 — Improper Cross-boundary Removal of Sensitive Data **Fichier(s)** : `veza-backend-api/internal/workers/hard_delete_worker.go:101-121` **Composant** : RGPD — Account Deletion **Description** Le hard delete worker anonymise uniquement `users` et `user_profiles` en DB. Les données PII restent dans : Redis (sessions/cache), Elasticsearch (index de recherche), audit_logs (user_id, IP), RabbitMQ (messages en queue). **Remédiation** Étendre le hard delete worker pour nettoyer : Redis keys, ES index, audit_logs anonymisation, RabbitMQ purge. **Priorité de correction** : Sprint suivant --- ### [HIGH-008] : RTMP callback auth — fallback à query param "name" (stream key) **Sévérité** : HAUTE **CVSS v3.1** : 7.3 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N) **CWE** : CWE-287 — Improper Authentication **Fichier(s)** : `veza-backend-api/internal/handlers/live_stream_callback.go:25-36` **Composant** : Livestream — RTMP Callbacks **Description** La fonction `validateCallbackSecret()` fallback à `c.Query("name")` quand le header `X-RTMP-Callback-Secret` est absent. Le paramètre "name" contient en réalité le stream_key de l'utilisateur, pas le secret. Cela permet à quiconque connaissant un stream_key de déclencher des callbacks publish/publish_done. De plus, si `RTMP_CALLBACK_SECRET` n'est pas configuré, la validation retourne `true` (fail-open). **Remédiation** - Supprimer le fallback à `c.Query("name")` - Retourner `false` si le secret n'est pas configuré (fail-closed) - Utiliser `hmac.Equal()` pour la comparaison **Priorité de correction** : Sprint suivant --- ### [HIGH-009] : Co-écoute — participant non-host peut prendre le contrôle **Sévérité** : HAUTE **CVSS v3.1** : 6.5 (AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N) **CWE** : CWE-863 — Incorrect Authorization **Fichier(s)** : `veza-backend-api/internal/websocket/colistening/hub.go:102-139`, `handlers/co_listening_websocket_handler.go:50-126` **Composant** : Co-écoute WebSocket **Description** Le flag `IsHost` est défini à la connexion mais n'est pas re-vérifié lors du traitement des messages. Un listener peut envoyer un message `UpdateHostState` et son état de lecture sera broadcast comme autoritaire à tous les participants. **Remédiation** Vérifier `conn.IsHost` avant de traiter les messages de type `UpdateHostState`/`SyncClientStateMsg`. **Priorité de correction** : Sprint suivant --- ### [HIGH-010] : Modérateur peut émettre des strikes sans contrôle de conflit d'intérêts **Sévérité** : HAUTE **CVSS v3.1** : 6.5 (AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:N) **CWE** : CWE-863 — Incorrect Authorization **Fichier(s)** : `veza-backend-api/internal/services/moderation_service.go:725-737` **Composant** : Modération — Strikes **Description** Aucune vérification n'empêche un modérateur d'émettre un strike à lui-même ou de résoudre son propre appel. Un modérateur malveillant pourrait abuser du système pour cibler des utilisateurs spécifiques. **Remédiation** Ajouter la vérification : `if reportedUserID == moderatorID { return error }`. **Priorité de correction** : Sprint suivant --- ### [MEDIUM-001] : Codes de récupération 2FA générés avec math/rand **Sévérité** : MOYENNE **CVSS v3.1** : 5.9 (AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N) **CWE** : CWE-338 — Use of Cryptographically Weak PRNG **OWASP** : A02:2021 — Cryptographic Failures **ASVS** : V2.6.1 **Fichier(s)** : `veza-backend-api/internal/services/two_factor_service.go:200` **Composant** : Authentication — 2FA **Description** Les codes de récupération 2FA sont générés avec `mathrand.Intn()` (math/rand) au lieu de `crypto/rand`. `math/rand` utilise un PRNG prédictible — si un attaquant connaît le seed ou peut observer suffisamment de sorties, il peut prédire les codes de récupération futurs. **Preuve de concept** ```go // Ligne 200 - Utilisation de math/rand code[j] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[mathrand.Intn(36)] ``` Depuis Go 1.20, `math/rand` auto-seed est non déterministe, réduisant le risque. Cependant, les standards cryptographiques exigent `crypto/rand` pour tout matériel lié à l'authentification. **Impact** Prédiction théorique des codes de récupération 2FA, permettant le contournement de l'authentification à deux facteurs. **Remédiation** ```go import "crypto/rand" import "math/big" for j := 0; j < 8; j++ { n, _ := rand.Int(rand.Reader, big.NewInt(36)) code[j] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[n.Int64()] } ``` **Priorité de correction** : Sprint suivant --- ### [MEDIUM-002] : Bypass du whitelist IP des métriques via X-Forwarded-For **Sévérité** : MOYENNE **CVSS v3.1** : 5.3 (AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N) **CWE** : CWE-290 — Authentication Bypass by Spoofing **OWASP** : A05:2021 — Security Misconfiguration **Fichier(s)** : `veza-backend-api/internal/middleware/metrics_protection.go:52-54` **Composant** : Infrastructure — Metrics **Description** Le middleware de protection des métriques lit `X-Forwarded-For` directement depuis la requête et l'utilise pour vérifier l'IP, remplaçant `c.ClientIP()`. Un attaquant peut forger ce header pour bypasser le whitelist IP. ```go // Ligne 52-54 — X-Forwarded-For forgeable clientIP := c.ClientIP() if forwarded := c.GetHeader("X-Forwarded-For"); forwarded != "" { clientIP = strings.TrimSpace(strings.Split(forwarded, ",")[0]) } ``` **Preuve de concept** ```bash curl -H "X-Forwarded-For: 127.0.0.1" https://veza.fr/metrics # Bypasse le whitelist si 127.0.0.1 est autorisé ``` **Impact** Information disclosure : un attaquant peut accéder aux métriques Prometheus (pool DB, error rates, endpoints lents, charge système) pour préparer une attaque ciblée. **Remédiation** Supprimer la lecture de `X-Forwarded-For` dans ce middleware. Utiliser uniquement `c.ClientIP()` qui est déjà configuré pour tenir compte du proxy de confiance (via `gin.SetTrustedProxies`). ```go // Supprimer les lignes 52-55, utiliser uniquement : clientIP := c.ClientIP() ``` **Priorité de correction** : Sprint suivant --- ### [MEDIUM-003] : Image Docker ClamAV non pinnée (:latest) **Sévérité** : MOYENNE **CVSS v3.1** : 4.8 (AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N) **CWE** : CWE-1104 — Use of Unmaintained Third Party Components **OWASP** : A06:2021 — Vulnerable and Outdated Components **Fichier(s)** : `docker-compose.yml:60`, `docker-compose.prod.yml:73` **Composant** : Infrastructure — Docker **Description** L'image ClamAV utilise le tag `:latest` dans les deux fichiers docker-compose (dev ET production). Cela expose à : 1. Des breaking changes non testées 2. Un potentiel supply chain attack (image compromise) 3. Des builds non reproductibles **Impact** Un changement dans l'image ClamAV pourrait casser silencieusement le scan antivirus des uploads, soit en désactivant la protection, soit en bloquant tous les uploads. **Remédiation** Pinner l'image à un tag de version spécifique : ```yaml clamav: image: clamav/clamav:1.4.2 # Vérifier la dernière version stable ``` **Priorité de correction** : Sprint suivant --- ### [MEDIUM-004] : Pagination sans limite maximale explicite **Sévérité** : MOYENNE **CVSS v3.1** : 5.3 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L) **CWE** : CWE-770 — Allocation of Resources Without Limits **OWASP API** : API4:2023 — Unrestricted Resource Consumption **Fichier(s)** : `veza-backend-api/internal/pagination/` (à vérifier), handlers multiples **Composant** : API — Pagination **Description** L'audit précédent signalait l'absence de limite maximale explicite sur la pagination. Si un utilisateur envoie `?limit=100000`, le serveur pourrait retourner une réponse massive consommant mémoire et bande passante. **Impact** Un attaquant peut envoyer des requêtes avec un `limit` très élevé pour surcharger le serveur (DoS). Impact amplifié si la requête joint plusieurs tables. **Remédiation** Ajouter un cap sur le paramètre `limit` dans le middleware de pagination : ```go const MaxPageSize = 100 if limit > MaxPageSize { limit = MaxPageSize } ``` **Priorité de correction** : Sprint suivant --- ### [MEDIUM-005] : Production JWT utilise HS256 — stream token potentiellement forgeable si secret compromis **Sévérité** : MOYENNE (sous-finding de HIGH-002) **CVSS v3.1** : 5.9 (AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N) **CWE** : CWE-327 — Use of a Broken or Risky Cryptographic Algorithm **OWASP** : A02:2021 — Cryptographic Failures **Fichier(s)** : `veza-backend-api/internal/services/jwt_service.go:253-277` **Composant** : Streaming — Stream Token **Description** Le stream token (JWT court, 5 min TTL) pour l'authentification HLS utilise le même secret/clé que le JWT principal. Avec HS256 en production, le compromis du `JWT_SECRET` permet de forger des stream tokens pour accéder à n'importe quel contenu audio. Le stream token utilise des claims différents (`iss: veza-platform`, `aud: veza-services`) mais la même clé de signature. **Impact** Accès non autorisé au streaming audio de tous les contenus de la plateforme. **Remédiation** Migrer vers RS256 (cf. HIGH-002). Alternativement, utiliser un secret séparé pour les stream tokens. **Priorité de correction** : Sprint suivant (résolu par HIGH-002) --- ### [MEDIUM-006] : CSP unsafe-inline et unsafe-eval pour les routes Swagger **Sévérité** : MOYENNE **CVSS v3.1** : 4.7 (AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N) **CWE** : CWE-79 — Cross-site Scripting (XSS) **OWASP** : A05:2021 — Security Misconfiguration **Fichier(s)** : `veza-backend-api/internal/middleware/security_headers.go:78` **Composant** : Infrastructure — Security Headers **Description** Les routes Swagger (`/swagger/`, `/docs/`) ont un CSP permissif avec `'unsafe-inline'` et `'unsafe-eval'` pour le JavaScript. Bien que nécessaire pour le fonctionnement de Swagger UI, cela crée un vecteur XSS si un attaquant peut injecter du contenu dans la documentation Swagger. ```go csp := "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; ..." ``` **Impact** XSS limité aux pages Swagger. L'impact est réduit car Swagger devrait être désactivé en production (déjà le cas selon l'audit précédent). **Remédiation** Vérifier que Swagger est effectivement désactivé en production. Ajouter un guard : ```go if isProd { // Ne pas servir Swagger en production c.AbortWithStatus(404) return } ``` **Priorité de correction** : Backlog --- ### [MEDIUM-007] : Actions CI/CD non pinnées par SHA **Sévérité** : MOYENNE **CVSS v3.1** : 4.8 (AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:N) **CWE** : CWE-829 — Inclusion of Functionality from Untrusted Control Sphere **OWASP** : A08:2021 — Software and Data Integrity Failures **Fichier(s)** : `.github/workflows/ci.yml` (et tous les workflows) **Composant** : CI/CD — GitHub Actions **Description** Les actions GitHub sont référencées par tag mutable (`@v4`, `@v5`, `@stable`) au lieu de SHA de commit. Un tag peut être déplacé par le mainteneur de l'action, ou en cas de compromission du compte du mainteneur. Exemples : `actions/checkout@v4`, `actions/setup-node@v4`, `actions/setup-go@v5`, `dtolnay/rust-toolchain@stable` **Impact** Un attaquant compromettant un mainteneur d'action GitHub pourrait injecter du code malveillant dans le pipeline CI (exfiltration de secrets, backdoor dans les artifacts de build). **Remédiation** Pinner chaque action par SHA : ```yaml # Avant - uses: actions/checkout@v4 # Après - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 ``` **Priorité de correction** : Sprint suivant --- ### [MEDIUM-008] : RabbitMQ Management UI exposé en dev **Sévérité** : MOYENNE **CVSS v3.1** : 5.3 (AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N) **CWE** : CWE-668 — Exposure of Resource to Wrong Sphere **OWASP** : A05:2021 — Security Misconfiguration **Fichier(s)** : `docker-compose.yml:91` **Composant** : Infrastructure — RabbitMQ **Description** Le Management UI de RabbitMQ (port 25672→15672) est exposé sur l'hôte en dev. En production (`docker-compose.prod.yml`), les ports ne sont pas exposés directement, mais le service utilise toujours l'image `rabbitmq:3-management-alpine` qui inclut le plugin management. **Impact** En dev : accès au dashboard RabbitMQ exposant la topologie des queues et les messages. En prod : si un attaquant accède au réseau Docker, il peut accéder au management UI. **Remédiation** - Prod : utiliser `rabbitmq:3-alpine` (sans management) ou désactiver le plugin management - Dev : OK si réseau local uniquement **Priorité de correction** : Backlog --- ### [MEDIUM-009] : Free trial réutilisable indéfiniment **Sévérité** : MOYENNE **CVSS v3.1** : 5.3 (AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N) **CWE** : CWE-863 — Incorrect Authorization **Fichier(s)** : `veza-backend-api/internal/core/subscription/service.go:236-240` **Composant** : Subscriptions **Description** Le code accorde un essai gratuit si `plan.TrialDays > 0` sans vérifier si l'utilisateur a déjà bénéficié d'un essai. Un utilisateur peut cycler entre free et premium pour obtenir des essais illimités. **Remédiation** Vérifier en DB si l'utilisateur a déjà eu un trial pour ce plan avant d'en accorder un nouveau. **Priorité de correction** : Sprint suivant --- ### [MEDIUM-010] : WebSocket — pas de re-validation token après connexion **Sévérité** : MOYENNE **CVSS v3.1** : 5.4 (AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N) **CWE** : CWE-613 — Insufficient Session Expiration **Fichier(s)** : `veza-backend-api/internal/handlers/chat_websocket_handler.go:32-66` **Composant** : Chat — WebSocket **Description** Le token JWT est validé uniquement à l'upgrade WebSocket. Si le token est révoqué (logout, ban), l'utilisateur reste connecté et peut continuer à envoyer/recevoir des messages. **Remédiation** Implémenter une re-validation périodique du token (toutes les 30 secondes) ou écouter les événements de révocation via Redis PubSub. **Priorité de correction** : Sprint suivant --- ### [MEDIUM-011] : Email logué en clair dans les tentatives de login **Sévérité** : MOYENNE **CVSS v3.1** : 4.3 (AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N) **CWE** : CWE-532 — Insertion of Sensitive Information into Log File **Fichier(s)** : `veza-backend-api/internal/handlers/auth.go:52-56` **Composant** : Logging — Auth **Description** L'email de l'utilisateur est logué en clair lors des tentatives de login. Le `SecretFilterCore` filtre les champs `password`, `token`, `secret` mais pas `email`. **Remédiation** Ajouter `email` à la liste de redaction dans `secret_filter.go`, ou logger un hash de l'email. **Priorité de correction** : Backlog --- ### [MEDIUM-012] : Analytics créateur sans k-anonymité (minimum 10 utilisateurs) **Sévérité** : MOYENNE **CVSS v3.1** : 4.3 (AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N) **CWE** : CWE-359 — Exposure of Private Personal Information to an Unauthorized Actor **Fichier(s)** : `veza-backend-api/internal/handlers/playback_analytics_handler.go` **Composant** : Analytics — Privacy **Description** Les analytics créateur affichent des statistiques détaillées (heatmap, écoutes, géographie) même si un track n'a que 1-2 écoutes. Cela pourrait permettre de ré-identifier des auditeurs par recoupement. **Remédiation** Supprimer les analytics détaillées si `unique_listeners < 10`. Afficher uniquement des catégories agrégées. **Priorité de correction** : Backlog --- ### [LOW-001] : Incohérence politique mot de passe frontend/backend **Sévérité** : BASSE **CVSS v3.1** : 3.7 (AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N) **CWE** : CWE-521 — Weak Password Requirements **OWASP** : A07:2021 — Identification and Authentication Failures **ASVS** : V2.1.1 **Fichier(s)** : `apps/web/src/lib/passwordValidator.ts` vs `veza-backend-api/internal/validators/password_validator.go` **Composant** : Authentication — Password Policy **Description** L'audit précédent (VEZA-SEC-005) signalait que le frontend accepte 8 caractères alors que le backend exige 12. Si non corrigé, l'utilisateur reçoit une erreur serveur après avoir rempli le formulaire — mauvaise UX. **Remédiation** Aligner le frontend sur 12 caractères minimum. **Priorité de correction** : Backlog --- ### [LOW-002] : Version Hyperswitch datée (~1 an) **Sévérité** : BASSE **CVSS v3.1** : 3.1 (AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:L/A:N) **CWE** : CWE-1104 — Use of Unmaintained Third Party Components **OWASP** : A06:2021 — Vulnerable and Outdated Components **Fichier(s)** : `docker-compose.yml:137`, `docker-compose.prod.yml:117` **Composant** : Infrastructure — Payments **Description** L'image Hyperswitch `juspaydotin/hyperswitch-router:2025.01.21.0-standalone` date de janvier 2025 (~14 mois). Des correctifs de sécurité et des améliorations de stabilité ont pu être publiés depuis. **Remédiation** Mettre à jour vers la dernière version stable d'Hyperswitch après test en staging. **Priorité de correction** : Backlog --- ### [LOW-003] : Dépendance Rust `dotenv 0.15` obsolète **Sévérité** : BASSE **CVSS v3.1** : 2.0 (AV:N/AC:H/PR:H/UI:R/S:U/C:N/I:L/A:N) **CWE** : CWE-1104 — Use of Unmaintained Third Party Components **Fichier(s)** : `veza-stream-server/Cargo.toml` **Composant** : Stream Server — Dependencies **Description** `dotenv 0.15` date de 2020 et n'est plus maintenu activement. Le fork `dotenvy` est le successeur recommandé. **Remédiation** ```toml # Cargo.toml dotenvy = "0.15" # Remplacer dotenv par dotenvy ``` Et mettre à jour les imports dans le code Rust. **Priorité de correction** : Backlog --- ### [LOW-004] : Elasticsearch sans authentification dans le réseau Docker **Sévérité** : BASSE **CVSS v3.1** : 3.5 (AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N) **CWE** : CWE-306 — Missing Authentication for Critical Function **OWASP** : A07:2021 — Identification and Authentication Failures **Fichier(s)** : `docker-compose.yml` (Elasticsearch si présent), configuration ES **Composant** : Infrastructure — Elasticsearch **Description** Elasticsearch n'a pas d'authentification configurée dans les docker-compose. Tout service du réseau Docker peut accéder aux index et requêter les données directement. **Impact** Si un autre container est compromis, l'attaquant peut accéder aux index Elasticsearch contenant potentiellement des données utilisateur (tracks, descriptions, messages indexés). **Remédiation** Activer l'authentification native Elasticsearch ou OpenSearch Security. **Priorité de correction** : Backlog --- ### [LOW-005] : context.Background() dans les background jobs **Sévérité** : BASSE **CVSS v3.1** : 2.0 **CWE** : CWE-404 — Improper Resource Shutdown or Release **Fichier(s)** : `veza-backend-api/internal/jobs/*.go` **Composant** : Background Jobs **Description** Les jobs de nettoyage (cleanup_password_reset_tokens, cleanup_sessions, cleanup_hls_segments, cleanup_verification_tokens) utilisent `context.Background()`. Cela est acceptable pour des background jobs, mais ils devraient utiliser un context dérivé du shutdown manager pour permettre un arrêt gracieux. **Remédiation** Passer un context annulable depuis le shutdown manager. **Priorité de correction** : Backlog --- ### [LOW-006] : Redis sans mot de passe en développement **Sévérité** : BASSE **CVSS v3.1** : 2.0 (AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N) **CWE** : CWE-287 — Improper Authentication **Fichier(s)** : `docker-compose.yml:36-48` **Composant** : Infrastructure — Redis **Description** Redis en dev n'a pas de `requirepass`. Acceptable en développement local, mais le docker-compose.yml de base ne devrait pas être utilisé tel quel en staging/pré-production. **Impact** Limité au développement local. La config production (`docker-compose.prod.yml:34`) utilise bien `requirepass`. **Remédiation** OK en l'état (déjà protégé en prod). Documenter que `docker-compose.yml` est strictement pour le dev local. **Priorité de correction** : Backlog --- ### [INFO-001] : Bonnes pratiques de sécurité confirmées **Sévérité** : INFO (Positif) Les contrôles de sécurité suivants sont correctement implémentés : 1. **JWT Token Versioning** : Révocation immédiate via `token_version` vérifié contre la DB à chaque requête (`auth.go:218`) 2. **Token Blacklist Redis** : Double vérification avec blacklist Redis quand disponible (`auth.go:189`) 3. **Session Validation** : Triple vérification (JWT signature → token version → session DB) (`auth.go:175-260`) 4. **Security Headers** : Ensemble complet (HSTS, CSP strict pour API, X-Frame-Options DENY, nosniff, Referrer-Policy, Permissions-Policy) (`security_headers.go`) 5. **CORS Strict** : Production requiert whitelist explicite, panic si non configuré (`cors.go`) 6. **CSRF Protection** : Middleware CSRF avec tokens Redis (`csrf.go`) 7. **Rate Limiting Multi-couche** : Global, par IP, par endpoint, par user, upload-specific (`rate_limiter.go`, `ratelimit_redis.go`, `endpoint_limiter.go`) 8. **ClamAV Obligatoire** : `CLAMAV_REQUIRED=true` en production (`docker-compose.prod.yml:170`) 9. **Bcrypt Cost 12** : Conforme aux recommandations OWASP 10. **Path Validation** : `ValidateExecPath()` sur tous les appels `exec.Command` (`utils/sanitizer.go`) 11. **Secret Filtering** : Logs filtrés pour les secrets (`logging/secret_filter.go`) 12. **Audit Trail** : Middleware d'audit sur POST/PUT/DELETE (`audit.go`) 13. **RBAC Middleware** : Contrôle d'accès basé sur les rôles avec middleware dédié (`rbac_middleware.go`) 14. **Ownership Checks** : `RequireOwnershipOrAdmin` sur les ressources utilisateur 15. **Webhook HMAC-SHA512** : Vérification de signature avec comparaison constant-time 16. **Non-root Docker** : Container backend tourne en user `app:1001` 17. **Multi-stage Dockerfile** : Build optimisé sans outils de compilation dans l'image finale 18. **GORM** : Pas de SQL injection — toutes les queries production utilisent des paramètres préparés 19. **DOMPurify** : Sanitisation HTML côté frontend avec whitelist de tags 20. **User Enumeration Protection** : Messages d'erreur génériques sur login (pas de "user not found" vs "wrong password") --- ### [INFO-002] : Vérification des correctifs de l'audit précédent **Sévérité** : INFO | ID précédent | Statut | Détail | |---|---|---| | VEZA-SEC-001 (JWT secret par défaut Rust) | ✅ CORRIGÉ | `config_rust.rs:234` — `secret: String::new()`, `load_from_env` exige `JWT_SECRET` | | VEZA-SEC-002 (Issuer/audience mismatch) | ✅ CORRIGÉ | `GenerateStreamToken` utilise `iss: veza-platform`, `aud: veza-services` | | VEZA-SEC-003 (Shutdown AppState Rust) | ⚠️ À VÉRIFIER | Non vérifié dans cet audit | | VEZA-SEC-004 (Webhook worker goroutine) | ⚠️ À VÉRIFIER | Non vérifié dans cet audit | | VEZA-SEC-005 (Password policy mismatch) | ❌ NON CORRIGÉ | Repris comme LOW-001 | | VEZA-SEC-006 (Métriques exposées) | ✅ CORRIGÉ | `metrics_protection.go` avec bearer token + IP whitelist | | VEZA-SEC-007 (context.Background password_reset) | ⚠️ À VÉRIFIER | Non vérifié dans cet audit | | VEZA-SEC-008 (Callback no-op seller) | ⚠️ À VÉRIFIER | Non vérifié dans cet audit | | VEZA-SEC-009 (dotenv obsolète) | ❌ NON CORRIGÉ | Repris comme LOW-003 | --- ### [INFO-003] : Recommandation — Ajouter un scan de dépendances dans le pipeline **Sévérité** : INFO Le CI inclut déjà `govulncheck`, `cargo audit`, et `npm audit`. Recommandation supplémentaire : - Ajouter `npm audit --production` (exclure les devDeps) - Ajouter un check de licence automatisé (ex: `license-checker` pour npm, `golicense` pour Go) - Configurer Dependabot pour les GitHub Actions elles-mêmes --- ### [INFO-004] : Recommandation — Tests de sécurité automatisés **Sévérité** : INFO Des tests de sécurité existent (`tests/security/`, `tests/integration/webhook_security_test.go`). Recommandation : - Ajouter des tests de race condition automatisés pour les scénarios marketplace (HIGH-001) - Ajouter un test DAST automatisé (OWASP ZAP) dans le pipeline CI/CD - Ajouter un test de politique CSP (vérifier que `unsafe-inline` n'apparaît pas sur les routes non-Swagger) --- ### [INFO-005] : Aucun `unsafe` dans le code Rust **Sévérité** : INFO (Positif) Aucun bloc `unsafe` trouvé dans le code applicatif du stream server Rust. Le code utilise exclusivement les abstractions safe de Rust, éliminant les risques de corruption mémoire. --- ## ANALYSE PAR CATÉGORIE OWASP TOP 10 | Catégorie | Verdict | Findings | |-----------|---------|----------| | A01 — Broken Access Control | ❌ FAIL | CRIT-001 (IDOR rooms), CRIT-002 (play_count public), HIGH-009 (co-listen), HIGH-010 (modération) | | A02 — Cryptographic Failures | ⚠️ PARTIEL | HIGH-002 (HS256 prod), MEDIUM-001 (math/rand 2FA) | | A03 — Injection | ✅ PASS | GORM, ValidateExecPath, DOMPurify | | A04 — Insecure Design | ⚠️ PARTIEL | HIGH-001 (race condition), HIGH-003 (context bypass) | | A05 — Security Misconfiguration | ⚠️ PARTIEL | MEDIUM-002, MEDIUM-006, MEDIUM-008 | | A06 — Vulnerable Components | ⚠️ PARTIEL | MEDIUM-003, LOW-002, LOW-003 | | A07 — Auth Failures | ✅ PASS | JWT validation solide, rate limiting, session management | | A08 — Software Integrity | ⚠️ PARTIEL | MEDIUM-007 (CI actions) | | A09 — Logging & Monitoring | ✅ PASS | Audit trail, secret filtering, structured logging | | A10 — SSRF | ✅ PASS | Pas de SSRF identifié | ## ANALYSE PAR CATÉGORIE OWASP API TOP 10 | Catégorie | Verdict | Findings | |-----------|---------|----------| | API1 — Broken Object Level Auth | ❌ FAIL | CRIT-001 (IDOR rooms — pas de membership check) | | API2 — Broken Authentication | ⚠️ PARTIEL | HIGH-002 (HS256), MEDIUM-001 (math/rand) | | API3 — Broken Object Property Auth | ✅ PASS | Validation struct tags, pas de mass assignment | | API4 — Unrestricted Resource Consumption | ⚠️ PARTIEL | MEDIUM-004 (pagination limit) | | API5 — Broken Function Level Auth | ✅ PASS | RequireAdmin, RequireRole middleware | | API6 — Unrestricted Business Flows | ⚠️ PARTIEL | HIGH-001 (race condition downloads) | | API7 — SSRF | ✅ PASS | Pas de SSRF identifié | | API8 — Security Misconfiguration | ⚠️ PARTIEL | MEDIUM-002, MEDIUM-006 | | API9 — Improper Inventory | ✅ PASS | API versionnée, pas de endpoints dépréciés exposés | | API10 — Unsafe API Consumption | ✅ PASS | Webhook signatures vérifiées | --- ## CRITÈRES GO/NO-GO v1.0.0 | Critère | Statut | Détail | |---------|--------|--------| | Aucun finding CRITIQUE non résolu | ❌ NO-GO | 2 findings CRITIQUES à corriger (IDOR rooms, play_count public) | | Aucun finding HAUT non résolu | ❌ NO-GO | 10 findings HAUTS à corriger | | Tous les MOYENS ont un plan de remédiation | ✅ GO | 12 findings avec remédiation documentée | | ASVS Level 2 sans FAIL obligatoire | ❌ NO-GO | 3 FAIL (IDOR, race condition, crypto/rand) | **Décision : NO-GO — Corriger les 2 CRITIQUES + 10 HAUTS avant v1.0.0 (~30h d'effort total)** --- *Rapport généré le 2026-03-11 par Claude Opus 4.6* *Méthodologie : OWASP Top 10 (2021), OWASP API Security Top 10 (2023), ASVS v4.0 Level 2* *Référence audit précédent : AUDIT_TECHNIQUE_VEZA_2026-03-04.md*