SEC-AUTHZ-006. delete_team etait la seule des dix operations d'equipe
gardee par la capacite globale can_manage_teams() ; les neuf autres passent
par can_manage_this_org_team(team). Le constat avait raison sur
l'incoherence et tort sur le correctif.
Appliquer sa recommandation telle quelle -- remplacer par la verification
par objet -- ELARGIT l'acces. Coach repond False a la capacite globale et
True pour ses propres equipes : la substitution donnait donc a chaque coach
le pouvoir de supprimer l'equipe qu'il entraine, avec ses notes d'equipe et
son historique de matchs. Le constat raisonnait sur Manager, ou les deux
repondent True, et a manque le role ou elles divergent.
Les deux sont donc exigees. Le comportement d'aujourd'hui est preserve a
l'identique (administrateurs et gerants oui, coachs non) et la dette que le
constat visait est bien fermee : le jour ou Manager.can_manage_this_org_team
sera resserre -- ce qui est souhaitable -- la suppression se resserrera avec
lui au lieu de rester la seule porte ouverte.
C'est la quatrieme recommandation d'audit qu'il faut corriger avant de
l'appliquer. Le test qui l'epingle echoue si quelqu'un refait la
simplification : verifie par mutation.
Co-Authored-By: Claude Opus 5 <[email protected]>
Active la regle isort (I) de ruff. 45 fichiers reordonnes, aucun
changement de comportement : la suite passe avant comme apres.
app/models/__init__.py en est exclu. Ses imports sont ranges en onze
couches commentees qui decrivent le graphe de dependances ; trier par
ordre alphabetique laisse chaque titre au-dessus d un import qu il ne
decrit pas, et ce fichier n a qu un role, etre lu.
Commit isole, comme le formatage : un diff de brassage ne doit pas
servir de couverture a un changement de comportement.
QUA-002, premiere moitie. **Ce commit ne fait que reformater** : aucun
changement de comportement, aucune ligne de logique touchee. 72 fichiers,
4 restaient deja conformes. Il est isole exprès, pour que `git log -p` sur
les commits voisins reste lisible.
`quote-style = "preserve"` etait deja pose dans pyproject.toml, ce qui
evite le brassage guillemets simples / doubles : le diff porte sur les
retours a la ligne, l indentation des appels longs et les virgules
finales, pas sur le style de chaine.
Verification : 263 tests passent avant et apres, ruff check propre.
L activation en CI arrive dans le commit suivant, separement, pour que ce
diff-ci ne contienne rien d autre.
Co-Authored-By: Claude Opus 5 <[email protected]>
SEC-AUTHZ-007 - auto-verrouillage de l'administration
Le changement de role n'excluait ni l'utilisateur courant, ni le dernier
compte admin actif. Une seule manipulation suffisait a transformer le seul
president en joueur, et plus aucune interface ne permettait de revenir en
arriere : il fallait une intervention directe en base.
Deux gardes distinctes, parce que ce sont deux erreurs differentes :
- changer son propre role est refuse, meme s'il reste d'autres admins.
Un president qui veut se retrograder doit le faire faire par un autre.
- retrograder le dernier admin actif est refuse.
Le decompte exclut les comptes desactives : trois admins dont deux
desactives, cela fait un seul administrateur reel.
SEC-WEB-003 - CORS ouvert par defaut
Sans CORS_ALLOWED_ORIGINS, la branche else appelait
CORS(app, supports_credentials=True) sans argument origins. flask-cors
retient alors '*' et, les identifiants etant autorises, renvoie en echo
l'Origin de l'appelant avec Access-Control-Allow-Credentials: true --
l'inverse exact de ce qu'annonçait le commentaire.
L'exploitation etait bloquee par SESSION_COOKIE_SAMESITE = 'Lax', qui
empeche le navigateur de joindre le cookie de session a une requete
fetch inter-site. Toute la protection tenait donc a ce seul reglage.
Cette application rend du HTML en meme origine : elle n'a besoin
d'aucune politique CORS. La branche par defaut est supprimee, la
configuration explicite reste possible.
5 tests ajoutes, dont deux verifient que les chemins legitimes continuent
de fonctionner : un autre administrateur reste retrogradable, et une
origine explicitement configuree est toujours honoree.
Co-Authored-By: Claude Opus 5 <[email protected]>
Il n'existait aucun test, et le code n'offrait aucune prise pour en ecrire :
create_app() exigeait SECRET_KEY et DATABASE_URL dans l'environnement,
creait les tables et demarrait un bot Discord. C'etait la cause, pas le
symptome.
create_app(config=None)
Les valeurs par defaut viennent toujours de l'environnement, les
surcharges de l'appelant sont appliquees ensuite, et la validation
vient en dernier pour qu'un test puisse fournir les siennes. Deux
effets de bord passent sous drapeau, actifs par defaut pour que la
production et le developpement se comportent a l'identique :
AUTO_CREATE_TABLES controle db.create_all()
ENABLE_DISCORD_BOT controle start_bot()
FORCE_HTTPS passe egalement en configuration : lu via os.getenv a
chaque requete, il renvoyait un 301 sur tout appel de test.
Suite de tests : 47 tests, 3 xfail, 32 % de couverture.
tests/conftest.py fabriques par role, connexion par le vrai
formulaire, base SQLite temporaire
test_auth_session.py expiration de session, desactivation de
compte, deconnexion
test_security_headers.py en-tetes, non-divulgation sur /health,
echappement de nl2br
test_authorization.py acces anonyme, vertical, horizontal,
validation des entrees, CSRF
Les tests marques xfail(strict=True) decrivent des constats non encore
corriges. Ils echouent par construction ; le mode strict transforme une
reussite inattendue en echec, ce qui signale qu'il faut retirer le
marqueur. Trois subsistent : enumeration de comptes (SEC-AUTH-006), CSP
unsafe-inline (SEC-WEB-001), auto-retrogradation du dernier administrateur
(SEC-AUTHZ-007).
pyproject.toml
Configuration pytest et ruff. Ruff n'avait aucune configuration : la CI
l'executait avec le jeu de regles par defaut. Les 33 F401 de
app/models/__init__.py sont ignores par fichier, c'est une facade de
re-export intentionnelle.
requirements-dev.txt separe l'outillage de test des dependances de
production.
Co-Authored-By: Claude Opus 5 <[email protected]>