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>
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
- Générer une paire RSA 2048-bit pour la production
- Ajouter dans
docker-compose.prod.yml:- JWT_PRIVATE_KEY_PATH=/secrets/jwt_private.pem - JWT_PUBLIC_KEY_PATH=/secrets/jwt_public.pem - Monter les clés via Docker secrets ou volume sécurisé
- Supprimer
JWT_SECRETde la config production - 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 :
- Les timeouts de requête ne s'appliquent pas aux opérations DB sous-jacentes
- L'annulation de la requête (client disconnect) ne cancelle pas la query DB
- 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
falsesi 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 à :
- Des breaking changes non testées
- Un potentiel supply chain attack (image compromise)
- 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 :
- JWT Token Versioning : Révocation immédiate via
token_versionvérifié contre la DB à chaque requête (auth.go:218) - Token Blacklist Redis : Double vérification avec blacklist Redis quand disponible (
auth.go:189) - Session Validation : Triple vérification (JWT signature → token version → session DB) (
auth.go:175-260) - Security Headers : Ensemble complet (HSTS, CSP strict pour API, X-Frame-Options DENY, nosniff, Referrer-Policy, Permissions-Policy) (
security_headers.go) - CORS Strict : Production requiert whitelist explicite, panic si non configuré (
cors.go) - CSRF Protection : Middleware CSRF avec tokens Redis (
csrf.go) - Rate Limiting Multi-couche : Global, par IP, par endpoint, par user, upload-specific (
rate_limiter.go,ratelimit_redis.go,endpoint_limiter.go) - ClamAV Obligatoire :
CLAMAV_REQUIRED=trueen production (docker-compose.prod.yml:170) - Bcrypt Cost 12 : Conforme aux recommandations OWASP
- Path Validation :
ValidateExecPath()sur tous les appelsexec.Command(utils/sanitizer.go) - Secret Filtering : Logs filtrés pour les secrets (
logging/secret_filter.go) - Audit Trail : Middleware d'audit sur POST/PUT/DELETE (
audit.go) - RBAC Middleware : Contrôle d'accès basé sur les rôles avec middleware dédié (
rbac_middleware.go) - Ownership Checks :
RequireOwnershipOrAdminsur les ressources utilisateur - Webhook HMAC-SHA512 : Vérification de signature avec comparaison constant-time
- Non-root Docker : Container backend tourne en user
app:1001 - Multi-stage Dockerfile : Build optimisé sans outils de compilation dans l'image finale
- GORM : Pas de SQL injection — toutes les queries production utilisent des paramètres préparés
- DOMPurify : Sanitisation HTML côté frontend avec whitelist de tags
- 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-checkerpour npm,golicensepour 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-inlinen'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