veza/apps/web/dev_audit/frontend/06_security_frontend.md

153 lines
6.7 KiB
Markdown
Raw Normal View History

# Phase F — Sécurité Frontend
---
## F1. Secrets et données sensibles
### Variables VITE_* exposées au bundle
| Variable | Contenu | Sensible ? |
|----------|---------|-----------|
| `VITE_API_URL` | URL API backend | ❌ Public |
| `VITE_WS_URL` | URL WebSocket | ❌ Public |
| `VITE_STREAM_URL` | URL streaming | ❌ Public |
| `VITE_UPLOAD_URL` | URL upload | ❌ Public |
| `VITE_APP_NAME` | Nom de l'app | ❌ Public |
| `VITE_DEBUG` | Flag debug | ❌ Non sensible |
| `VITE_USE_MSW` | Flag MSW | ❌ Dev only |
| `VITE_FCM_VAPID_KEY` | Clé push notifications | ⚠️ Public par design (VAPID) |
| `VITE_FEATURE_*` | Feature flags | ❌ Non sensible |
**Verdict** : Aucune clé API secrète exposée côté client. ✅
### Fichiers .env
- `.env.local` (450B) : Contient `VITE_DOMAIN`, `VITE_API_URL`, `VITE_WS_URL`, `VITE_STREAM_URL`**pas de secrets**
- `.env.production` (1.8KB) : Contient URLs et config — **pas de secrets**
- `.env.example` (2.2KB) : Template
**Attention** : `.env.local` et `.env.production` sont versionnés (présents dans le repo). Le `.gitignore` ne semble pas exclure `.env.local`. Même si le contenu n'est pas sensible actuellement, c'est un **risque si quelqu'un ajoute un secret à l'avenir**.
### Stockage JWT
- **httpOnly cookies** : Les tokens JWT sont gérés côté backend via cookies httpOnly ✅
- `TokenStorage.getAccessToken()` retourne `null` en mode cookie — pas d'accès JS aux tokens ✅
- `tokenStorage.ts` est un wrapper de compatibilité, pas de stockage réel en localStorage ✅
---
## F2. XSS
### `dangerouslySetInnerHTML`
| Fichier | Ligne | Source des données | Sanitization |
|---------|-------|--------------------|-------------|
| `features/chat/components/ChatMessages.tsx` | 145-147 | `message.content` (backend) | ✅ `sanitizeChatMessage()` via DOMPurify |
| `features/chat/components/virtualized-chat-messages/VirtualizedChatMessageItem.tsx` | 58-60 | `message.content` (backend) | ✅ `sanitizeChatMessage()` via DOMPurify |
**Sanitization** : `utils/sanitize.ts` (429L) utilise DOMPurify avec :
- Allowlist stricte de tags HTML [sanitize.ts]
- Allowlist d'attributs [sanitize.ts]
- URL schemes limitées (http, https, mailto) [sanitize.ts]
- `javascript:` protocol filtré ✅
**Verdict XSS** : ✅ Les deux usages de `dangerouslySetInnerHTML` sont correctement sanitizés.
### Autres vecteurs XSS
- `eval()` / `new Function()` : **0 occurrence**
- Template literals dans le DOM : Non détecté ✅
- `document.write` : **0 occurrence**
---
## F3. Stockage client
### Données en localStorage
| Clé | Données | Sensible ? | Expiration |
|-----|---------|-----------|-----------|
| `ui-storage` | theme, language, sidebarOpen | ❌ | Persist |
| `veza-cart-storage` | Items du panier | ❌ | Persist |
| `auth-storage` | `isAuthenticated` (boolean) | ❌ | Persist |
| `rememberedEmail` | Email utilisateur | ⚠️ PII | Persist |
| `veza_offline_queue` | Requêtes en attente | ⚠️ Peut contenir des données | Nettoyé |
| `PENDING_ANALYTICS_STORAGE_KEY` | Analytics payload | ❌ | Nettoyé |
| `feature-highlight-*` | Dismissal flags | ❌ | Persist |
| `pwa-install-dismissed` | Flag | ❌ | Persist |
| `veza_wrong_server_shown` | Flag toast | ❌ | Persist |
| **Developer API keys** | **Clés API** | **🔴 OUI** | **Persist** |
**Problème critique** : `services/developerService.ts` stocke des clés API dans localStorage. Même si ce sont des clés de développeur (probablement des API keys publiques), le stockage en localStorage les expose à toute extension de navigateur ou code tiers injecté. **Recommandation : déléguer la gestion au backend.**
### sessionStorage
- Pas d'usage sensible détecté ✅
---
## F4. Dépendances
### Vulnérabilités connues
[DONNÉES INSUFFISANTES — nécessite `npm audit` sur la machine]
### Dépendances à risque
| Dépendance | Version | Risque | Usage |
|-----------|---------|--------|-------|
| `dompurify` | 3.3.x | ✅ Activement maintenu | Sanitization |
| `axios` | 1.13.x | ✅ Récent | HTTP |
| `swagger-ui-react` | 5.31.x | ⚠️ Exposé en production ? | Dev tools |
| `hls.js` | 1.6.x | ✅ Maintenu | Streaming |
**Attention** : `swagger-ui-react` et `swagger-ui-dist` sont en `dependencies` (pas `devDependencies`). Si le composant SwaggerUI est accessible en production, cela expose la documentation API. **Vérifier que la route `/developer` est bien protégée et gated.**
---
## F5. Autres vecteurs
### Open redirect
| Fichier | Risque | Détail |
|---------|--------|--------|
| `features/playlists/hooks/usePlaylistNotifications.ts:203,219,235,251` | 🔴 **HAUT** | `window.location.href = notification.link!` — URL provenant du backend, pas de validation. Si un attaquant compromet les notifications, il peut rediriger vers un site malveillant. |
| Autres `window.location.href` | ✅ Sûr | Tous vers des chemins statiques (`/login`, `/marketplace`, etc.) |
### CORS / CSP
- **CORS** : Géré côté backend. Le proxy Vite en dev élimine les problèmes CORS [vite.config.ts:63-76] ✅
- **CSP** : [DONNÉES INSUFFISANTES — nécessite inspection des headers serveur]
### Prototype pollution
- Pas d'usage de `lodash.merge` ou similaire détecté ✅
- `immer` (10.x) utilisé pour l'immutabilité — protège contre la mutation directe ✅
---
## Classement des vulnérabilités
| Gravité | Vulnérabilité | Fichier:ligne | Exploitabilité | Correction urgence |
|---------|--------------|---------------|----------------|-------------------|
| 🔴 CRITIQUE | Open redirect via notification.link | `usePlaylistNotifications.ts:203,219,235,251` | Moyenne (nécessite compromission backend/notifications) | Immédiate — valider l'URL (same-origin ou allowlist) |
| 🟠 HAUTE | Clés API en localStorage | `developerService.ts:33` | Faible (nécessite accès au navigateur) | Court terme — migrer vers backend |
| 🟡 MOYENNE | `.env.local` versionné | `.env.local` | Faible (pas de secrets actuels) | Ajouter `.env.local` au `.gitignore` |
| 🟡 MOYENNE | swagger-ui en production | `package.json` (dependencies) | Faible (route protégée) | Déplacer en devDependencies si non nécessaire en prod |
| 🟢 BASSE | `rememberedEmail` en localStorage | `LoginPage.tsx:115` | Très faible (PII minimal) | Acceptable avec notice RGPD |
---
## Score Sécurité implicite
Ce score n'est pas dans le tableau principal car la pondération est ×1.5, mais les observations sont globalement positives :
- ✅ httpOnly cookies pour JWT
- ✅ CSRF protection
- ✅ DOMPurify pour `dangerouslySetInnerHTML`
- ✅ Zod validation sur les réponses API
- ✅ Pas de `eval()` ni secrets exposés
- ❌ Open redirect dans usePlaylistNotifications
- ❌ Clés API en localStorage
**Score Sécurité : 7/10**