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]>
150 lines
8.2 KiB
Markdown
150 lines
8.2 KiB
Markdown
# 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
|
|
<!-- register.html:61 -->
|
|
<input type="hidden" name="discord_user_id" value="{{ discord_data.id }}">
|
|
```
|
|
|
|
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é |
|