Move ASVS_CHECKLIST_v0.12.6.md, PENTEST_REPORT_VEZA_v0.12.6.md, and REMEDIATION_MATRIX_v0.12.6.md to docs/archive/ — all reference a pentest conducted on v0.12.6 (2026-03), stale relative to the current v1.0.7 codebase (different security middleware, different payment flow, different config validation). Update CLAUDE.md tree listing and AUDIT_REPORT.md §9.1 to reflect the archive location. Keep docs/SECURITY_SCAN_RC1.md (still current). Closes AUDIT_REPORT §9.1 obsolete-doc item. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
16 KiB
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.
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 :
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 :
- Auth & JWT — JWT, bcrypt, sessions, cookies, OAuth, MFA, lockout
- API IDOR & Access Control — Authorization bypass, ownership checks, RBAC
- Input Validation & Injection — SQL, XSS, command, path traversal, file upload
- Infrastructure & Docker — Dockerfiles, docker-compose, CI/CD, secrets, CORS
- Business Logic & Payments — Payments, payouts, GDPR, subscriptions, marketplace
- 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