Le miroir GitHub audite en premiere passe etait en retard de 16 commits
sur git.immortal.host/clubesportsudes/team-tryouts. L'audit est rebase
sur immortal/main @ bb0bc1c et l'ensemble des constats reverifie.
Resolus par l'equipe (archives dans 00-provenance.md) :
- seed automatique en production supprime
- print du token Discord supprime
- proxy_pass nginx corrige vers 127.0.0.1
- dossier supporting_scrits renomme
Nouveaux constats :
- SEC-20 flux OAuth2 Discord sans parametre state (CSRF de liaison)
- SEC-21 le deploiement SFTP pousse .git/ sur le serveur
- SEC-22 clear_db.py destructif sans garde-fou, admin/password en dur
- MNT-16 discord_pending.json versionne
Requalifies :
- SEC-01 secrets retires du fichier mais toujours dans l'historique des
deux depots, et dans le HEAD du miroir GitHub -> revocation requise
- SEC-05 trusted_proxy='*' + bind 0.0.0.0 rend X-Forwarded-For usurpable,
ce qui ouvre le rate limiting au lieu de le corriger
- SEC-07 l'OAuth2 ajoute ne contraint pas l'identite : le discord_user_id
transite par un champ cache du formulaire
- MNT-05 psycopg[binary] non epingle, incompatible avec les URI
postgresql:// que SQLAlchemy resout vers psycopg2
48 constats. Aucune modification du code applicatif.
Co-Authored-By: Claude Opus 5 <[email protected]>
6.9 KiB
Audit — Team Tryouts
Audit statique de l'application team-tryouts (Flask 3.1 / SQLAlchemy 2.0 / Jinja2 / PostgreSQL).
| Branche | audit/securite-maintenabilite-standards |
| Base | immortal/main @ bb0bc1c — dépôt de référence git.immortal.host/clubesportsudes/team-tryouts |
| Date | 2026-08-07 |
| Périmètre | Sécurité · Maintenabilité · Respect des standards de la stack |
| Méthode | Revue de code statique (100 % du code Python, configuration CI/nginx/déploiement, .gitignore, dépendances). Aucun test dynamique, aucune exécution de l'application. |
Note de provenance. Une première passe a été menée sur le miroir GitHub
cedrick2711/team-tryouts, qui s'est révélé être en retard de 16 commits. L'audit a été rebasé sur le dépôt de référence et intégralement revérifié. Voir00-provenance.mdpour le détail de l'écart et la liste des constats devenus caducs.
Documents
| Fichier | Contenu |
|---|---|
00-provenance.md |
Écart entre les deux dépôts, constats résolus |
01-securite.md |
22 constats de sécurité |
02-maintenabilite.md |
16 constats de maintenabilité et d'outillage |
03-standards-stack.md |
10 écarts aux conventions Flask / SQLAlchemy / 12-factor |
plan-remediation.md |
Ordre de traitement proposé |
Synthèse exécutive
Le socle est sain sur plusieurs points : ORM utilisé partout (aucune injection SQL, y compris dans le SQL brut ajouté récemment, qui est correctement paramétré), autoescape Jinja2 actif et aucun |safe dans les templates, CSRF activé globalement, hachage Werkzeug, modèle d'autorisation polymorphe cohérent, en-têtes de sécurité complets, verrouillage de compte après 5 échecs.
L'équipe a par ailleurs corrigé de sa propre initiative plusieurs points relevés lors de la première passe : le seed automatique en production a été supprimé, le token Discord n'est plus imprimé sur stdout, proxy_pass pointe désormais vers 127.0.0.1, et la faute de frappe supporting_scrits a été corrigée. Ces constats sont archivés dans 00-provenance.md.
Il reste deux constats critiques, tous deux de configuration :
- Les secrets de production sont toujours récupérables. Le fichier
app/.env.exemplea bien été nettoyé (commitfd258de), mais les valeurs réelles — token du bot Discord, mot de passe PostgreSQL,SECRET_KEY— restent dans l'historique git des deux dépôts (commit2d3721b), et sont encore présentes dans le HEAD du miroir GitHub. Nettoyer un fichier ne révoque pas un secret. - Le CORS accepte toutes les origines avec
supports_credentials=Truedès queCORS_ALLOWED_ORIGINSn'est pas défini — et le.env.exemplefourni ne le définit pas.
Deux points méritent une attention particulière parce qu'ils ressemblent à des correctifs mais n'en sont pas :
- Le paramétrage
trusted_proxyde Waitress (wsgi.py:38-41) a été ajouté pour traiter les en-têtes de proxy. Maistrusted_proxy='*'accepte ces en-têtes de n'importe quelle source, et le serveur écoute toujours sur0.0.0.0. Le résultat est l'inverse de l'intention : un attaquant joignant directement le port applicatif peut désormais usurperX-Forwarded-Forà chaque requête et contourner intégralement le rate limiting et le suivi des tentatives de connexion. Voir SEC-05. - L'OAuth2 Discord ajouté à l'inscription aurait pu résoudre le problème d'usurpation d'identifiant Discord. Mais l'identifiant vérifié par Discord est réinjecté dans le formulaire via un champ caché (
register.html:61) avant d'être enregistré : il est donc modifiable par l'utilisateur. La vérification est cosmétique. Voir SEC-07.
Le flux OAuth2 est en outre dépourvu de paramètre state, ce qui l'expose à la CSRF classique de liaison de compte (SEC-20).
Enfin, la chaîne de déploiement ajoutée (.gitea/workflows/git-to-ptero.yaml) pousse le répertoire .git/ complet sur le serveur SFTP — donc l'historique contenant les secrets du point 1 (SEC-21).
Côté outillage, rien n'a bougé : requirements.txt est toujours en UTF-16, le job CI « Security Scan » pointe toujours vers un chemin inexistant (et le renommage du dossier l'a même éloigné davantage), le job « Tests » reste un echo, et .gitignore contient toujours *.html — tout nouveau template est silencieusement ignoré par git.
Répartition des constats
| Sévérité | Sécurité | Maintenabilité | Standards | Total |
|---|---|---|---|---|
| 🔴 Critique | 2 | — | — | 2 |
| 🟠 Élevé | 6 | 4 | 1 | 11 |
| 🟡 Moyen | 8 | 8 | 6 | 22 |
| 🔵 Faible | 6 | 4 | 3 | 13 |
| Total | 22 | 16 | 10 | 48 |
(4 constats de la première passe ont été résolus par l'équipe — voir 00-provenance.md.)
Les 6 actions à mener en premier
| # | Action | Détail |
|---|---|---|
| 1 | Révoquer les 3 secrets — ils sont dans l'historique, le nettoyage du fichier n'a rien révoqué | SEC-01 |
| 2 | Purger l'historique des deux dépôts, et mettre le miroir GitHub à jour ou le supprimer | SEC-01 |
| 3 | Refuser le démarrage si CORS_ALLOWED_ORIGINS est vide |
SEC-03 |
| 4 | trusted_proxy='127.0.0.1' + HOST=127.0.0.1 |
SEC-05 |
| 5 | Ajouter le paramètre state au flux OAuth2 Discord |
SEC-20 |
| 6 | Exclure .git/ du déploiement SFTP |
SEC-21 |
Ce qui est déjà bien fait
Pour éviter de le casser lors des corrections :
- Aucune injection SQL. L'ORM est utilisé sans exception. Les deux ajouts de SQL brut sont corrects :
users.py:120-123utilise des paramètres liés, etmigrations/add_tryout_coaches.pyn'interpole aucune entrée utilisateur. - Aucun
|safe, aucun{% autoescape false %}dans les templates ; lesinnerHTMLdemain.jsne manipulent que des chaînes littérales. - Autorisation par dispatch polymorphe (
Admin/Manager/Coach/Player/Scout) plutôt que par comparaison de chaînes de rôles — approche propre et testable. - Protection contre la fixation de session au login (
auth.py:150-155). - Validation anti-open-redirect sur le paramètre
next(auth.py:40-53). - Filtre de redaction des secrets dans les logs (
logging_config.py:18-50). - Le changement de rôle par SQL brut (
users.py:114-126) est une solution correcte à un vrai problème SQLAlchemy : modifier un discriminateur polymorphe sur une instance chargée corrompt l'identity map. Le commentaire explique le raisonnement. À conserver.