OBS-005. Un 500 dans errors.log et les six lignes de app.log qui y menent
n etaient relies que par leur horodatage — ce qui n est pas une relation
des que le serveur traite plus d une requete a la fois. Et un utilisateur
qui dit « ca a plante quand j ai clique sur enregistrer » ne donnait a
personne de quoi chercher.
Chaque requete recoit un identifiant, porte par toutes les lignes de
journal qu elle produit, renvoye en X-Request-Id, et affiche sur la page
500 comme reference a citer.
Il est **genere**, jamais lu depuis un en-tete entrant. Accepter celui du
client serait pratique pour tracer a travers nginx, et permettrait aussi a
n importe qui d ecrire du texte arbitraire — retours a la ligne compris —
dans le fichier de journal. C est ainsi qu un journal cesse d etre une
preuve. Il n y a de toute facon aucun proxy de confiance tant qu OPS-002
est ouvert.
Le test correspondant assure sur l alphabet plutot qu en envoyant un
retour a la ligne : le client de test de Werkzeug refuse d emettre un tel
en-tete, donc l attaque ne peut meme pas etre construite par la, ce qui ne
prouverait rien sur l application.
**Defaut trouve en chemin, et repare.** Les cinq gabarits d erreur
remplissent le bloc `content`, qui n existait que dans la branche
authentifiee de la mise en page. Un visiteur deconnecte tombant sur une
erreur — donc typiquement sur la page de connexion — recevait le logo, le
selecteur de langue, et **aucun message**. Le code de statut etait bon, les
journaux etaient bons, la page etait vide. Le <title> disait quand meme
« 404 », ce qui explique en grande partie que personne ne l ait vu.
Le bloc est desormais rendu dans les deux branches via self.content(),
Jinja refusant deux blocs de meme nom. Un seul cote du if s execute, donc
jamais de double rendu — et c est assure, pas suppose.
550 tests.
Active la regle isort (I) de ruff. 45 fichiers reordonnes, aucun
changement de comportement : la suite passe avant comme apres.
app/models/__init__.py en est exclu. Ses imports sont ranges en onze
couches commentees qui decrivent le graphe de dependances ; trier par
ordre alphabetique laisse chaque titre au-dessus d un import qu il ne
decrit pas, et ce fichier n a qu un role, etre lu.
Commit isole, comme le formatage : un diff de brassage ne doit pas
servir de couverture a un changement de comportement.
QUA-002, seconde moitie. Le depot etant formate, l elargissement porte sur
des defauts et non sur du brassage.
Ajoute a la selection : B (bugbear), C4, RET, SIM, UP. Le lot entier n a
produit que 24 signalements sur 76 fichiers -- le code etait plus propre
que l audit ne le craignait. Neuf corriges automatiquement, quinze a la
main.
SIM108 est ignore : forcer un ternaire se lit moins bien que le if/else
qu il remplace, au seul endroit ou il se declenche.
isort (I) n est PAS active. Il reordonnerait les imports de 48 fichiers,
soit une seconde passe de pur brassage juste apres le commit de formatage.
A faire, mais seul.
Deux vrais defauts trouves par les nouvelles regles
- team_matches.edit_match faisait `except ValueError: pass` sur l heure de
debut et l heure de fin, trois lignes sous un champ date qui, lui,
signale et redirige. Une heure mal saisie etait donc acceptee par le
formulaire, jetee, l ancienne valeur conservee -- et la page annoncait
la reussite. Meme traitement que la date desormais.
- backup.py levait BackupError depuis deux blocs `except` sans `from`,
ce qui perdait la cause d origine dans la trace.
Ainsi que : un `return` explicite dans force_https, `%`-formatage remplace
dans log_auth_event (operations de chaine avant journalisation, pas des
gabarits de logger -- la redaction n est pas affectee), une compréhension
inutile, un `set(...)` en compréhension d ensemble, `open(..., 'r')`, une
variable de boucle inutilisee, et `contextlib.suppress` dans conftest.
CI : `ruff format --check` remplace le commentaire qui expliquait pourquoi
il etait absent.
263 tests passent. Les deux nouveaux messages sont traduits ; attention,
pybabel les avait apparies en `fuzzy` avec des entrees « date » existantes,
et une entree fuzzy est ignoree a l execution.
Co-Authored-By: Claude Opus 5 <[email protected]>
QUA-002, premiere moitie. **Ce commit ne fait que reformater** : aucun
changement de comportement, aucune ligne de logique touchee. 72 fichiers,
4 restaient deja conformes. Il est isole exprès, pour que `git log -p` sur
les commits voisins reste lisible.
`quote-style = "preserve"` etait deja pose dans pyproject.toml, ce qui
evite le brassage guillemets simples / doubles : le diff porte sur les
retours a la ligne, l indentation des appels longs et les virgules
finales, pas sur le style de chaine.
Verification : 263 tests passent avant et apres, ruff check propre.
L activation en CI arrive dans le commit suivant, separement, pour que ce
diff-ci ne contienne rien d autre.
Co-Authored-By: Claude Opus 5 <[email protected]>
OBS-001. logging_config.py configurait un fichier auth.log avec rotation,
un logger nomme 'team_tryouts.auth' et un filtre de redaction. Mais
get_auth_logger n'etait importe nulle part : le fichier etait cree et
restait vide. Aucune connexion, aucun echec, aucun verrouillage, aucun
changement de role, aucune suppression de compte ne laissait de trace.
En cas de suspicion de compromission, il n'y avait rien a consulter.
Ajout de log_auth_event(event, **fields), qui emet des paires cle=valeur
ordonnees -- greppable sans dependance de journalisation JSON.
Evenements couverts
authentification login.success, login.failure,
login.failure.unknown_user, login.rejected.locked,
login.rejected.deactivated, account.locked, logout,
account.registered
administration account.created_by_admin, account.updated,
account.role_changed (avec ancien et nouveau role),
account.deleted, account.password_reset_by_admin
libre-service account.password_changed
Le champ ip vient de request.remote_addr, donc de X-Forwarded-For. Tant que
Waitress tourne avec trusted_proxy='*' (SEC-WEB-002), cette valeur est
choisie par l'appelant : c'est une indication, pas une preuve. Le point est
documente dans la docstring.
Un test a fait remonter ARCH-003, jusqu'ici classe comme fragilite latente
Journaliser en fin de edit_user levait DetachedInstanceError : le
changement de role appelle db.session.remove() en plein cycle de requete,
ce qui detache current_user de la session. Le code s'en tirait parce qu'il
redirigeait immediatement sans plus y toucher. L'identite de l'acteur est
desormais capturee en debut de traitement. Le constat est donc confirme
comme reel, et non plus seulement probable -- sa correction de fond reste
au programme.
15 tests, dont 4 sur le filtre de redaction lui-meme : c'est un controle de
securite, il doit etre verifie.
Co-Authored-By: Claude Opus 5 <[email protected]>
/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]>