# Note de sécurité — 3 correctifs prioritaires

**Date** : 05/08/2026 · **Périmètre** : EFEKTIVACADEMIE-BACK, branche `development` (= production)
**Branche de correctif** : `hotfix/securite-p0`
**Statut** : correctifs écrits, testés, en attente de relecture et de mise en production

---

## Résumé

Un audit de code mené sur la branche `development` a mis en évidence trois défauts actifs en
production. Deux sont des failles de sécurité exploitables ; le troisième est un défaut de
conformité pédagogique. Ils sont corrigés sur la branche `hotfix/securite-p0`, avec des tests
de non-régression.

| # | Défaut | Gravité | Exploitable sans outil ? |
|---|---|---|---|
| 1 | Escalade de privilèges : tout rôle managérial peut créer un compte administrateur | **Critique** | Oui — une seule requête HTTP |
| 2 | Accès aux certificats d'autrui par énumération d'identifiants (IDOR) | **Élevée** | Oui — incrémenter un identifiant dans l'URL |
| 3 | Seuil de réussite des quiz jamais appliqué : certificat obtenu même à 0 % | **Élevée (conformité)** | Sans objet — comportement par défaut |

Les défauts 1 et 2 ont été **démontrés par test automatisé** : les tests écrits pour ce correctif
échouent sur le code actuel de `development` et passent après correction.

---

## 1. Escalade de privilèges verticale (critique)

**Ce qui se passe.** À la création d'un utilisateur, le rôle demandé est appliqué (`assignRole()`)
**avant** le contrôle qui aurait dû le valider. Par ailleurs, le champ `role` n'était soumis à
aucune liste blanche : la règle de validation `in:` avait disparu de `CreateUserRequest`, alors
que le message d'erreur correspondant (`role.in`) est resté dans les fichiers de langue — signe
d'une régression. Enfin, cinq groupes de routes exposent la même méthode de création.

**Conséquence.** Un compte `sub_manager`, `director`, `manager_general` ou `manager` peut créer un
compte **administrateur** en une requête, puis s'y connecter (les identifiants sont envoyés par
email). Autrement dit, tout encadrant disposant d'un accès à la plateforme peut obtenir les droits
complets, y compris sur les données de l'ensemble des apprenants. La même faiblesse existait sur la
**modification** de rôle (promotion d'un compte existant).

**Correctif.** Introduction d'un référentiel de rôles avec niveaux hiérarchiques
(`app/Support/RoleHierarchy.php`). Invariant appliqué : *on ne peut attribuer qu'un rôle
strictement inférieur au sien*. Le contrôle est exécuté **avant toute écriture en base**, à la
création comme à la modification, et la liste blanche est rétablie côté validation.

**Limite volontaire.** La règle produit « seul l'administrateur crée des utilisateurs » n'est pas
appliquée ici : c'est une décision fonctionnelle non encore arbitrée (elle retirerait des routes
utilisées aujourd'hui). Le correctif ferme la faille sans modifier les usages légitimes.

## 2. Accès aux certificats d'autrui (élevée)

**Ce qui se passe.** Le téléchargement d'un certificat récupérait l'enregistrement par son seul
identifiant, sans vérifier qu'il appartient à l'appelant.

**Conséquence.** Tout apprenant authentifié pouvait parcourir les identifiants (`/1`, `/2`, `/3`…)
et récupérer les certificats nominatifs de tous les autres — donc nom, prénom et parcours de
formation de l'ensemble des collaborateurs inscrits. Accessoirement, un identifiant inexistant
provoquait une erreur serveur (500) au lieu d'une réponse « introuvable ».

**Correctif.** Le certificat est désormais recherché dans le périmètre de l'apprenant authentifié ;
tout autre cas renvoie 404. Le téléchargement groupé côté encadrant s'appuie désormais sur les
enregistrements en base plutôt que sur un balayage du dossier de stockage, qui embarquait aussi
des fichiers orphelins.

**Reste à traiter (hors de ce correctif).** Les PDF sont stockés sur le disque public : ils
restent atteignables par quiconque connaît l'URL exacte du fichier. Les déplacer hors du disque
public impose d'adapter le front (qui construit aujourd'hui l'URL directement) — à traiter avec le
chantier « téléchargements authentifiés et journalisés ».

## 3. Seuil de réussite des quiz jamais appliqué (conformité)

**Ce qui se passe.** Chaque quiz possède un seuil de réussite (`passing_percentage`, 80 % par
défaut), paramétrable dans l'interface. Ce seuil était enregistré et affiché, mais **jamais
comparé au score** : la validation d'un quiz ne lisait ni le score ni le seuil.

**Conséquence.** Un apprenant validait un quiz quel que soit son résultat, y compris 0 %. Comme la
génération du certificat se déclenche lorsque toutes les leçons et tous les quiz sont validés,
**les certificats délivrés n'attestent d'aucun acquis**. C'est un point sensible si une formation
est présentée comme certifiante ou opposable.

**Correctif.** Le seuil est désormais évalué à la validation d'un quiz : en dessous, la validation
est refusée avec un message indiquant le score obtenu, le seuil à atteindre et la possibilité de
refaire le quiz.

**Effet sur l'existant : aucun.** Le contrôle ne s'applique qu'aux validations **nouvelles** : un
quiz déjà marqué comme terminé le reste, et aucun certificat déjà délivré n'est retiré. Le sort des
acquis passés (faut-il inviter certains apprenants à repasser un quiz ?) est une décision de
gouvernance à prendre séparément — elle n'est **pas** engagée par ce correctif.

---

## Vérification

- 11 tests ajoutés (`tests/Feature/SecurityHardeningTest.php`) couvrant les trois correctifs et
  leurs cas nominaux.
- Preuve de la réalité des failles : sur le code de `development` **sans** les correctifs, 6 de ces
  tests échouent — dont la création effective d'un compte administrateur par un `sub_manager`.
- Suite complète : 457 tests, **aucune régression** (les 5 échecs restants sont antérieurs et sans
  rapport : ils portent sur le champ `cta_url` devenu obligatoire sans mise à jour des tests).
- Style de code : Laravel Pint appliqué.

## Changements de comportement à connaître avant mise en production

1. Un quiz en dessous du seuil renvoie désormais **422** au lieu d'accepter la validation. Le front
   affiche le message renvoyé par l'API ; il peut être utile de soigner cet écran côté interface.
   **Vérifier les seuils configurés** sur les quiz existants avant mise en production : c'est la
   valeur `passing_percentage` de chaque quiz qui s'appliquera (80 % par défaut).
2. Le téléchargement d'un certificat par un non-propriétaire renvoie **404** (auparavant toléré).
   Les encadrants conservent leur téléchargement groupé dédié.
3. Un rôle non autorisé à la création/modification renvoie **403** avec un message explicite.
4. Trois tests existants qui figeaient l'ancien comportement ont été mis à jour en conséquence.

## Suites recommandées

- Sortir les certificats et les ressources de cours du disque public (téléchargement authentifié
  et journalisé).
- Revue des comptes administrateurs existants : vérifier qu'aucun compte `admin` n'a été créé par
  un rôle qui n'aurait pas dû le pouvoir.
- Arbitrage sur les acquis antérieurs au seuil de réussite (point 3).
