Aller au contenu principal

ADR-018 : Durcissement sécurité P0 — Pentest Team Coca

StatutAccepté
Date2026-01-25
AuteursChristophe ABOULICAM, Team Coca (Chucky, N3m0, Gh0st, Pr1nc3ss)
RelecteursOSS Killer, Archi Vétéran
TicketsCAB-938, CAB-939, CAB-945, CAB-950
Score10/10 (approbation unanime)

Contexte

Un test de pénétration interne mené par Team Coca le 2026-01-25 a identifié quatre vulnérabilités de sécurité P0 (critiques) dans la plateforme STOA :

TicketVulnérabilitéSévéritéCVSS
CAB-938Validation d'audience JWT désactivée🔴 Critique8,1
CAB-939Épuisement des connexions SSE (Slowloris)🔴 Critique7,5
CAB-945Mauvaises configurations de sécurité des conteneurs🔴 Critique7,2
CAB-950CORS wildcard permet des attaques cross-origin🔴 Critique6,5

Équipe d'audit

  • 🔪 Chucky — Lead pentest, Infrastructure
  • 🐟 N3m0 — Application web, Injection
  • 👻 Gh0st — Chaîne d'approvisionnement, CI/CD
  • 👸 Pr1nc3ss — Ingénierie sociale, OSINT

Équipe de revue

  • 💀 OSS Killer — Avocat du diable
  • 🏛️ Archi Vétéran — Revue d'architecture (23 ans d'expérience)

Décision

Nous implémenterons les quatre correctifs de sécurité avec les décisions de conception suivantes :

CAB-938 : Validation d'audience JWT

Avant (vulnérable) :

# mcp-gateway/src/middleware/auth.py:311
payload = jwt.decode(
token, rsa_key, algorithms=["RS256"],
options={"verify_aud": False} # ❌ Tout token de service accepté !
)

Après (sécurisé) :

payload = jwt.decode(
token, rsa_key, algorithms=["RS256"],
audience=self.settings.allowed_audiences_list,
options={"verify_aud": bool(self.settings.allowed_audiences)}
)

Décisions de conception :

  • Audience par défaut : stoa-mcp-gateway,account (compatibilité Keycloak)
  • La chaîne vide désactive la validation (compatibilité ascendante)
  • Rollback : ALLOWED_AUDIENCES=""

CAB-950 : Liste blanche CORS

Avant (vulnérable) :

# mcp-gateway/src/config/settings.py:74
cors_origins: str = "*" # ❌ N'importe quelle origine peut effectuer des requêtes !

Après (sécurisé) :

cors_origins: str = "https://console.stoa.dev,https://portal.stoa.dev,..."
cors_allow_methods: str = "GET,POST,PUT,DELETE,OPTIONS"
cors_allow_headers: str = "Authorization,Content-Type,X-Request-ID,X-Tenant-ID"
cors_expose_headers: str = "X-Request-ID,X-Trace-ID"
cors_max_age: int = 600

Décisions de conception :

  • Liste blanche de domaines explicites uniquement
  • Méthodes et headers restreints
  • Rollback : CORS_ORIGINS="*" (non recommandé)

CAB-939 : Limites de connexions SSE

Avant (vulnérable) :

# Aucune limite ! Un attaquant peut ouvrir 10 000+ connexions
async def sse_endpoint():
while True: # ❌ Infini, sans timeout
yield event

Après (sécurisé) :

# Nouveau module : mcp-gateway/src/middleware/sse_limiter.py
class SSEConnectionLimiter:
MAX_PER_IP: int = 10
MAX_PER_TENANT: int = 100
MAX_TOTAL: int = 5000
IDLE_TIMEOUT: int = 30 # secondes
MAX_DURATION: int = 3600 # 1 heure
RATE_LIMIT_PER_MIN: int = 5

Décisions de conception :

  • Paramètres passés explicitement pour éviter les imports circulaires (correctif OSS Killer)
  • Proxies de confiance vides par défaut (doivent être configurés explicitement)
  • Rollback : SSE_LIMITER_ENABLED=false

CAB-945 : Durcissement des conteneurs

Avant (vulnérable) :

# portal/k8s/deployment.yaml
securityContext:
runAsNonRoot: true
readOnlyRootFilesystem: false # ❌ Système de fichiers accessible en écriture !
# Manquant : capabilities, seccompProfile

Après (sécurisé) :

spec:
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
fsGroup: 101
containers:
- securityContext:
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
seccompProfile:
type: RuntimeDefault

Ressources supplémentaires créées :

  • k8s/namespace-pss.yaml — Pod Security Standards (restricted)
  • k8s/networkpolicy-control-plane.yaml — Isolation réseau

Architecture

Avant (vulnérable)

┌─────────────────────────────────────────────────────────────┐
│ FAILLES DE SÉCURITÉ │
├─────────────────────────────────────────────────────────────┤
│ │
│ 🔴 JWT : verify_aud=False │
│ └── Tout token Keycloak de tout service accepté │
│ │
│ 🔴 CORS : origins="*" │
│ └── Requêtes cross-origin depuis n'importe quel domaine │
│ │
│ 🔴 SSE : Aucune limite │
│ └── Attaque Slowloris peut épuiser les ressources serveur│
│ │
│ 🔴 Conteneurs : Système de fichiers accessible en écriture,│
│ aucune suppression de capabilities │
│ └── Évasion de conteneur → compromission nœud possible │
│ │
└─────────────────────────────────────────────────────────────┘

Après (durci)

┌─────────────────────────────────────────────────────────────┐
│ DÉFENSE EN PROFONDEUR │
├─────────────────────────────────────────────────────────────┤
│ │
│ ✅ JWT : verify_aud=True, audience="stoa-mcp-gateway" │
│ └── Seuls les tokens émis pour ce service acceptés │
│ │
│ ✅ CORS : Liste blanche explicite (*.stoa.dev, *.gostoa.dev)│
│ └── Seuls les frontends connus peuvent faire des requêtes│
│ │
│ ✅ SSE : 10/IP, 100/tenant, 30s inactif, 1h max │
│ └── Attaques par épuisement de connexions atténuées │
│ │
│ ✅ Conteneurs : Lecture seule, capabilities supprimées, │
│ seccomp │
│ └── Surface d'attaque minimisée, PSS appliqué │
│ │
│ ✅ NetworkPolicy : Control plane isolé │
│ └── Trafic est-ouest restreint aux services connus │
│ │
└─────────────────────────────────────────────────────────────┘

Conséquences

Positives

  • Surface d'attaque réduite — Les quatre vulnérabilités P0 sont corrigées
  • Défense en profondeur — Plusieurs couches de sécurité
  • Prêt pour la conformité — PSS restricted, journalisation d'audit, RBAC
  • Capacité de rollback — Chaque correctif dispose d'un interrupteur via variable d'environnement
  • Documenté — ADR, changelog, guide de configuration

Négatives

  • Risque de changement cassant — La validation d'audience JWT peut rejeter les tokens existants
  • Configuration requise — Les proxies de confiance doivent être configurés explicitement
  • Surveillance nécessaire — Le limiteur SSE nécessite des métriques Prometheus (P1)

Neutres

  • Impact sur les performances — Minimal (~1 ms pour la vérification de la limite SSE)
  • Chemin de migration — Compatibilité ascendante avec les interrupteurs de variables d'environnement

Conformité

StandardExigenceStatut
OWASP API SecurityAPI8:2023 Mauvaise configuration sécurité✅ Corrigé
CIS Kubernetes5.2.* Pod Security Standards✅ Implémenté
NIS2Segmentation réseau✅ NetworkPolicy
DORARéponse aux incidents✅ Plan de rollback

Références

Externes

Internes

Annexe : Historique des revues

Scores de revue

RelecteurRôlev1v2v3v4 (Final)
🔪 ChuckyInfra7/109/109/1010/10
🐟 N3m0Web8/109/109/1010/10
👻 Gh0stCI/CD6/108,5/109/1010/10
👸 Pr1nc3ssOSINT9/1010/1010/1010/10
💀 OSS KillerAdversarial--8,5/1010/10
🏛️ Archi VétéranArchitecture--9/1010/10

Corrections clés par relecteur

RelecteurProblème identifiéCorrectif appliqué
ChuckyautomountServiceAccountToken manquantAjouté aux deployments
ChuckyHealth checks NetworkPolicy trop ouvertsRestreints au CIDR cluster
N3m0Audience Keycloak par défautAjout de account aux valeurs par défaut
N3m0expose_headers manquantParamètre CORS ajouté
Pr1nc3ssUsurpation X-Forwarded-ForValidation des proxies de confiance
Gh0stAbsence de tests dans le planTests dans le même commit
Gh0stAbsence de plan de rollbackInterrupteurs via variables d'environnement
OSS KillerRisque d'import circulaireParamètres passés explicitement
OSS KillerProxies de confiance trop largesVide par défaut
Archi VétéranAbsence de documentationSECURITY-CHANGELOG ajouté

Pentest Team Coca — 2026-01-25 🍫