# 0 — Provenance et écart entre les dépôts ## Deux dépôts, un seul fait autorité | Dépôt | Rôle | HEAD `main` | |---|---|---| | `git.immortal.host/clubesportsudes/team-tryouts` | **Référence** — c'est celui-ci qui est audité | `bb0bc1c` | | `github.com/cedrick2711/team-tryouts` | Miroir, **en retard de 16 commits** | `08f02f7` | Les deux partagent un ancêtre commun (`d6fe505`). Le miroir GitHub porte en outre 2 commits qui ne sont pas sur le dépôt de référence (`0d5a408 fix registration` et son merge `08f02f7`) ; le correctif correspondant existe sur `immortal` sous la forme du commit `7d30aff Corriger erreur d'enregistrement`. **La première passe de cet audit a été menée sur le miroir GitHub**, avant que la bonne source ne soit connue. L'ensemble des constats a été revérifié contre `immortal/main`. Le présent document liste ce qui a changé. ### Les 16 commits d'écart ``` bb0bc1c ajout de plateforme de base pour les url TRN f89e4de régler problème avec mise a jour des status de message du discord bot fcf10bc bug fix: Manager ne pouvait pas voir les tryouts. probleme avec discord bot 68e3da6 fix probleme avec dispos d25b35c régler erreur 500 sur changement de role par admin aeba4d7 régler problème de changement de rôle 7bb8022 added a clear_db to start fresh with only an admin 0dd4ecd Erreur de frappe dans un des dossiers 0f7788e Changement du layout de la page d'enregistrement 47d5ec4 added discord oauth2 to get basic user info to complete profile when registering 10af8c0 small change for prod 7d30aff Corriger erreur d'enregistrement 4097416 Update wsgi.py 056cea0 Update .gitea/workflows/git-to-ptero.yaml d3b5700 Add .gitea/workflows/git-to-ptero.yaml fd258de Update app/.env.exemple ``` --- ## Constats de la première passe devenus caducs Quatre constats ont été résolus par l'équipe entre les deux points de l'historique. Ils sont conservés ici pour mémoire, et retirés du décompte des documents 01 à 03. ### ✅ Seed automatique en production — résolu Le bloc de `app/app.py` qui déclenchait le seed quand la table `users` était vide a été supprimé (commit `7bb8022`), et `app/supporting_scrits/seed.py` (460 lignes, comptes `manager1`, `coach1`, `scout1`… tous avec le mot de passe `password`) a été supprimé. ```diff with app.app_context(): import app.models as models - from app.models import User db.create_all() - - # Seed database if empty - if User.query.count() == 0: - from app.supporting_scrits.seed import seed_database - seed_database() ``` Un déploiement neuf ne crée donc plus de comptes par défaut. > **Reste à traiter.** Le script de remplacement `clear_db.py` code toujours en dur `admin` / `password`, et il est bien plus dangereux que l'ancien seed sur un autre plan. Voir [SEC-22](01-securite.md#sec-22--). > > **À vérifier en production**, indépendamment du code : les comptes créés par l'ancien seed (`admin`, `manager1`, `manager2`, `coach1`, `coach2`, `coach3`, `scout1`) peuvent toujours exister en base avec le mot de passe `password`. La suppression du script ne supprime pas les comptes qu'il a créés. ### ✅ Token Discord imprimé sur stdout — résolu La ligne `print(DISCORD_BOT_TOKEN or 'FAILED TO PRINT BOT TOKEN')` de `app/discord_bot.py:25` a été supprimée. Le module ne comporte plus aucun `print()`. ### ✅ `proxy_pass` vers `0.0.0.0` — résolu `app/nginx.conf:122` : `proxy_pass http://0.0.0.0:5000;` → `proxy_pass http://127.0.0.1:5000;` > **Reste à traiter.** L'incohérence de ports subsiste : `wsgi.py:24` utilise toujours `PORT` par défaut à **10000** alors que nginx envoie vers **5000** et que le docstring du même fichier annonce 5000. Voir [STD-06](03-standards-stack.md#std-06--). ### ✅ Faute de frappe `supporting_scrits` — résolu Le dossier a été renommé `app/supporting_scripts/` (commit `0dd4ecd`). > **Effet de bord non traité.** Le job CI « Security Scan » invoquait déjà un chemin erroné (`python security_scan.py` à la racine) ; le renommage l'éloigne encore. Voir [MNT-04](02-maintenabilite.md#mnt-04--). --- ## Constats aggravés ou requalifiés ### ⚠️ Secrets exposés — le nettoyage n'a pas révoqué `app/.env.exemple` a été nettoyé (commit `fd258de`) : les valeurs réelles ont été remplacées par des marqueurs (`flask_app_secret_key`, `my_discord_bot_token`, `URI_vers_db_posgres`). **Mais les secrets restent intégralement récupérables.** Vérification par recherche dans l'historique complet des deux dépôts : ``` $ git log --all --oneline -S "MTUyNzY3ODU3NjUyNTA1NDEyNQ" fd258de Update app/.env.exemple ← retrait 2d3721b Ajout d'un .env.exemple pour simplifier la collaboration ← introduction ``` Le blob contenant le token Discord, le mot de passe PostgreSQL et la `SECRET_KEY` est toujours atteignable par `git show 2d3721b:app/.env.exemple` sur **les deux dépôts**. Il est de surcroît **toujours dans le HEAD du miroir GitHub**, donc visible par simple navigation dans l'interface web. Le constat reste donc critique et sa correction inchangée : **révoquer, puis purger**. Voir [SEC-01](01-securite.md#sec-01--). ### ⚠️ En-têtes de proxy — le correctif a inversé le risque `wsgi.py` a reçu un bloc de configuration proxy (commit `4097416`) : ```python trusted_proxy='*', trusted_proxy_count=1, trusted_proxy_headers={'x-forwarded-for', 'x-forwarded-proto'}, clear_untrusted_proxy_headers=True ``` L'intention est bonne, et `clear_untrusted_proxy_headers=True` est le bon réflexe. Mais `trusted_proxy='*'` signifie « faire confiance aux en-têtes de proxy quelle qu'en soit la provenance », et `HOST` vaut toujours `0.0.0.0` par défaut (`wsgi.py:26`). Avant ce changement, `request.remote_addr` valait toujours l'IP de nginx : le rate limiting était appliqué à un seau global — gênant, mais fermé. Désormais, un attaquant qui atteint directement le port applicatif contrôle `X-Forwarded-For` et peut donc **présenter une IP différente à chaque requête**, ce qui neutralise le rate limiting et le suivi de tentatives par IP. Le constat change de nature et reste élevé. Voir [SEC-05](01-securite.md#sec-05--). ### ⚠️ Identifiant Discord — la vérification ajoutée n'en est pas une Un flux OAuth2 Discord complet a été ajouté à l'inscription (commit `47d5ec4`, `auth.py:326-456`). Il obtient de Discord un identifiant authentifié et le place en session. Mais cet identifiant est ensuite réinjecté dans le formulaire d'inscription **par un champ caché** : ```html ``` et c'est cette valeur — passée par le client — qui est enregistrée (`auth.py:253`, `:290`). Un utilisateur peut la modifier avant envoi, ou poster directement le formulaire sans jamais passer par Discord. `RegisterSchema` valide désormais le format de `discord_user_id` (17-20 chiffres, `validators.py:211-215`), ce qui est un progrès, mais ne prouve rien sur la propriété du compte. La conséquence pratique est inchangée par rapport à la première passe — et le risque de fausse assurance est nouveau, puisque le flux *paraît* vérifié. Voir [SEC-07](01-securite.md#sec-07--). ### ⚠️ Limite d'inscription desserrée `auth.py:190` : `@limiter.limit("3 per hour")` → `@limiter.limit("20 per hour")`. Le CAPTCHA arithmétique étant trivial ([SEC-13](01-securite.md#sec-13--)), le rate limiting était la protection réellement efficace contre la création automatisée de comptes. Passer à 20/heure la divise par un facteur ~7, et [SEC-05](01-securite.md#sec-05--) la rend contournable. --- ## Éléments nouveaux ajoutés à l'audit | Élément | Constat | |---|---| | `auth.py:326-456` — flux OAuth2 Discord | [SEC-20](01-securite.md#sec-20--) absence de paramètre `state` · [SEC-07](01-securite.md#sec-07--) identité non contraignante | | `.gitea/workflows/git-to-ptero.yaml` | [SEC-21](01-securite.md#sec-21--) déploiement de `.git/` par SFTP | | `clear_db.py` | [SEC-22](01-securite.md#sec-22--) script destructif sans garde-fou | | `migrations/add_tryout_coaches.py` | [STD-01](03-standards-stack.md#std-01--) migration manuelle hors outillage | | `discord_pending.json` | [MNT-16](02-maintenabilite.md#mnt-16--) état d'exécution versionné |