veza/PENTEST_REPORT_VEZA_v0.12.6.md
senke f56b5a2b45 feat(v0.12.6): consolidated audit — 2 CRITICAL, 10 HIGH findings
Deep audit with 6 parallel analysis passes reveals additional findings:

CRITICAL:
- CRIT-001: IDOR on chat rooms — any user can read private conversations
- CRIT-002: play_count/like_count publicly exposed (violates VEZA ethics)

NEW HIGH:
- HIGH-004/005: Race conditions on promo codes and exclusive licenses
- HIGH-006: Rate limiter bypass via X-Forwarded-For (no TrustedProxies)
- HIGH-007: GDPR hard delete incomplete (Redis, ES, audit_logs)
- HIGH-008: RTMP callback auth fallback to stream_key as secret
- HIGH-009: Co-listening host hijack by non-host participants
- HIGH-010: Moderator can issue strikes without conflict-of-interest check

Total: 2 CRITICAL, 10 HIGH, 12 MEDIUM, 6 LOW, 5 INFO
Estimated remediation: ~39h30

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 15:44:51 +01:00

40 KiB

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

# L'utilisateur B (non membre) accède à la conversation privée de l'utilisateur A
curl -H "Cookie: access_token=<jwt_user_B>" \
  https://veza.fr/api/v1/conversations/<room_uuid_private>/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

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

// 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

// 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 :
    - 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

// 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 :

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

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

// 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

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.

// 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

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).

// 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 :

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 :

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.

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 :

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 :

# 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

# 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:234secret: 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