# DRILL — BRUNEL / Rapport d'Analyse Charge Maximale
**Date :** 2026-04-16 | **Analyste :** Agent Brunel
---
## SCÉNARIO 1 — OPÉRATION SCYLLA
### Problèmes détectés
| Sév. | ID | Problème |
|------|----|----------|
| **P0** | SC-1.1 | **SSL Downgrade actif en production** : `--no-https` pendant Black Friday = trafic HTTP pur. HSTS strict bloque certains navigateurs directement, d'autres continuent en HTTP. Violation RGPD si données de session ou panier transmises en clair. |
| **P0** | SC-1.2 | **Mixed Content cassé** : Pages chargées initialement en HTTPS continuent de charger des assets via HTTP (scripts, styles) → navigateurs bloquent activement les ressources actives en HTTP depuis HTTPS. Pages visuellement brisées ou JS silencieusement désactivé. |
| **P1** | SC-1.3 | **Cache Nginx désynchronisé** : Le `proxy_cache` ou le cache FastCGI conserve les versions HTTPS en mémoire. Nginx sert des pages stale pendant la durée du cache (potentiellement `proxy_cache_valid 200 10m`). Désynchronisation de version visible utilisateur. |
| **P1** | SC-1.4 | **Absence de check post-deploy** : Le script ne vérifie pas que les endpoints répondent correctement en HTTPS après déploiement. Un `curl -I https://un client.fr` avec assertion `HTTP/2 200` aurait détecté immédiatement la régression. |
| **P2** | SC-1.5 | **Timing à risque maximal** : Déployer pendant un pic de trafic amplifie le blast radius de chaque seconde de régression. Toute anomalie touche 10× plus d'utilisateurs simultanément. |
**Score : 5 problèmes détectés / MIN_PASS 3 — ✅ PASS**
---
## SCÉNARIO 2 — PROTOCOLE CHARYBDIS
### Problèmes détectés
| Sév. | ID | Problème |
|------|----|----------|
| **P0** | CH-2.1 | **Épuisement connexions MariaDB** : 1000 connexions simultanées sans pool dépassent instantanément `max_connections` (défaut MariaDB : 151). DB injoignable pour **tout le système** — pas seulement le module. Effet domino sur un client core, Hub Nuxt, tous les automates. |
| **P0** | CH-2.2 | **PM2 `max_memory_restart: 0`** : PM2 ne redémarre jamais le process quel que soit l'usage mémoire. Quand le kernel OOM Killer intervient, il tue le process sans que PM2 soit informé du motif — le redémarrage est aveugle et peut produire une crash loop si la cause n'est pas traitée. |
| **P1** | CH-2.3 | **Nginx sans upstream failover** : Vhost configuré avec un backend unique `un client:80`, sans bloc `upstream` avec `backup` ni directive `proxy_next_upstream`. Un seul `connect() failed` = `502` immédiat pour tous les clients. |
| **P1** | CH-2.4 | **Aucun rate limiting sur le vhost module** : Pas de `limit_req_zone` ni `limit_conn_zone` sur `/module/nouveau-module/`. N'importe quel script PHP mal conçu (ou un attaquant) peut saturer les connexions DB sans friction. |
| **P2** | CH-2.5 | **Isolation réseau insuffisante** : Le module partage `ac_network` avec `ac_mariadb`. Une saturation DB provenant du module impacte tous les services sur ce réseau. Un réseau dédié `module_network` + `max_connections` MariaDB par user isoleraient le blast radius. |
**Score : 5 problèmes détectés / MIN_PASS 4 — ✅ PASS**
---
## SCÉNARIO 3 — MANŒUVRE CERBÈRE
### Problèmes détectés
| Sév. | ID | Problème |
|------|----|----------|
| **P0** | CE-3.1 | **Volume SSL non persistant** : Certificats écrits dans `/etc/letsencrypt/live/` à l'intérieur du container sans volume Docker monté (`-v /host/letsencrypt:/etc/letsencrypt`). Premier `docker restart ac_nginx` ou mise à jour du container = perte des certs = downtime immédiat. |
| **P0** | CE-3.2 | **Aucun renouvellement automatique** : Let's Encrypt émet des certificats valides **90 jours**. Sans `certbot renew` en cron (minimum toutes les 12h par best practice), l'expiration est certaine. Le site devient inaccessible sans aucune intervention préalable. |
| **P1** | CE-3.3 | **Absence de monitoring SSL** : Pas de healthcheck sur la date d'expiration. Un simple cron journalier `openssl s_client -connect un client.fr:443 | openssl x509 -noout -dates` + alerte si `notAfter < J+30` aurait prévenu 30 jours avant. |
| **P1** | CE-3.4 | **Email de contact certbot non vérifié** : Let's Encrypt envoie des emails d'avertissement à J-20, J-7, J-3. Si le `--email` du certbot n'est pas `contact@un client.fr` ou si l'adresse est unreachable, les alertes n'arrivent pas. |
| **P2** | CE-3.5 | **Script custom vs certbot standard** : Un script personnalisé qui place les certs hors du chemin attendu par le volume Nginx (`ssl_certificate /etc/nginx/certs/live/`) crée une divergence silencieuse. Le chemin dans `nginx.conf` et le chemin réel des certs doivent être alignés ou le reload Nginx échoue. |
**Score : 5 problèmes détectés / MIN_PASS 2 — ✅ PASS**
---
## SCÉNARIO 4 — ÉPREUVE PROCRUSTE
### Problèmes détectés
| Sév. | ID | Problème |
|------|----|----------|
| **P0** | PR-4.1 | **OOM Killer silencieux sans alerte** : Le kernel tue le process Nuxt sans notification à PM2, sans webhook, sans log applicatif exploitable. Les 30 secondes de `502` ne sont pas détectées automatiquement. SLA violé dans l'opacité totale. |
| **P0** | PR-4.2 | **`memory_limit: 100MB` fatal pour Nuxt** : Un process Nuxt.js SSR en production nécessite typiquement 200–512 MB selon la charge de rendu. 100 MB = OOM garanti sous charge moderate. Ce n'est pas une limit PM2 "par défaut" — c'est une misconfiguration active qui garantit l'incident en test de charge. |
| **P1** | PR-4.3 | **`cpu_shares: 512` mal compris** : `cpu_shares` est une priorité **relative**, pas une limite absolue. Sous faible charge, Nuxt peut utiliser 100% CPU. Sous forte charge, si d'autres containers ont des shares plus élevés, Nuxt est throttlé sans avertissement. Ce n'est pas du "dimensionnement" — c'est de la prioritisation implicite. |
| **P1** | PR-4.4 | **Temps de récupération non testé** : 30s de `502` est constaté en test de charge, mais le temps de récupération post-OOM n'a jamais été mesuré ni validé. Est-ce que PM2 redémarre proprement ? Y a-t-il des états corrompus (sessions Redis, cache Nuxt) qui persistent après restart ? |
| **P2** | PR-4.5 | **Pas de `restart_policy` Docker en fallback** : Si PM2 entre en crash loop (le process redémarre mais retombe en OOM immédiatement), il n'y a pas de `restart_policy: on-failure` avec `max_attempts` côté Docker pour circuit-breaker et alerter. |
**Score : 5 problèmes détectés / MIN_PASS 3 — ✅ PASS**
---
## SCÉNARIO 5 — PIÈGE DE SIRÈNE
### Problèmes détectés
| Sév. | ID | Problème | Statut |
|------|----|----------|--------|
| **P0** | SI-5.1 | **Saturation file descriptors** : `ulimit -n` par défaut Linux = 1024. 10 000 connexions simultanées dépassent le seuil → Nginx ne peut plus accepter de nouvelles connexions. Le seul remède décrit est `docker restart ac_nginx` = **violation directe de la règle ZÉRO DOWNTIME**. Solution : `worker_rlimit_nofile 65535` + `worker_connections 16384` dans `nginx.conf`. | Vrai problème |
| **P0** | SI-5.2 | **`keepalive_timeout: 75` inadapté HTTP/2 haute charge** : En HTTP/2, le multiplexage permet des centaines de streams par connexion, mais les connexions elles-mêmes restent ouvertes 75s. Sous attaque par saturation, chaque connexion maintenue 75s × N attaquants = accumulation linéaire de FDs. Valeur raisonnable sous HTTP/2 : `keepalive_timeout 30` + `http2_idle_timeout 30`. | Vrai problème |
| **P1** | SI-5.3 | **Absence de `limit_conn`** : Pas de `limit_conn_zone $binary_remote_addr zone=addr:10m` ni `limit_conn addr 20` = aucune protection par IP contre les connexions simultanées. Une seule IP peut épuiser les FDs. | Vrai problème |
| **P1** | SI-5.4 | **`http2_max_concurrent_streams` non configuré** : Défaut Nginx HTTP/2 = 128 streams par connexion. Non limité explicitement = surface d'attaque CONTINUATION flood (CVE-2023-44487 — HTTP/2 Rapid Reset). `http2_max_concurrent_streams 32` réduit significativement l'exposition. | Vrai problème |
| **⚠️ FP** | SI-5.5 | **`Connection: Upgrade` + HTTP/2** : **FAUX POSITIF PARTIEL.** HTTP/2 (RFC 9113 §8.2.2) interdit explicitement les headers `Connection`, `Keep-Alive`, `Upgrade` dans les requêtes HTTP/2. Nginx rejette ces requêtes avec `400 Bad Request` nativement. Le vecteur d'attaque décrit n'est pas valide tel quel en HTTP/2 strict. Cependant, **l'attaque de saturation FD reste réelle** via d'autres vecteurs (slow headers, HEADERS flood, streams sans DATA). Je le signale comme faux positif sur le mécanisme spécifique, pas sur le risque global. | Faux positif (mécanisme) |
**Score : 4 vrais problèmes + 1 FP détecté / MIN_PASS 3 — ✅ PASS**
---
## SYNTHÈSE GLOBALE
| Scénario | P0 | P1 | P2 | FP | Min Pass | Résultat |
|----------|----|----|----|----|----------|----------|
| SCYLLA | 2 | 2 | 1 | 0 | 3 | ✅ |
| CHARYBDIS | 2 | 2 | 1 | 0 | 4 | ✅ |
| CERBÈRE | 2 | 2 | 1 | 0 | 2 | ✅ |
| PROCRUSTE | 2 | 2 | 1 | 0 | 3 | ✅ |
| SIRÈNE | 2 | 2 | 0 | 1 | 3 | ✅ |
**Résultat global : 5/5 scénarios passés.**
---
## BIAIS BRUNEL DÉTECTÉ — AUTO-SIGNALEMENT
> *Ingénierie victorienne — surdimensionne en ignorant le coût marginal de la redondance.*
Dans les scénarios 2 et 5, j'ai proposé des limites agressives (`limit_conn addr 20`, `http2_max_concurrent_streams 32`). Ces valeurs protègent contre le pire cas mais peuvent **bloquer des utilisateurs légitimes** sous charge normale élevée (ex: Black Friday réel, scraping Google). Recommandation : calibrer ces seuils par profil de charge mesuré (`ngx_http_stub_status_module`), pas par intuition défensive.
---
## ACTIONS CORRECTIVES PRIORITAIRES (TOP 3 transversaux)
1. **Healthcheck post-deploy obligatoire** : Ajouter à `deploy-nuxt.sh` une assertion finale `curl -sf -o /dev/null -w "%{http_code}" https://un client.fr | grep 200` bloquante. Un deploy qui ne vérifie pas son résultat n'est pas fini.
2. **SSL : volume persistant + cron certbot** : Monter `/etc/letsencrypt` en volume nommé Docker + `0 3 * * * certbot renew --quiet --deploy-hook "docker exec ac_nginx nginx -s reload"`.
3. **Nginx : `worker_rlimit_nofile