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>
941 lines
40 KiB
Markdown
941 lines
40 KiB
Markdown
# 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=<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**
|
|
```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*
|