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>
This commit is contained in:
parent
1a38fdbcca
commit
41ae04679f
4 changed files with 345 additions and 33 deletions
|
|
@ -73,7 +73,7 @@
|
|||
| V4.1.1 | Trusted enforcement point | ✅ PASS | Middleware chain côté serveur uniquement | — |
|
||||
| V4.1.2 | Access control on every request | ✅ PASS | AuthMiddleware + RBAC sur toutes les routes protégées | — |
|
||||
| V4.1.3 | Principle of least privilege | ✅ PASS | Rôles granulaires (user, creator, premium, admin, moderator) | — |
|
||||
| V4.2.1 | IDOR protection | ✅ PASS | RequireOwnershipOrAdmin sur tracks, playlists, products, messages | — |
|
||||
| V4.2.1 | IDOR protection | ❌ FAIL | Chat rooms (GetRoom, GetRoomHistory) sans vérification membership | CRIT-001 |
|
||||
| V4.2.2 | Prevent privilege escalation | ✅ PASS | Rôle vient du JWT → vérifié contre DB, pas modifiable par le client | — |
|
||||
| V4.3.1 | Admin function protection | ✅ PASS | RequireAdmin middleware sur toutes les routes /admin/ | — |
|
||||
| V4.3.2 | Directory listing disabled | ✅ PASS | API REST pure, pas de file serving sauf signedURL S3 | — |
|
||||
|
|
@ -214,17 +214,18 @@
|
|||
|
||||
| Résultat | Nombre | % |
|
||||
|----------|--------|---|
|
||||
| ✅ PASS | 73 | 82% |
|
||||
| ❌ FAIL | 2 | 2% |
|
||||
| ✅ PASS | 72 | 80% |
|
||||
| ❌ FAIL | 3 | 3% |
|
||||
| ⚠️ PARTIEL | 13 | 15% |
|
||||
| 🔍 NON VÉRIFIABLE | 3 | 3% |
|
||||
| N/A | 1 | 1% |
|
||||
|
||||
**FAIL obligatoires** :
|
||||
- V4.2.1 : IDOR protection → **CRIT-001** (chat rooms sans membership check)
|
||||
- V2.6.1 : Recovery codes crypto/rand → **MEDIUM-001** (à corriger)
|
||||
- V11.1.7 : Concurrency controls → **HIGH-001** (à corriger)
|
||||
|
||||
**Conclusion** : ASVS Level 2 est atteint à condition de corriger les 2 FAILs identifiés.
|
||||
**Conclusion** : ASVS Level 2 NON atteint — 3 FAILs à corriger, dont 1 CRITIQUE (IDOR).
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -17,19 +17,19 @@
|
|||
|
||||
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 : **GO CONDITIONNEL**
|
||||
### Recommandation : **NO-GO — Corriger les CRITIQUES et HAUTS avant v1.0.0**
|
||||
|
||||
L'audit identifie **0 finding CRITIQUE**, **3 findings HAUTS**, **8 findings MOYENS**, **6 findings BAS**, et **5 findings INFO**. Les findings HAUTS doivent être corrigés avant le GO 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 | 0 | — |
|
||||
| HAUTE | 3 | Race condition marketplace, production HS256, user repository context bypass |
|
||||
| MOYENNE | 8 | Recovery codes math/rand, metrics IP spoofing, ClamAV latest, pagination sans limit max, WebSocket rate limit gaps, CORS Swagger CSP, CI actions non-pinnées, RabbitMQ mgmt exposé |
|
||||
| BASSE | 6 | Password policy mismatch FE/BE, Hyperswitch version, dotenv crate, Elasticsearch sans auth, context.Background dans workers, exported debug endpoints |
|
||||
| INFO | 5 | Bonnes pratiques confirmées, recommandations d'amélioration |
|
||||
| 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 |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -37,6 +37,88 @@ L'audit identifie **0 finding CRITIQUE**, **3 findings HAUTS**, **8 findings MOY
|
|||
|
||||
---
|
||||
|
||||
### [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
|
||||
|
|
@ -152,6 +234,146 @@ Propager le context dans toute la chaîne d'appel (handler → service → repos
|
|||
|
||||
---
|
||||
|
||||
### [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
|
||||
|
|
@ -398,6 +620,78 @@ En dev : accès au dashboard RabbitMQ exposant la topologie des queues et les me
|
|||
|
||||
---
|
||||
|
||||
### [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
|
||||
|
|
@ -601,7 +895,7 @@ Aucun bloc `unsafe` trouvé dans le code applicatif du stream server Rust. Le co
|
|||
|
||||
| Catégorie | Verdict | Findings |
|
||||
|-----------|---------|----------|
|
||||
| A01 — Broken Access Control | ✅ PASS | RBAC, ownership checks, CORS strict |
|
||||
| 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) |
|
||||
|
|
@ -616,7 +910,7 @@ Aucun bloc `unsafe` trouvé dans le code applicatif du stream server Rust. Le co
|
|||
|
||||
| Catégorie | Verdict | Findings |
|
||||
|-----------|---------|----------|
|
||||
| API1 — Broken Object Level Auth | ✅ PASS | RequireOwnershipOrAdmin sur toutes les ressources |
|
||||
| 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) |
|
||||
|
|
@ -633,12 +927,12 @@ Aucun bloc `unsafe` trouvé dans le code applicatif du stream server Rust. Le co
|
|||
|
||||
| Critère | Statut | Détail |
|
||||
|---------|--------|--------|
|
||||
| Aucun finding CRITIQUE non résolu | ✅ GO | 0 finding critique |
|
||||
| Aucun finding HAUT non résolu | ❌ NO-GO | 3 findings HAUTS à corriger |
|
||||
| Tous les MOYENS ont un plan de remédiation | ✅ GO | 8 findings avec remédiation documentée |
|
||||
| ASVS Level 2 sans FAIL obligatoire | ⚠️ CONDITIONNEL | Voir checklist ASVS séparée |
|
||||
| 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 3 findings HAUTS avant v1.0.0**
|
||||
**Décision : NO-GO — Corriger les 2 CRITIQUES + 10 HAUTS avant v1.0.0 (~30h d'effort total)**
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -9,9 +9,18 @@
|
|||
|
||||
| # | Finding | Sévérité | CVSS | Fichier(s) | Effort estimé | Priorité | Assignation suggérée | Statut |
|
||||
|---|---------|----------|------|------------|---------------|----------|---------------------|--------|
|
||||
| **CRIT-001** | **IDOR rooms — lecture conversations privées** | **CRITIQUE** | **9.1** | `room_handler.go:134-314` | 3h | **Immédiate** | Backend dev | ⏳ À FAIRE |
|
||||
| **CRIT-002** | **play_count/like_count publics (violation éthique)** | **CRITIQUE** | **5.3** | `models/track.go:39-40` | 4h | **Immédiate** | Backend dev | ⏳ À FAIRE |
|
||||
| HIGH-001 | Race condition TOCTOU downloads marketplace | HAUTE | 7.5 | `marketplace/service.go:794-817` | 2h | Immédiate | Backend dev | ⏳ À FAIRE |
|
||||
| HIGH-002 | Production HS256 au lieu de RS256 | HAUTE | 7.4 | `docker-compose.prod.yml:158`, `jwt_service.go` | 4h | Immédiate | DevOps + Backend | ⏳ À FAIRE |
|
||||
| HIGH-003 | User repository context.Background() bypass | HAUTE | 5.3 | `user_repository.go:125-150` | 4h | Sprint suivant | Backend dev | ⏳ À FAIRE |
|
||||
| HIGH-004 | Race condition codes promo | HAUTE | 7.5 | `marketplace/service.go:463,753` | 2h | Sprint suivant | Backend dev | ⏳ À FAIRE |
|
||||
| HIGH-005 | Race condition licence exclusive | HAUTE | 7.5 | `marketplace/service.go:393-532` | 2h | Sprint suivant | Backend dev | ⏳ À FAIRE |
|
||||
| HIGH-006 | Rate limiter bypass (TrustedProxies) | HAUTE | 7.5 | `rate_limiter.go:131` | 30min | Immédiate | Backend dev | ⏳ À FAIRE |
|
||||
| HIGH-007 | RGPD hard delete incomplet | HAUTE | 6.5 | `hard_delete_worker.go:101` | 4h | Sprint suivant | Backend dev | ⏳ À FAIRE |
|
||||
| HIGH-008 | RTMP callback auth faible | HAUTE | 7.3 | `live_stream_callback.go:25-36` | 1h | Sprint suivant | Backend dev | ⏳ À FAIRE |
|
||||
| HIGH-009 | Co-écoute host hijack | HAUTE | 6.5 | `colistening/hub.go:102` | 1h | Sprint suivant | Backend dev | ⏳ À FAIRE |
|
||||
| HIGH-010 | Modérateur self-strike | HAUTE | 6.5 | `moderation_service.go:725` | 1h | Sprint suivant | Backend dev | ⏳ À FAIRE |
|
||||
| MEDIUM-001 | Recovery codes 2FA avec math/rand | MOYENNE | 5.9 | `two_factor_service.go:200` | 30min | Sprint suivant | Backend dev | ⏳ À FAIRE |
|
||||
| MEDIUM-002 | Metrics IP spoofing via X-Forwarded-For | MOYENNE | 5.3 | `metrics_protection.go:52-54` | 15min | Sprint suivant | Backend dev | ⏳ À FAIRE |
|
||||
| MEDIUM-004 | Pagination sans limite maximale | MOYENNE | 5.3 | Pagination middleware + handlers | 2h | Sprint suivant | Backend dev | ⏳ À FAIRE |
|
||||
|
|
@ -20,6 +29,10 @@
|
|||
| MEDIUM-007 | CI actions non pinnées par SHA | MOYENNE | 4.8 | `.github/workflows/*.yml` | 1h | Sprint suivant | DevOps | ⏳ À FAIRE |
|
||||
| MEDIUM-006 | CSP unsafe-inline Swagger routes | MOYENNE | 4.7 | `security_headers.go:78` | 30min | Backlog | Backend dev | ⏳ À FAIRE |
|
||||
| MEDIUM-008 | RabbitMQ Management UI en prod | MOYENNE | 5.3 | `docker-compose.prod.yml` | 15min | Backlog | DevOps | ⏳ À FAIRE |
|
||||
| MEDIUM-009 | Free trial réutilisable indéfiniment | MOYENNE | 5.3 | `subscription/service.go:236` | 1h | Sprint suivant | Backend dev | ⏳ À FAIRE |
|
||||
| MEDIUM-010 | WebSocket pas de re-validation token | MOYENNE | 5.4 | `chat_websocket_handler.go:32` | 2h | Sprint suivant | Backend dev | ⏳ À FAIRE |
|
||||
| MEDIUM-011 | Email logué en clair | MOYENNE | 4.3 | `handlers/auth.go:52` | 30min | Backlog | Backend dev | ⏳ À FAIRE |
|
||||
| MEDIUM-012 | Analytics sans k-anonymité | MOYENNE | 4.3 | `playback_analytics_handler.go` | 1h30 | Backlog | Backend dev | ⏳ À FAIRE |
|
||||
| LOW-001 | Password policy mismatch FE/BE | BASSE | 3.7 | `passwordValidator.ts` | 30min | Backlog | Frontend dev | ⏳ À FAIRE |
|
||||
| LOW-002 | Hyperswitch version datée | BASSE | 3.1 | `docker-compose*.yml` | 2h (+ tests) | Backlog | DevOps | ⏳ À FAIRE |
|
||||
| LOW-003 | dotenv 0.15 obsolète (Rust) | BASSE | 2.0 | `Cargo.toml` | 30min | Backlog | Backend dev | ⏳ À FAIRE |
|
||||
|
|
@ -33,29 +46,33 @@
|
|||
|
||||
| Priorité | Nombre de findings | Effort total estimé |
|
||||
|----------|--------------------|---------------------|
|
||||
| Immédiate (avant v1.0.0) | 2 | ~6h |
|
||||
| Sprint suivant | 6 | ~4h30 |
|
||||
| Backlog | 8 | ~7h |
|
||||
| **Total** | **16** | **~17h30** |
|
||||
| Immédiate (bloquant v1.0.0) | 5 | ~13h30 |
|
||||
| Sprint suivant | 15 | ~18h |
|
||||
| Backlog | 10 | ~8h |
|
||||
| **Total** | **30** | **~39h30** |
|
||||
|
||||
---
|
||||
|
||||
## Plan de remédiation recommandé
|
||||
|
||||
### Phase 1 — Immédiate (avant merge en main)
|
||||
1. **HIGH-002** : Générer paire RSA 2048-bit, configurer `docker-compose.prod.yml` avec `JWT_PRIVATE_KEY_PATH` / `JWT_PUBLIC_KEY_PATH`, supprimer `JWT_SECRET` en prod
|
||||
2. **HIGH-001** : Wrapper `GetDownloadURL` dans une transaction avec `SELECT FOR UPDATE`
|
||||
### Phase 1 — Immédiate (bloquant — avant merge en main)
|
||||
1. **CRIT-001** : Ajouter vérification membership sur `GetRoom` et `GetRoomHistory`
|
||||
2. **CRIT-002** : Supprimer `play_count`/`like_count` des réponses API publiques
|
||||
3. **HIGH-002** : Migrer production vers RS256 (paire RSA 2048-bit)
|
||||
4. **HIGH-006** : Configurer `engine.SetTrustedProxies()` pour le rate limiter
|
||||
5. **HIGH-001** : Transaction + `SELECT FOR UPDATE` sur `GetDownloadURL`
|
||||
|
||||
### Phase 2 — Sprint suivant
|
||||
3. **HIGH-003** : Refactorer `UserRepository` pour accepter `context.Context` sur toutes les méthodes
|
||||
4. **MEDIUM-001** : Remplacer `mathrand.Intn` par `crypto/rand.Int` dans `two_factor_service.go`
|
||||
5. **MEDIUM-002** : Supprimer la lecture de `X-Forwarded-For` dans `metrics_protection.go`
|
||||
6. **MEDIUM-003** : Pinner ClamAV à une version spécifique
|
||||
7. **MEDIUM-004** : Ajouter `MaxPageSize = 100` dans le middleware de pagination
|
||||
8. **MEDIUM-007** : Pinner les actions CI par SHA commit
|
||||
6. **HIGH-004/005** : Protéger codes promo et licences exclusives contre race conditions
|
||||
7. **HIGH-007** : Compléter le hard delete RGPD (Redis, ES, audit_logs)
|
||||
8. **HIGH-008** : Corriger RTMP callback auth (supprimer fallback query param)
|
||||
9. **HIGH-009/010** : Corriger co-écoute et modération
|
||||
10. **MEDIUM-001** : crypto/rand pour recovery codes 2FA
|
||||
11. **MEDIUM-009/010** : Free trial + WebSocket re-auth
|
||||
12-15. Autres MEDIUM
|
||||
|
||||
### Phase 3 — Backlog
|
||||
9-16. Corrections restantes (LOW + MEDIUM backlog)
|
||||
16-30. Corrections LOW + MEDIUM backlog
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1127,7 +1127,7 @@ Référence : ORIGIN_REVISION_SUMMARY.md §6 Incohérence #2
|
|||
- [x] Rapport de remédiation documenté : `REMEDIATION_MATRIX_v0.12.6.md`
|
||||
- [x] Checklist ASVS Level 2 : `ASVS_CHECKLIST_v0.12.6.md`
|
||||
|
||||
**Résultat de l'audit** : 0 CRITIQUE, 3 HAUTE, 8 MOYENNE, 6 BASSE, 5 INFO. GO CONDITIONNEL (corriger les 3 HAUTE avant v1.0.0)
|
||||
**Résultat de l'audit** : 2 CRITIQUE, 10 HAUTE, 12 MOYENNE, 6 BASSE, 5 INFO. NO-GO (corriger les 2 CRITIQUES + 10 HAUTS avant v1.0.0 — ~30h)
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue