Quatre taches de la matrice du rapport, toutes sans dependance, qu aucune
liste de « ce qui reste » ne reprenait.
OPS-003 — app/.env.exemple disait « copiez ce fichier et remplissez les
valeurs pour la production », puis posait FLASK_DEBUG=true,
SESSION_COOKIE_SECURE=false et FORCE_HTTPS=false. Le debogueur Werkzeug
execute du code soumis par le navigateur : cette ligne transformait un
copier-coller en shell distant. Chaque valeur est desormais sure a la
copie, et le fichier refuse de demarrer tant que les deux secrets
obligatoires ne sont pas remplis plutot que de demarrer grand ouvert.
Renomme en .env.example : l orthographe francaise ne correspondait pas a
l exception !.env.example du .gitignore, donc le fichier n etait suivi que
par accident de l ordre des regles. Les deux points de la decision ouverte
du §8 tombent d un seul git mv.
OPS-002 — trusted_proxy='*' et HOST ne sont plus soudes dans wsgi.py. Les
defauts sont **inchanges**, deliberement : choisir sans connaitre la
topologie coupe la prod si nginx est ailleurs, ou casse la limitation de
debit pour tout le monde si on cesse de croire X-Forwarded-For alors que
c etait la seule source d adresses. Ce sont maintenant des variables, les
valeurs sures sont dans .env.example pour un nouveau deploiement, et
docs/deployment.md donne les quatre topologies avec la valeur de chacune.
wsgi.py avertit au demarrage tant que les deux defauts sont en place.
Le commentaire de HOST annoncait « bind to localhost by default » a cote
d un defaut a 0.0.0.0 : il decrivait l intention pendant que le code
faisait l inverse. Il dit maintenant ce qu il fait.
QUA-004 — Font Awesome et FullCalendar etaient charges sans empreinte,
depuis des hotes que la CSP autorise nommement. Qui controle ces CDN
controlait ce qui s execute sur chaque page. Empreintes posees, avec ce
que SRI promet et ce qu il ne promet pas ecrit a cote : ca fige le fichier,
ca ne prouve pas qu il etait honnete au moment du calcul.
**Le CSS de FullCalendar n existait pas.** La v6 embarque ses styles dans
le JS et ce fichier n est pas publie : le <link> repondait 404 a chaque
ouverture du calendrier depuis la montee de version. Une feuille de style
en echec est silencieuse dans le navigateur, c est ce qui l a fait durer.
CI-003 — actions epinglees sur un commit, version en commentaire, dans les
deux forges. Un tag est un pointeur mobile : deplacer v4 fait executer du
code arbitraire dans le job qui detient la cle SSH de production. Ce job
recoit aussi enfin un bloc permissions.
517 tests.
OPS-011, en partie. Ce que le workflow garantit maintenant :
- rien ne part d un arbre casse. La suite, ruff check et ruff format
tournent sur le runner de deploiement avant tout envoi. Une CI verte sur
GitHub ne prouve rien ici : le deploiement se declenche a la main, sur ce
que la branche contient a cet instant ;
- seuls les fichiers nommes partent. La charge est une liste blanche —
app/, wsgi.py, requirements.txt — et non l arbre de travail moins neuf
exclusions. C est par cette porte que clear_db.py, la suite de tests et
les definitions de CI se sont retrouves sur le noeud de production ;
- le deploiement est verifie. /health est interroge pendant deux minutes
apres l envoi et le job echoue s il ne repond jamais « healthy ». Avant,
un arbre a moitie televerse etait un deploiement vert.
Ce qui n est pas garanti, et c est ecrit dans le fichier : la bascule n est
pas atomique. Le miroir se fait sur place, donc pendant le transfert la
production execute un melange de deux versions.
En cherchant a fermer ce point, un defaut a part entiere est apparu. Les
contrats etaient ranges a os.getcwd()/documents et leur chemin absolu
ecrit en base. La racine de stockage suivait donc le repertoire depuis
lequel le processus avait ete lance : redemarrer le serveur ailleurs
envoie les nouveaux contrats dans un nouvel arbre et rend les anciens
illisibles — la base continuant d affirmer qu ils sont la, la panne se
manifeste par un 500 au telechargement, pas par quelque chose
d actionnable.
app/storage.py fixe la racine et DOCUMENTS_ROOT la deplace. Les nouvelles
lignes gardent un chemin relatif, les anciennes gardent leur chemin absolu
et continuent de resoudre : aucune migration de donnees n est necessaire,
donc ce changement n attend pas Alembic.
C etait aussi le troisieme pre-requis de la bascule par repertoires de
version. Les deux autres sont hors d atteinte d ici — la commande de
demarrage Pterodactyl doit pointer sur current/, et les repertoires
partages doivent etre installes sur le noeud. Les deux sont decrits dans
docs/deployment.md, avec la procedure de retour arriere qui manquait.
511 tests.
Les quatre jobs existaient ; aucun ne verifiait ce qu'il annoncait.
security-audit
`pip-audit --require-hashes --no-deps || pip-audit`. L'etape
d'installation ne posait que pip-audit, et aucune des deux formes ne
nommait le fichier d'exigences : le repli auditait l'environnement du
runner, qui ne contenait que pip-audit lui-meme. Le job passait au vert
sans avoir examine une seule dependance de l'application. Remplace par
`pip-audit -r requirements.txt`.
security-scan
Appelait `python security_scan.py`, alors que le fichier se trouve dans
app/supporting_scripts/. En echec a chaque execution depuis le
deplacement du fichier. Trois autres defauts sont apparus en le faisant
tourner :
- la CI passe --skip-http, un argument que l'argparse du script
n'acceptait pas : sortie en erreur 2 meme avec le bon chemin.
- check_dependencies lisait data['dependencies'] comme la liste des
vulnerabilites. Ce tableau liste en realite TOUTES les dependances,
chacune portant un champ vulns vide si le paquet est sain. Les ~45
paquets installes etaient donc signales vulnerables a chaque
execution. Le filtrage se fait desormais sur vulns non vide.
- check_flask_config interceptait son exception et renvoyait quand
meme all_ok : ne pas reussir a charger l'application comptait comme
un controle reussi. La section la plus importante du rapport n'avait
jamais tourne. Elle renvoie desormais False, et l'import fonctionne
grace a l'ajout de la racine du projet dans sys.path.
- la banniere en caracteres semi-graphiques faisait planter le script
sur une console Windows en cp1252, la plateforme meme du projet.
Passee en ASCII.
lint
Ruff n'avait aucun fichier de configuration : le job tournait sur le jeu
de regles par defaut. La configuration vit maintenant dans pyproject.toml.
`ruff format --check` est retire pour l'instant : la base n'ayant jamais
ete formatee, il echouerait sur 62 fichiers sur 64 pour des raisons
etrangeres a la correction. Reformatage puis application : QUA-002.
test
Un `echo` protege par continue-on-error : le job annoncait un succes
sans rien executer. Il lance desormais pytest avec couverture, et bloque.
permissions: contents: read au niveau du workflow, aucune etape n'ecrivant
dans le depot.
Deploiement Gitea
actions/checkout@v7 n'existe pas (derniere majeure : v5) : le workflow
echouait des sa premiere etape. Ramene a v4.
Le miroir lftp poussait l'integralite de l'arbre de travail, dont
clear_db.py -- un script qui vide toutes les tables et recree
admin/password -- vers le noeud de production. Liste d'exclusions ajoutee.
--delete reste volontairement absent : les contrats televerses, les
journaux et le .env du serveur vivent sous la racine de deploiement et
sont absents du depot ; les supprimer detruirait des donnees.
Le workflow de deploiement n'a pas pu etre execute depuis ici : la
syntaxe lftp reste a valider lors du prochain deploiement manuel.
Co-Authored-By: Claude Opus 5 <[email protected]>