docs(audit): rebase sur le depot de reference et reverification complete
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]>
This commit is contained in:
+50
-37
@@ -2,21 +2,24 @@
|
||||
|
||||
Ordre proposé. Les lots sont séquentiels : chacun lève des blocages du suivant.
|
||||
|
||||
Établi sur `immortal/main` @ `bb0bc1c`. Les constats déjà résolus par l'équipe sont listés dans [`00-provenance.md`](00-provenance.md) et absents de ce plan.
|
||||
|
||||
---
|
||||
|
||||
## Lot 0 — Aujourd'hui, avant tout commit
|
||||
|
||||
Ces actions ne sont pas du code. Elles sont urgentes parce que les secrets sont exposés dès maintenant.
|
||||
Ces actions ne sont pas du code.
|
||||
|
||||
| # | Action | Réf. |
|
||||
|---|---|---|
|
||||
| 0.1 | **Révoquer le token du bot Discord** (Developer Portal → Bot → Reset Token) | [SEC-01](01-securite.md#sec-01--) |
|
||||
| 0.2 | **Faire tourner le mot de passe PostgreSQL** sur Render | SEC-01 |
|
||||
| 0.3 | **Générer une nouvelle `SECRET_KEY`** — invalide toutes les sessions, c'est voulu | SEC-01 |
|
||||
| 0.4 | **Vérifier en production** si les comptes `admin`, `manager1/2`, `coach1/2/3`, `scout1` existent avec le mot de passe `password` ; les désactiver ou changer leur mot de passe | [SEC-02](01-securite.md#sec-02--) |
|
||||
| 0.5 | Consulter les logs Discord et PostgreSQL pour détecter un éventuel accès non autorisé | SEC-01 |
|
||||
| 0.4 | **Vérifier en production** si les comptes de l'ancien seed (`admin`, `manager1`, `manager2`, `coach1`, `coach2`, `coach3`, `scout1`) existent avec le mot de passe `password` ; les désactiver ou changer leur mot de passe | [SEC-22](01-securite.md#sec-22--) |
|
||||
| 0.5 | **Vérifier si `.git/` est présent et téléchargeable** sur le serveur de production (`curl https://<app>/.git/HEAD`) ; le supprimer le cas échéant | [SEC-21](01-securite.md#sec-21--) |
|
||||
| 0.6 | Consulter les logs Discord et PostgreSQL pour détecter un éventuel accès non autorisé | SEC-01 |
|
||||
|
||||
> 0.1 à 0.3 sont indépendantes du code et peuvent être faites immédiatement. La purge de l'historique git (0.6, ci-dessous) vient après, et ne remplace pas la révocation : les secrets ont pu être clonés.
|
||||
> **Le nettoyage de `app/.env.exemple` (commit `fd258de`) n'a rien révoqué.** Les valeurs restent lisibles dans l'historique des deux dépôts, et dans le HEAD du miroir GitHub. 0.1 à 0.3 restent nécessaires et sont indépendantes de toute manipulation de git.
|
||||
|
||||
---
|
||||
|
||||
@@ -24,12 +27,13 @@ Ces actions ne sont pas du code. Elles sont urgentes parce que les secrets sont
|
||||
|
||||
| # | Action | Réf. | Effort |
|
||||
|---|---|---|---|
|
||||
| 1.1 | Vider `app/.env.exemple` de ses valeurs réelles, le renommer `.env.example`, ajouter `.env*` au `.gitignore` | SEC-01 | 15 min |
|
||||
| 1.2 | Purger l'historique git (`git filter-repo`), prévenir l'équipe de recloner | SEC-01 | 1 h |
|
||||
| 1.3 | Conditionner le seed à `SEED_DEMO_DATA` ; générer les mots de passe de démo aléatoirement | SEC-02 | 30 min |
|
||||
| 1.1 | Purger l'historique des **deux** dépôts (`git filter-repo --path app/.env.exemple --invert-paths`), prévenir l'équipe de recloner | [SEC-01](01-securite.md#sec-01--) | 2 h |
|
||||
| 1.2 | Synchroniser ou supprimer le miroir GitHub — il expose encore les secrets dans son HEAD | SEC-01 | 30 min |
|
||||
| 1.3 | Ajouter `.env*` au `.gitignore` avec `!.env.example` ; renommer `.env.exemple` → `.env.example` | SEC-01 | 15 min |
|
||||
| 1.4 | Refuser le démarrage si `CORS_ALLOWED_ORIGINS` est vide hors développement | [SEC-03](01-securite.md#sec-03--) | 20 min |
|
||||
| 1.5 | Supprimer `print(DISCORD_BOT_TOKEN)` — `discord_bot.py:25` | [SEC-04](01-securite.md#sec-04--) | 2 min |
|
||||
| 1.6 | Ajouter un scan de secrets (`gitleaks`) à la CI | SEC-01 | 30 min |
|
||||
| 1.5 | `trusted_proxy='127.0.0.1'` + `HOST` par défaut à `127.0.0.1` dans `wsgi.py` | [SEC-05](01-securite.md#sec-05--) | 20 min |
|
||||
| 1.6 | Exclure `.git/` (et `.github/`, `__pycache__/`, `logs/`) du miroir SFTP ; épingler `known_hosts` | [SEC-21](01-securite.md#sec-21--) | 1 h |
|
||||
| 1.7 | Ajouter un scan de secrets (`gitleaks`) à la CI | SEC-01 | 30 min |
|
||||
|
||||
---
|
||||
|
||||
@@ -39,12 +43,14 @@ Ces actions ne sont pas du code. Elles sont urgentes parce que les secrets sont
|
||||
|
||||
| # | Action | Réf. | Effort |
|
||||
|---|---|---|---|
|
||||
| 2.1 | Réencoder `requirements.txt` en UTF-8, ajouter `.gitattributes` | [MNT-02](02-maintenabilite.md#mnt-02--) | 15 min |
|
||||
| 2.2 | Corriger `.gitignore` (`*.html`, `docs/`), vérifier les fichiers déjà perdus | [MNT-01](02-maintenabilite.md#mnt-01--) | 30 min |
|
||||
| 2.3 | Corriger le chemin de `security_scan.py` dans la CI et lui fournir `DATABASE_URL` | [MNT-04](02-maintenabilite.md#mnt-04--) | 20 min |
|
||||
| 2.4 | Ajouter `pyproject.toml` avec la configuration Ruff, formater en un commit isolé | [MNT-06](02-maintenabilite.md#mnt-06--) | 2 h |
|
||||
| 2.5 | Retirer `dotenv`, `login`, `discord` ; `psycopg2` → `psycopg2-binary` | [MNT-05](02-maintenabilite.md#mnt-05--) | 30 min |
|
||||
| 2.6 | Créer `tests/`, avec les tests de la matrice de permissions ; activer réellement le job CI | [MNT-03](02-maintenabilite.md#mnt-03--) | 1 j |
|
||||
| 2.1 | **Vérifier le pilote PostgreSQL** — `psycopg[binary]` est psycopg 3 ; confirmer que `DATABASE_URL` utilise `postgresql+psycopg://`, ou normaliser l'URI au démarrage. Épingler la version. | [MNT-05](02-maintenabilite.md#mnt-05--) | 1 h |
|
||||
| 2.2 | Réencoder `requirements.txt` en UTF-8, ajouter `.gitattributes` | [MNT-02](02-maintenabilite.md#mnt-02--) | 15 min |
|
||||
| 2.3 | Corriger `.gitignore` (`*.html`, `docs/`), vérifier les fichiers déjà perdus | [MNT-01](02-maintenabilite.md#mnt-01--) | 30 min |
|
||||
| 2.4 | Corriger le chemin de `security_scan.py` dans la CI et lui fournir `DATABASE_URL` | [MNT-04](02-maintenabilite.md#mnt-04--) | 20 min |
|
||||
| 2.5 | Ajouter `pyproject.toml` avec la configuration Ruff, formater en un commit isolé | [MNT-06](02-maintenabilite.md#mnt-06--) | 2 h |
|
||||
| 2.6 | Retirer `dotenv`, `login`, `discord` des dépendances | MNT-05 | 30 min |
|
||||
| 2.7 | Retirer `discord_pending.json` du suivi git | [MNT-16](02-maintenabilite.md#mnt-16--) | 10 min |
|
||||
| 2.8 | Créer `tests/`, avec les tests de la matrice de permissions ; activer réellement le job CI | [MNT-03](02-maintenabilite.md#mnt-03--) | 1 j |
|
||||
|
||||
---
|
||||
|
||||
@@ -52,17 +58,19 @@ Ces actions ne sont pas du code. Elles sont urgentes parce que les secrets sont
|
||||
|
||||
| # | Action | Réf. | Effort |
|
||||
|---|---|---|---|
|
||||
| 3.1 | Appliquer `ProxyFix`, corriger `HOST` (`wsgi.py`) et `proxy_pass` (`nginx.conf`) | [SEC-05](01-securite.md#sec-05--) · [STD-06](03-standards-stack.md#std-06--) | 1 h |
|
||||
| 3.2 | Brancher `CreateUserSchema`, `EditUserSchema`, `EditProfileSchema` sur leurs routes | [SEC-06](01-securite.md#sec-06--) | 3 h |
|
||||
| 3.3 | Adosser Flask-Limiter à Redis ou PostgreSQL ; réévaluer les seuils | [SEC-08](01-securite.md#sec-08--) | 2 h |
|
||||
| 3.4 | Restreindre `view_user` et réduire les données transmises au template | [SEC-15](01-securite.md#sec-15--) | 2 h |
|
||||
| 3.5 | Valider les téléversements de contrats signés | [SEC-11](01-securite.md#sec-11--) | 1 h |
|
||||
| 3.6 | Uniformiser les messages de login, égaliser les temps de réponse | [SEC-12](01-securite.md#sec-12--) | 1 h |
|
||||
| 3.7 | Corriger `nl2br` (échapper avant `Markup`) ou le supprimer | [SEC-09](01-securite.md#sec-09--) | 15 min |
|
||||
| 3.8 | Masquer l'erreur brute de `/health` | [SEC-14](01-securite.md#sec-14--) | 10 min |
|
||||
| 3.9 | Alimenter le logger `team_tryouts.auth` | [SEC-19](01-securite.md#sec-19--) | 2 h |
|
||||
| 3.10 | Contrôler la cohérence tryout/équipe dans `add_to_team` | [SEC-17](01-securite.md#sec-17--) | 30 min |
|
||||
| 3.11 | Remplacer les `int()` nus par `request.form.get(..., type=int)` | [SEC-16](01-securite.md#sec-16--) | 2 h |
|
||||
| 3.1 | Ajouter le paramètre `state` au flux OAuth2 Discord | [SEC-20](01-securite.md#sec-20--) | 1 h |
|
||||
| 3.2 | Prendre `discord_user_id` depuis la session OAuth2, pas depuis le champ caché du formulaire | [SEC-07](01-securite.md#sec-07--) | 2 h |
|
||||
| 3.3 | Brancher `CreateUserSchema`, `EditUserSchema`, `EditProfileSchema` sur leurs routes | [SEC-06](01-securite.md#sec-06--) | 3 h |
|
||||
| 3.4 | Garde-fous sur `clear_db.py` : confirmation, variable d'environnement, mot de passe aléatoire | [SEC-22](01-securite.md#sec-22--) | 1 h |
|
||||
| 3.5 | Adosser Flask-Limiter à Redis ou PostgreSQL ; réévaluer les seuils (dont `register`, passé à 20/h) | [SEC-08](01-securite.md#sec-08--) · [SEC-13](01-securite.md#sec-13--) | 2 h |
|
||||
| 3.6 | Restreindre `view_user` et réduire les données transmises au template | [SEC-15](01-securite.md#sec-15--) | 2 h |
|
||||
| 3.7 | Valider les téléversements de contrats signés | [SEC-11](01-securite.md#sec-11--) | 1 h |
|
||||
| 3.8 | Uniformiser les messages de login, égaliser les temps de réponse | [SEC-12](01-securite.md#sec-12--) | 1 h |
|
||||
| 3.9 | Corriger `nl2br` (échapper avant `Markup`) ou le supprimer | [SEC-09](01-securite.md#sec-09--) | 15 min |
|
||||
| 3.10 | Masquer l'erreur brute de `/health` | [SEC-14](01-securite.md#sec-14--) | 10 min |
|
||||
| 3.11 | Alimenter le logger `team_tryouts.auth` — **après** 1.5 | [SEC-19](01-securite.md#sec-19--) | 2 h |
|
||||
| 3.12 | Contrôler la cohérence tryout/équipe dans `add_to_team` | [SEC-17](01-securite.md#sec-17--) | 30 min |
|
||||
| 3.13 | Remplacer les `int()` nus par `request.form.get(..., type=int)` | [SEC-16](01-securite.md#sec-16--) | 2 h |
|
||||
|
||||
---
|
||||
|
||||
@@ -72,14 +80,15 @@ Ce lot débloque des correctifs de sécurité qui exigent des changements de sch
|
||||
|
||||
| # | Action | Réf. | Effort |
|
||||
|---|---|---|---|
|
||||
| 4.1 | Introduire Flask-Migrate, `flask db stamp head` sur la base existante | [STD-01](03-standards-stack.md#std-01--) | 3 h |
|
||||
| 4.2 | Objets de configuration par environnement ; un seul `load_dotenv()` | [STD-05](03-standards-stack.md#std-05--) | 4 h |
|
||||
| 4.3 | Sortir les effets de bord de `create_app()` (create_all, seed, bot) | [STD-02](03-standards-stack.md#std-02--) | 3 h |
|
||||
| 4.4 | Séparer le bot Discord en processus distinct | [STD-03](03-standards-stack.md#std-03--) | 1 j |
|
||||
| 4.5 | *(dépend de 4.1)* Unicité sur `discord_user_id` + vérification de possession | [SEC-07](01-securite.md#sec-07--) | 1 j |
|
||||
| 4.1 | Introduire Flask-Migrate, `flask db stamp head` ; reprendre `migrations/add_tryout_coaches.py` comme première révision Alembic | [STD-01](03-standards-stack.md#std-01--) | 3 h |
|
||||
| 4.2 | Objets de configuration par environnement ; un seul `load_dotenv()` ; vérifier les 3 variables Discord OAuth2 au démarrage | [STD-05](03-standards-stack.md#std-05--) | 4 h |
|
||||
| 4.3 | Sortir les effets de bord de `create_app()` — bénéficie aussi à `clear_db.py` et aux migrations | [STD-02](03-standards-stack.md#std-02--) | 3 h |
|
||||
| 4.4 | Séparer le bot Discord en processus distinct ; persister son état en base | [STD-03](03-standards-stack.md#std-03--) · [MNT-16](02-maintenabilite.md#mnt-16--) | 1 j |
|
||||
| 4.5 | *(dépend de 4.1)* Unicité sur `discord_user_id` | [SEC-07](01-securite.md#sec-07--) | 2 h |
|
||||
| 4.6 | *(dépend de 4.1)* Cascades FK sur `OrgTeam` ; corriger `delete_team` | [MNT-11](02-maintenabilite.md#mnt-11--) | 3 h |
|
||||
| 4.7 | *(dépend de 4.1)* Migrer vers `datetime.now(timezone.utc)` et `DateTime(timezone=True)` | [MNT-13](02-maintenabilite.md#mnt-13--) | 4 h |
|
||||
| 4.8 | Journaliser sur stdout ; handlers fichier optionnels | [STD-07](03-standards-stack.md#std-07--) | 1 h |
|
||||
| 4.9 | Aligner les ports (`wsgi.py` défaut 5000, `run.py` en `127.0.0.1`) | [STD-06](03-standards-stack.md#std-06--) | 30 min |
|
||||
|
||||
---
|
||||
|
||||
@@ -89,7 +98,7 @@ Sans urgence. À traiter au fil des évolutions plutôt qu'en chantier dédié.
|
||||
|
||||
| # | Action | Réf. |
|
||||
|---|---|---|
|
||||
| 5.1 | Découper `users.py` en cinq blueprints | [MNT-08](02-maintenabilite.md#mnt-08--) |
|
||||
| 5.1 | Découper `users.py` (1 265 lignes) en cinq blueprints ; sortir l'OAuth2 de `auth.py` | [MNT-08](02-maintenabilite.md#mnt-08--) |
|
||||
| 5.2 | Choisir une approche de validation unique ; retirer Flask-WTF ou WTForms | [STD-04](03-standards-stack.md#std-04--) |
|
||||
| 5.3 | Extraire les utilitaires date/heure | [MNT-12](02-maintenabilite.md#mnt-12--) |
|
||||
| 5.4 | Résorber les N+1 sur `view_tryout` et `api_events` | [MNT-10](02-maintenabilite.md#mnt-10--) |
|
||||
@@ -100,18 +109,19 @@ Sans urgence. À traiter au fil des évolutions plutôt qu'en chantier dédié.
|
||||
| 5.9 | Remplacer le CAPTCHA par une validation du courriel institutionnel | [SEC-13](01-securite.md#sec-13--) |
|
||||
| 5.10 | Réparer `backup.py` (`pg_dump`) ou le restreindre aux documents | [MNT-07](02-maintenabilite.md#mnt-07--) |
|
||||
| 5.11 | Pagination des listes | [MNT-14](02-maintenabilite.md#mnt-14--) |
|
||||
| 5.12 | `Dockerfile` + `docker-compose.yml` | [STD-10](03-standards-stack.md#std-10--) |
|
||||
| 5.13 | Réécrire le README ; renommer `supporting_scrits` → `supporting_scripts` | [MNT-15](02-maintenabilite.md#mnt-15--) |
|
||||
| 5.12 | `Dockerfile` + `docker-compose.yml` ; remplacer le miroir SFTP par un artefact | [STD-10](03-standards-stack.md#std-10--) |
|
||||
| 5.13 | Réécrire le README, en indiquant quel dépôt fait autorité | [MNT-15](02-maintenabilite.md#mnt-15--) |
|
||||
|
||||
---
|
||||
|
||||
## Dépendances entre lots
|
||||
|
||||
```
|
||||
Lot 0 (révocation)
|
||||
Lot 0 (révocation, vérifications en prod)
|
||||
└─→ Lot 1 (correctifs critiques)
|
||||
└─→ Lot 2 (outillage : CI verte, premiers tests)
|
||||
└─→ Lot 2 (outillage : pilote DB, CI verte, premiers tests)
|
||||
├─→ Lot 3 (sécurité applicative)
|
||||
│ └─ 3.11 journal d'audit (bloqué par 1.5 — sinon on journalise des IP usurpables)
|
||||
└─→ Lot 4 (migrations, configuration)
|
||||
├─→ 4.5 unicité discord_user_id (bloqué par 4.1)
|
||||
├─→ 4.6 cascades FK (bloqué par 4.1)
|
||||
@@ -119,4 +129,7 @@ Lot 0 (révocation)
|
||||
└─→ Lot 5 (dette de conception)
|
||||
```
|
||||
|
||||
Le Lot 2 est délibérément placé avant les lots 3 et 4 : sans tests sur la matrice de permissions, les modifications d'autorisation du Lot 3 ne peuvent pas être validées autrement qu'à la main.
|
||||
Deux dépendances méritent attention :
|
||||
|
||||
- **2.1 avant tout le reste du Lot 2** : si le pilote PostgreSQL est mal apparié à l'URI, l'application ne démarre pas, et aucun test d'intégration ne peut être écrit.
|
||||
- **1.5 avant 3.11** : journaliser `request.remote_addr` tant que `trusted_proxy='*'` produit une traçabilité illusoire — pire que pas de traçabilité, parce qu'on lui fera confiance.
|
||||
|
||||
Reference in New Issue
Block a user