/health divulguait le message brut du pilote
L'endpoint n'est pas authentifie et renvoyait f'error: {str(e)}'. Les
exceptions psycopg contiennent regulierement l'hote, le port, le nom de
la base et l'utilisateur. Le detail part desormais dans les journaux,
la reponse ne porte plus qu'un statut.
Filtre nl2br non echappant
Markup('<br>'.join(...)) marquait le texte comme sur sans l'echapper.
Le filtre n'etant utilise dans aucun gabarit, la faille etait latente :
elle se serait ouverte au premier usage. Corrige en Markup('<br>').join(),
qui echappe chaque segment. Verifie : nl2br('<script>alert(1)</script>')
rend desormais <script>alert(1)</script>.
Filtre de redaction des secrets sans effet
SensitiveDataFilter n'inspectait que record.msg. Or le code journalise
en style parametre ('...: %s', valeur) : record.msg ne contient que la
chaine de format, et la donnee sensible vit dans record.args, ignore.
La redaction ne s'appliquait donc pratiquement jamais. Le record est
desormais rendu avant filtrage, puis args vide.
Sortie console conditionnee a FLASK_DEBUG
En production, l'application n'ecrivait rien sur stdout, precisement ou
regarde la console Pterodactyl. Le handler devient inconditionnel, seul
son niveau varie.
Journaux du bot Discord perdus
discord_bot.py utilise getLogger(__name__), soit 'app.discord_bot'.
Aucun handler n'etait attache a la hierarchie 'app' : les INFO etaient
jetes et les WARNING+ tombaient sur le handler de dernier recours, sans
format. Les handlers sont desormais rattaches au logger de paquet.
X-XSS-Protection retire (app.py et nginx.conf)
En-tete deprecie, l'auditeur vise a ete supprime des navigateurs
courants et ses dernieres implementations introduisaient elles-memes
des vulnerabilites.
Co-Authored-By: Claude Opus 5 <[email protected]>
Deux protections etaient configurees sans avoir d'effet.
Expiration de session
app.py:73 definit PERMANENT_SESSION_LIFETIME = 3600, mais Flask
n'applique cette duree qu'aux sessions marquees permanentes. Aucune
occurrence de session.permanent n'existait dans app/. Le cookie emis
etait donc un cookie de session navigateur, sans expiration, et le
serveur ne verifiait aucune anciennete. Ajout de session.permanent
juste avant login_user, apres la rotation anti-fixation.
Desactivation de compte
is_active_account n'etait consulte qu'au moment du login (auth.py:141).
User n'ayant pas surcharge is_active, UserMixin renvoyait True en
permanence. Desactiver un compte empechait donc la reconnexion mais
laissait vivre la session en cours.
La propriete is_active seule ne suffit pas : Flask-Login ne la consulte
qu'a l'appel de login_user, jamais lors de la restauration d'une session
depuis le cookie. Le verrou effectif est donc dans load_user, qui renvoie
desormais None pour un compte desactive. La propriete est ajoutee malgre
tout pour que login_user soit coherent avec le chargeur.
load_user passe au passage de Query.get() (API heritee, avertie en
SQLAlchemy 2.0) a db.session.get(), et tolere un identifiant non entier
sans lever.
Co-Authored-By: Claude Opus 5 <[email protected]>
Trois problemes distincts empechaient une installation propre.
1. audioop-lts==0.2.2 declare Requires-Python >=3.13. Le fichier ayant
ete produit par un pip freeze sur 3.13, la ligne etait inconditionnelle
et faisait echouer `pip install -r requirements.txt` sur le 3.12 cible
par la CI. Les jobs security-scan et test n'atteignaient donc jamais
leur etape utile. Ajout du marqueur python_version >= "3.13" : le
backport n'est tire que la ou audioop a quitte la bibliotheque standard.
2. Fichier encode en UTF-16 LE. pip sait le lire grace au BOM, mais
l'outillage tiers non, et le diff est illisible. Reecrit en UTF-8, LF.
3. psycopg[binary] etait la seule dependance non epinglee. Epinglee a la
version effectivement resolue (3.2.12).
Retrait de trois dependances parasites, verifiees non importees sur
l'ensemble de app/ et de la racine :
- dotenv==0.9.9 doublon relais de python-dotenv (seul import reel)
- discord==2.3.2 doublon relais de discord.py (seul import reel)
- login==0.0.6 sans rapport avec Flask-Login, jamais reference
WTForms est conserve : c'est une dependance transitive legitime de
Flask-WTF (utilisee par CSRFProtect), et non une dependance directe morte.
Installation verifiee en environnement neuf sous Python 3.12.10.
Co-Authored-By: Claude Opus 5 <[email protected]>
- Ajout des dossiers/fichiers des assistants IA (Claude, ChatGPT/OpenAI,
Cursor, Copilot, Aider, Windsurf, Gemini, Continue, Cline).
- Ajout des notes de travail et de suivi generees par assistant (.ai/,
audit/), retirees du suivi git via `git rm --cached` : les fichiers
restent sur disque mais ne sont plus versionnes.
- Retrait des regles `docs/` et `*.html`, qui ignoraient TOUT fichier
.html du depot, y compris les templates Jinja2. Un nouveau template
etait invisible pour git : fonctionnel en local, TemplateNotFound en
production, sans signal dans `git status`. Les rapports de couverture
sont desormais couverts par htmlcov/ et coverage_html_report/.
- `.env` -> `.env*` avec exception `!.env.example`, pour couvrir
.env.local, .env.production et les copies de sauvegarde.
- Ajout des cles et certificats, des sauvegardes (backups/), des
journaux (logs/), des environnements virtuels, des caches d outils
et des fichiers d editeur/OS.
- Deduplication des regles existantes (*.db et instance/ etaient en
double, .instance/ n existait pas).
Aucune modification du code applicatif.
Co-Authored-By: Claude Opus 5 <[email protected]>
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]>
Revue statique de l'ensemble du code Python, de la configuration CI/nginx,
du .gitignore et des dependances. 44 constats documentes avec references
fichier:ligne, impact et correctif propose.
- audit/01-securite.md 19 constats (4 critiques)
- audit/02-maintenabilite.md 15 constats
- audit/03-standards-stack.md 10 ecarts aux conventions Flask/SQLAlchemy
- audit/plan-remediation.md ordre de traitement en 6 lots
Points critiques : secrets de production reels committes dans
app/.env.exemple, seed automatique en production avec mot de passe
password, CORS ouvert a toutes les origines avec credentials par
defaut, token du bot Discord imprime sur stdout au demarrage.
Aucune modification du code applicatif.
Co-Authored-By: Claude Opus 5 <[email protected]>