# Rapport de Pentest — VEZA v0.12.6 **Date** : 2026-03-13 **Auditeur** : Claude Opus 4.6 (SAST + revue manuelle de code + 6 agents d'audit spécialisés) **Scope** : Monorepo complet — Go backend, Rust stream server, React frontend, CI/CD, infrastructure Docker **Méthodologie** : OWASP Top 10 2021, OWASP API Security Top 10 2023, ASVS Level 2 **Référence** : ORIGIN_SECURITY_FRAMEWORK.md --- ## Résumé Exécutif | Sévérité | Count | |----------|-------| | CRITICAL | 5 | | HIGH | 10 | | MEDIUM | 12 | | LOW | 6 | | INFO | 3 | | **Total findings** | **36** | | **PASS (contrôles validés)** | **24** | **Posture globale** : La codebase VEZA présente une architecture sécurisée avec d'excellentes fondations (JWT RS256, bcrypt, HMAC webhooks, rate limiting Redis). Cependant, l'audit approfondi révèle 5 vulnérabilités critiques — principalement des race conditions financières, des IDOR sur les analytics, et des WebSocket sans validation d'origine. Ces issues sont toutes remédiables sans refonte architecturale. --- ## CRITICAL FINDINGS ### CRIT-001 — WebSocket Cross-Site Hijacking (3 handlers) | Champ | Valeur | |-------|--------| | **Sévérité** | CRITICAL | | **OWASP** | A07:2021 — Cross-Site Request Forgery | | **CWE** | CWE-346 (Origin Validation Error) | | **Fichiers** | `chat_websocket_handler.go:50`, `playback_websocket_handler.go:106`, `co_listening_websocket_handler.go:105` | | **CVSS** | 8.1 | **Description** : Les 3 handlers WebSocket utilisent `InsecureSkipVerify: true` dans `websocket.AcceptOptions`, désactivant la validation du header Origin. Un attaquant peut établir une connexion WebSocket depuis n'importe quel domaine et usurper les sessions utilisateur. ```go conn, err := websocket.Accept(c.Writer, c.Request, &websocket.AcceptOptions{ InsecureSkipVerify: true, // VULNÉRABLE }) ``` **Impact** : Cross-Site WebSocket Hijacking (CSWSH) — un attaquant peut intercepter les messages chat, contrôler la lecture audio, et accéder aux sessions d'écoute collaborative. **Remédiation** : Supprimer `InsecureSkipVerify: true`, implémenter `OriginPatterns` avec whitelist des domaines autorisés. --- ### CRIT-002 — Payout Balance Race Condition (Double-Spend) | Champ | Valeur | |-------|--------| | **Sévérité** | CRITICAL | | **OWASP** | API6:2023 — Unrestricted Access to Sensitive Business Flows | | **CWE** | CWE-362 (Race Condition) | | **Fichier** | `veza-backend-api/internal/core/marketplace/payout.go:175-195` | | **CVSS** | 9.1 | **Description** : `RequestPayout()` lit le solde hors transaction (`GetSellerBalance`), puis ouvre une transaction pour déduire. Entre la lecture et la déduction, une requête concurrente peut aussi lire le même solde et traiter un deuxième payout. **Scénario d'attaque** : Seller avec $100 → 2 requêtes simultanées → les 2 lisent $100 → les 2 traitent $100 → **$200 payés pour $100 de solde**. **Remédiation** : Ajouter `SELECT FOR UPDATE` (`clause.Locking{Strength: "UPDATE"}`) sur la ligne de balance DANS la transaction, avant la validation du montant. --- ### CRIT-003 — Refund Double-Pay Race Condition | Champ | Valeur | |-------|--------| | **Sévérité** | CRITICAL | | **CWE** | CWE-362 | | **Fichier** | `marketplace/service.go:1136-1189` | | **CVSS** | 8.7 | **Description** : `RefundOrder()` lit l'order hors transaction, vérifie l'autorisation, puis appelle `rp.Refund()`. Deux requêtes concurrentes peuvent toutes les deux passer la validation et déclencher un double remboursement. **Remédiation** : Wrapper la lecture + validation + refund dans une seule transaction avec `SELECT FOR UPDATE` sur l'order. Vérifier `order.Status != "refunded"` atomiquement. --- ### CRIT-004 — Track Analytics IDOR (No Ownership Check) | Champ | Valeur | |-------|--------| | **Sévérité** | CRITICAL | | **OWASP** | API1:2023 — Broken Object Level Authorization | | **CWE** | CWE-639 (Authorization Bypass) | | **Fichier** | `analytics/handler.go:727-777` | | **CVSS** | 7.5 | **Description** : `GET /api/v1/analytics/tracks/:id` retourne les analytics détaillées (play count, completion rate, listener data) de n'importe quelle track sans vérifier que l'utilisateur authentifié en est le créateur. **Impact** : Tout utilisateur authentifié peut espionner les analytics privées de n'importe quel créateur. **Remédiation** : Ajouter une vérification `track.CreatorID == userID` avant de retourner les analytics. --- ### CRIT-005 — Path Traversal in Marketplace Preview Upload | Champ | Valeur | |-------|--------| | **Sévérité** | CRITICAL | | **CWE** | CWE-22 (Path Traversal) | | **Fichier** | `handlers/marketplace.go:285-290` | | **CVSS** | 8.8 | **Description** : Le nom de fichier uploadé est utilisé directement dans `filepath.Join` sans sanitization : ```go destPath := filepath.Join(previewDir, file.Filename) // VULNÉRABLE ``` Un attaquant peut uploader un fichier avec `../../../etc/cron.d/evil` pour écrire n'importe où sur le filesystem. **Remédiation** : Utiliser `filepath.Base(file.Filename)` ou générer un UUID comme nom de fichier. --- ## HIGH FINDINGS ### HIGH-001 — IP Spoofing via X-Forwarded-For (Rate Limit Bypass) | Champ | Valeur | |-------|--------| | **Sévérité** | HIGH | | **OWASP** | API4:2023 — Unrestricted Resource Consumption | | **Fichier** | `handlers/common.go:601-610` | | **CVSS** | 7.5 | **Description** : `GetClientIP()` lit directement `X-Forwarded-For` sans passer par `c.ClientIP()` de Gin. Permet le contournement du rate limiting. **Remédiation** : Remplacer par `c.ClientIP()` + configurer `SetTrustedProxies()`. --- ### HIGH-002 — Métriques de popularité exposées dans l'API publique | Champ | Valeur | |-------|--------| | **Sévérité** | HIGH | | **Fichiers** | `user_service.go:75`, `social_service.go:321`, `types/stats.go:31`, `social/models.go:32` | **Description** : `followers_count`, `following_count` exposés dans les réponses publiques (`PublicUserResponse`, `SuggestionUser`). `like_count` exposé sur le modèle `Post` social. **Remédiation** : Supprimer ces champs des réponses publiques. Les exposer uniquement dans le dashboard privé. --- ### HIGH-003 — Mass Assignment / Privilege Escalation (UpdateUser) | Champ | Valeur | |-------|--------| | **Sévérité** | HIGH | | **CWE** | CWE-915 (Mass Assignment) | | **Fichier** | `api/user/handler.go:75-95`, `api/user/service.go:188-267` | **Description** : `UpdateMe()` appelle `UpdateUser()` qui accepte des champs `Role`, `IsVerified`, `IsActive`. Un utilisateur non-admin pourrait escalader ses privilèges en envoyant `{"role": "admin"}`. **Remédiation** : Filtrer les champs autorisés par rôle. Seuls les admins peuvent modifier `role`, `is_verified`, `is_active`. --- ### HIGH-004 — RTMP Callback Authentication Faible | Champ | Valeur | |-------|--------| | **Sévérité** | HIGH | | **CWE** | CWE-345 | | **Fichier** | `handlers/live_stream_callback.go:26-84` | **Description** : Les callbacks Nginx-RTMP utilisent un simple header secret statique (`X-RTMP-Callback-Secret`) sans HMAC, sans timestamp, sans protection contre le replay. **Remédiation** : Implémenter HMAC-SHA256 avec timestamp + body dans la signature. --- ### HIGH-005 — Refresh Token TOCTOU Race Condition | Champ | Valeur | |-------|--------| | **Sévérité** | HIGH | | **CWE** | CWE-362 | | **Fichier** | `auth/service.go:653-707` | **Description** : Entre `Validate()` et `Rotate()` du refresh token, une requête concurrente peut réutiliser le même token. Le `RefreshLock` (line 665-671) réduit la fenêtre mais ne l'élimine pas. **Remédiation** : Combiner validate + rotate dans une seule transaction DB atomique. --- ### HIGH-006 — Promo Code Counter Race Condition | Champ | Valeur | |-------|--------| | **Sévérité** | HIGH | | **CWE** | CWE-362 | | **Fichier** | `marketplace/service.go:464, 764-787` | **Description** : Le `used_count` du promo code est incrémenté APRÈS validation mais sans garantie atomique. Deux requêtes simultanées peuvent dépasser `MaxUses`. **Remédiation** : Effectuer validation et incrémentation dans la même transaction avec `SELECT FOR UPDATE`. --- ### HIGH-007 — Stream Token Replay Attack (Rust) | Champ | Valeur | |-------|--------| | **Sévérité** | HIGH | | **CWE** | CWE-433 | | **Fichier** | `veza-stream-server/src/auth/token_validator.rs:100-138` | **Description** : Les URLs de streaming signées avec HMAC-SHA256 vérifient l'expiration mais pas la réutilisation. Un attaquant peut rejouer une URL valide multiple fois avant expiration, gonflant artificiellement les analytics. **Remédiation** : Ajouter un nonce ou stocker les tokens consommés dans Redis avec TTL. --- ### HIGH-008 — Account Deletion Missing Financial Data Cascade | Champ | Valeur | |-------|--------| | **Sévérité** | HIGH | | **Fichier** | `handlers/account_deletion_handler.go:75-135` | **Description** : La suppression de compte anonymise l'utilisateur mais ne traite pas : orders (buyer), licenses, seller payouts, seller balance, seller transfers. **Remédiation** : Ajouter le nettoyage GDPR pour les données financières (anonymiser plutôt que supprimer pour les obligations légales). --- ### HIGH-009 — Webhook Amount Not Re-Validated | Champ | Valeur | |-------|--------| | **Sévérité** | HIGH | | **CWE** | CWE-1025 | | **Fichier** | `marketplace/service.go:610-676` | **Description** : `ProcessPaymentWebhook()` ne vérifie pas que le montant payé (Hyperswitch) correspond au total de l'order. Un webhook forgé pourrait confirmer un paiement inférieur. **Remédiation** : Comparer `order.TotalAmount` avec le montant du webhook avant de traiter. --- ### HIGH-010 — Free Trial Reuse Not Atomic | Champ | Valeur | |-------|--------| | **Sévérité** | HIGH | | **CWE** | CWE-362 | | **Fichier** | `subscription/service.go:237-251` | **Description** : La vérification du trial gratuit (`previousTrialCount`) est hors transaction. Deux requêtes concurrentes peuvent toutes les deux voir `count = 0` et obtenir un trial. **Remédiation** : Déplacer la vérification dans la transaction avec `SELECT FOR UPDATE` sur le user. --- ## MEDIUM FINDINGS ### MEDIUM-001 — Incohérence bcrypt cost (10 vs 12) **Fichier** : `auth/service.go:156, :986` | `bcrypt.DefaultCost` au lieu de 12 ### MEDIUM-002 — GitHub Actions non épinglées par SHA (12+ actions) **Fichiers** : `sast.yml`, `security-scan.yml`, `cd.yml`, `stream-ci.yml`, `container-scan.yml` ### MEDIUM-003 — CORS .env.production avec HTTP **Fichier** : `.env.production:40` ### MEDIUM-004 — MinIO bucket publicly readable **Fichier** : `docker-compose.yml:296` — `mc anonymous set download veza/veza-files/public` ### MEDIUM-005 — Unbounded pagination (multiple handlers) **Fichiers** : `api/user/handler.go`, `core/admin/handler.go`, et ~15 autres endpoints sans `max(limit, 100)` ### MEDIUM-006 — File upload size validation missing (marketplace) **Fichier** : `handlers/marketplace.go:270-289` — pas de vérification `file.Size` ### MEDIUM-007 — Nginx RTMP wildcard CORS **Fichier** : `infra/nginx-rtmp/nginx.conf:46` — `Access-Control-Allow-Origin: *` ### MEDIUM-008 — Redis unprotected in development **Fichier** : `docker-compose.yml:35-56` — pas de `--requirepass` en dev ### MEDIUM-009 — Nginx production missing HSTS header **Fichier** : `apps/web/nginx.production.conf:11-14` ### MEDIUM-010 — WebSocket message size not limited (playback) **Fichier** : `playback_websocket_handler.go:138-152` — pas de `conn.SetReadLimit()` ### MEDIUM-011 — License download missing expiration check **Fichier** : `marketplace/service.go:806-840` — `expires_at` non vérifié ### MEDIUM-012 — pprof profiling endpoint exposed **Fichier** : `cmd/api/main.go:8` — `import _ "net/http/pprof"` sans restriction d'accès --- ## LOW FINDINGS ### LOW-001 — Cookie SameSite=Lax au lieu de Strict **Fichier** : `config/config.go:138` ### LOW-002 — CASCADE DELETE extensif (30+ migrations) **Fichier** : `migrations/*.sql` ### LOW-003 — utils.HashPassword utilise bcrypt.DefaultCost **Fichier** : `utils/utils.go:57` ### LOW-004 — Staging validation workflow actions non épinglées **Fichier** : `staging-validation.yml:35` ### LOW-005 — Docker base images use :latest (dev Dockerfiles) **Fichiers** : `veza-backend-api/Dockerfile:28`, `veza-stream-server/Dockerfile:29` ### LOW-006 — JWT_SECRET hardcoded fallback in docker-compose **Fichier** : `docker-compose.yml:171` — `dev-secret-key-minimum-32-characters-long` --- ## INFO FINDINGS ### INFO-001 — Binary database dump dans le repo **Fichier** : `veza-backend-api/veza_back_api_db/` ### INFO-002 — hash_gen/create_test_user tools use bcrypt.DefaultCost **Fichiers** : `cmd/tools/hash_gen/main.go:11`, `cmd/tools/create_test_user/main.go:54,72` ### INFO-003 — No JWT key rotation mechanism **Fichier** : `services/jwt_service.go` — pas de `kid` header, pas de support multi-clés --- ## Contrôles Validés (PASS) | # | Contrôle | Statut | Preuve | |---|----------|--------|--------| | P01 | Injection SQL | ✅ PASS | Toutes les requêtes utilisent des placeholders. ORDER BY protégé par whitelist. | | P02 | Injection de commande | ✅ PASS | ClamAV et FFmpeg via `exec.CommandContext` avec arguments séparés. | | P03 | JWT RS256 en production | ✅ PASS | RS256 primary, HS256 dev-only fallback. Algorithm validation empêche `alg: none`. | | P04 | Secrets non exposés | ✅ PASS | `json:"-"` sur passwords, tokens, secrets MFA, API keys hashées. | | P05 | crypto/rand (pas math/rand) | ✅ PASS | 19 fichiers utilisent `crypto/rand`. | | P06 | Webhook HMAC-SHA512 | ✅ PASS | `hmac.Equal()` timing-safe. | | P07 | Cookie HttpOnly | ✅ PASS | `CookieHttpOnly: true`. | | P08 | Cookie Secure auto-detect | ✅ PASS | `getCookieSecure()` retourne true en production. | | P09 | Track metrics hidden (backend) | ✅ PASS | `PlayCount` et `LikeCount` ont `json:"-"` sur le modèle Track. | | P10 | Rust memory safety | ✅ PASS | Aucun bloc `unsafe`. | | P11 | GDPR export | ✅ PASS | Endpoint fonctionnel, rate limité (3/24h). | | P12 | GDPR deletion flow | ✅ PASS | Anonymisation + hard delete 30j. | | P13 | Password validation | ✅ PASS | Complexité, historique 5 derniers, max 72 bytes. | | P14 | Account lockout | ✅ PASS | 5 attempts → 30min lockout, Redis + fallback in-memory. | | P15 | CSRF protection | ✅ PASS | 32-byte tokens, `subtle.ConstantTimeCompare`, Redis storage. | | P16 | Security headers (backend) | ✅ PASS | HSTS, CSP, X-Frame-Options, COEP/COOP. | | P17 | Container scanning | ✅ PASS | Trivy scans dans CI/CD. | | P18 | Secret scanning | ✅ PASS | Gitleaks action. | | P19 | OAuth PKCE | ✅ PASS | RFC 7636 implémenté (code_verifier + S256 challenge). | | P20 | OAuth state parameter | ✅ PASS | Généré avec crypto/rand, validé et consommé. | | P21 | OAuth redirect whitelist | ✅ PASS | URL validée contre whitelist configurée. | | P22 | Chat room membership | ✅ PASS | `CanRead()`, `CanSend()`, `CanJoin()` vérifient l'appartenance. | | P23 | Chat XSS protection | ✅ PASS | DOMPurify (frontend) + `html.EscapeString` (backend). | | P24 | Chat rate limiting | ✅ PASS | Redis sliding window, 10 msg/sec, fallback in-memory. | --- ## Méthodologie ### Agents d'audit spécialisés 6 agents parallèles ont couvert les domaines suivants : 1. **Auth & JWT** — JWT, bcrypt, sessions, cookies, OAuth, MFA, lockout 2. **API IDOR & Access Control** — Authorization bypass, ownership checks, RBAC 3. **Input Validation & Injection** — SQL, XSS, command, path traversal, file upload 4. **Infrastructure & Docker** — Dockerfiles, docker-compose, CI/CD, secrets, CORS 5. **Business Logic & Payments** — Payments, payouts, GDPR, subscriptions, marketplace 6. **WebSocket, Rust & Dependencies** — WS security, memory safety, SCA ### Scope couvert | Composant | Fichiers analysés | |-----------|------------------| | Go backend | ~200 fichiers .go | | Rust stream server | ~15 fichiers .rs | | React frontend | ~50 composants clés | | CI/CD | 11 workflows GitHub Actions | | Docker | 5 Dockerfiles, 5 docker-compose | | Migrations | 30+ fichiers SQL | | Configuration | .env.*, configs/, nginx | --- *Rapport généré le 2026-03-13 — VEZA v0.12.6 Pentest Security Audit* *Auditeur : Claude Opus 4.6 + 6 agents spécialisés*