9cedf4a03847003936a0a8498e2fc9dd8b84c6c1
15
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
bda23dbb67 |
fix(ops): la sauvegarde des contrats archivait le mauvais repertoire
OBS-006. Trois racines etaient baties sur os.getcwd() : le magasin de documents, les journaux et les sauvegardes. La vague G a corrige la premiere, parce qu'elle bloquait aussi OPS-011, et a laisse les deux autres. C'est la lecon deja consignee deux fois : un motif fautif corrige dans une seule couche reste dans les autres. Le plus serieux n'est pas le motif, c'est l'ecart qu'il a ouvert. backup.py gardait sa propre constante DOCUMENTS_DIR sur os.getcwd(), donc il ignorait DOCUMENTS_ROOT -- la variable que la vague G a introduite et que docs/deployment.md dit maintenant de regler pour sortir les televersements des repertoires de version. Des qu'un exploitant suit cette consigne, le script archive un repertoire ou l'application n'a jamais rien ecrit. Et comme il repond a un repertoire absent par une ligne d'information et un code de sortie 0, une tache planifiee qui surveille le code de sortie voit vert indefiniment. Autrement dit : plus l'exploitant suivait correctement la documentation de deploiement, plus surement ses sauvegardes de contrats etaient vides. Les trois racines viennent desormais d'app/storage.py, resolues a l'appel et non a l'import, et la sauvegarde imprime la source qu'elle a utilisee. Le message d'absence nomme le chemin ou elle a cherche : "No documents directory found" se lisait comme "il n'y a pas de documents" plutot que comme "je regarde au mauvais endroit". Le test qui porte est celui qui ouvre l'archive : un zip vide est un fichier de taille non nulle, donc verifier qu'un fichier a ete produit ne prouvait rien. Verifie par mutation. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
7a1dab21cd |
docs: les deux documents d exploitation qui manquaient
DOC-001 demandait quatre documents courts. Deux existaient — installation (deployment.md) et restauration (database-restore.md). Les deux autres n existaient pas. docs/roles-and-permissions.md decrit le comportement **implemente**, pas celui qu on souhaiterait, et note les endroits ou les deux divergent : un gerant gere toutes les equipes mais pas toutes les selections, asymetrie presente depuis toujours et ecrite nulle part ailleurs. Le piege du double rattachement coach/equipe y est en toutes lettres, avec le motif a ne jamais reintroduire. Le document dit aussi qui a raison en cas de desaccord : les tests. Une documentation d autorisation qui se contredit avec le code est pire que pas de documentation, parce qu on la croit. docs/incident-runbook.md part du symptome, pas du composant. Le reflexe d ouverture est la reference affichee sur la page d erreur — c est ce que l identifiant de requete rend possible, et sans un endroit qui le dise, la fonctionnalite ne sert a personne. Trois affirmations ont ete verifiees contre le code avant d etre ecrites, et deux etaient fausses : le corps de /health en echec dit database="error" et non "disconnected", et les evenements de verrouillage s appellent login.failure et account.throttled. La liste complete des quatorze evenements de auth.log est maintenant dans le manuel. README : index des documents, et deux points d etat corriges — TRUSTED_PROXY est desormais une variable, et l absence de validation des inscriptions par le staff est nommee comme decision en attente. |
||
|
|
7b9eee4805 |
feat(db): mesurer la derive du schema, au lieu de la supposer
DB-001. create_all() cree les tables manquantes et ne fait jamais d ALTER. Une colonne ajoutee a un modele il y a six mois est donc absente de toute base qui possedait deja la table, et rien ne le dit : l application demarre, et la premiere requete qui touche cette colonne echoue a l execution. C est la raison d etre de migrations/add_tryout_coaches.py, ecrit a la main pour rattraper un cas. Personne ne sait combien il y en a d autres. app/supporting_scripts/schema_report.py compare le catalogue d une base vivante aux modeles : tables, colonnes, types, nullabilite, contraintes d unicite, cles etrangeres, index. En **lecture seule** — il ouvre une connexion, lit, imprime, sort. Aucun DDL, aucun DML. Les constats sont classes par ce qu ils coutent, pas par ce qu ils sont : - BLOCKING : les modeles l attendent, la base ne l a pas. C est la derive ; - RISK : la base l a, aucun modele ne le decrit. Inoffensif tant que rien ne bouge — et **un alembic --autogenerate proposera de le supprimer**, avec ses donnees. C est la classe qu on lit en entier ; - DIFFERENCE : types, nullabilite, contraintes. Chacune demande un humain. Les types sont compares apres compilation vers le meme dialecte : opposer String(200) a VARCHAR(200) en chaines aurait signale chaque colonne comme differente, et un rapport qui crie partout ne se lit plus. --check-seed-accounts repond a la question de SEC-003 a laquelle le depot ne peut pas repondre : le compte admin/password seme par clear_db.py existe-t-il encore, et son mot de passe est-il toujours celui-la. 13 tests le pilotent contre des bases SQLite fabriquees pour diverger d une facon connue. Le cas qui compte le plus est la base propre : un rapport qui crie sur une base saine ne sera pas lu, et un rapport qui dit « aucun ecart » sur une base derivee est pire que pas de rapport — c est un feu vert pour laisser autogenerate ecrire la difference en DROP. docs/database-schema.md donne la suite, etape par etape, avec le piege de DB-002 en toutes lettres : la migration initiale doit decrire la base telle qu elle est, pas telle que les modeles la decrivent. Generer depuis les modeles puis estampiller revient a declarer que la derive n existe pas. Alembic n est pas ajoute aux dependances : rien ne l utilise encore, et une dependance que rien n utilise est exactement ce que l audit reprochait ailleurs. Le document dit a quelle etape l ajouter. 530 tests. |
||
|
|
3882b6035f |
fix(ops): defauts surs a la copie, CDN epingles, actions epinglees
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. |
||
|
|
39808dd04e |
ops: un deploiement qui refuse de partir casse, et qui se verifie
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. |
||
|
|
d0a9e75fe6 |
perf: servir les statiques par nginx, et dire ce que le bot n a pas livre
PERF-006. Le bloc location /static/ etait commente : 59 Ko de CSS et de JS
passaient par Waitress a chaque page. L activer tel quel aurait ete une
regression : ces URL ne changent jamais, donc un cache de 30 jours sert une
feuille de style vieille d un mois apres chaque deploiement, sans moyen de
l invalider. url_for('static') estampille maintenant chaque URL du mtime du
fichier ; c est ce qui rend le immutable vrai et pas seulement rapide.
Deux pieges nginx consignes dans le fichier : un add_header dans un location
annule tous les add_header herites du server (nosniff disparaissait du
JavaScript), et un statique manquant doit renvoyer 404 plutot que retomber
sur Flask, sinon un deploiement casse se cache derriere une page qui marche.
PERF-005. Les objets utilisateur Discord sont mis en cache. A etre precis
sur le gain : un envoi coute deux appels reseau, resoudre puis envoyer, et
seul le premier est economise — un premier match a vingt joueurs fait
toujours vingt resolutions. Ce qui est gagne l est entre notifications, la
ou le bot ecrit aux memes personnes soir apres soir.
Chaque message dit desormais ce qu il est devenu, avec le destinataire et
la raison. Les trois echecs ne se ressemblent pas et ne se lisent plus
pareil : une boite fermee est definitive et ne se retente pas, une erreur
HTTP est passagere, un identifiant sans proprietaire est un compte a
corriger. Le lot quotidien annonce son propre deficit.
Piege trouve en ecrivant les tests : configure_logging met propagate=False
sur le logger 'app', et le handler de caplog est sur la racine. Les
assertions sur les journaux passaient seules et echouaient dans la suite
complete, ou une application avait deja ete construite — elles lisaient un
journal vide, pas un bot silencieux.
417 tests.
|
||
|
|
aebc28fb8a |
docs: une seule source d architecture, et un README qui dit vrai
DOC-002. docs/architecture.html (746 l.) et docs/architecture-v3.html (798 l.) coexistaient, en HTML versionne, sans rien qui indique laquelle faisait foi ni ou etait passee la v2. docs/architecture.md reprend la v3 -- la plus complete : 24 classes au lieu de 19, plus la structure des paquets -- en Markdown avec les diagrammes en Mermaid. Lisible en revue, comparable en diff, rendu directement par Gitea comme par GitHub. Ajoute au document ce que les diagrammes ne montrent pas et qu il faut savoir avant de les lire : la double modelisation coach/equipe, l absence de migrations, et le bot dans le meme processus que le web. README Il annoncait « Authorization Checks: Proper ownership validation on all sensitive operations » a une epoque ou trois IDOR etaient ouverts, et « Rate Limiting » sans mentionner que trusted_proxy='*' la rend contournable. Il ne disait pas non plus comment installer, lancer ni tester le projet. Reecrit en francais, avec deux sections distinctes : ce qui est en place et verifie par des tests, et **ce qui ne l est pas** -- migrations absentes, secrets non revoques, proxy de confiance, identite Discord non prouvee. Une liste de securite qui ne mentionne que les bonnes nouvelles est pire qu absente : on s y fie. Instructions d installation, de lancement et de verification ajoutees, avec les deux seuls points d entree (ARCH-007). Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
2f40290f00 |
fix(deps): nommer le pilote PostgreSQL, sinon rien ne demarre
QUA-001, volet dialecte.
`postgresql://` ne veut pas dire "le pilote installe" : SQLAlchemy y lit
psycopg2 et importe ce module a la creation du moteur. requirements.txt
epingle psycopg 3 (`psycopg[binary]`) et pas psycopg2. Une installation
propre demarree sur cette URL leve donc
ModuleNotFoundError: No module named 'psycopg2'
avant la premiere requete. Verifie dans le .venv du depot, et c est
exactement la forme que Render distribue -- celle que docs/deployment.md et
docs/database-restore.md donnaient en exemple.
normalise_database_url() nomme le pilote quand l URL n en nomme pas.
`postgres://` (alias hérite, abandonne par SQLAlchemy en 1.4) est traite de
meme. Une URL qui nomme deja son pilote est laissee telle quelle, y compris
`postgresql+psycopg2://` : un environnement qui a psycopg2 garde le choix.
La normalisation a lieu apres l application de la configuration passee en
argument, pour couvrir aussi les appels de test. backup.py n avait pas
besoin d etre touche : il retirait deja le suffixe +pilote.
Documentation alignee sur les trois fichiers qui donnaient l exemple, dont
docs/deployment.md qui proposait sqlite:/// pour DATABASE_URL alors que
create_app refuse de demarrer sans PostgreSQL.
Reste de QUA-001, dit franchement
- les trois paquets parasites (dotenv, login, discord) ne sont plus dans
requirements.txt : deja retires. psycopg est deja epingle.
- la consolidation vers des groupes de dependances n est PAS faite. Le
deploiement est un miroir de fichiers lftp sans etape de construction ;
les groupes PEP 735 demandent pip >= 25.1 sur une machine dont on ne
peut pas verifier la version d ici. A revoir avec OPS-011.
14 tests, dont trois qui prouvent que l echec est reel et non theorique.
Co-Authored-By: Claude Opus 5 <[email protected]>
|
||
|
|
37f70c89e3 |
feat(i18n): traduire les messages flash et de validation
198 appels flash dans les sept modules de routes, plus les 23 messages de validation de app/validators.py. Le catalogue compte desormais 631 chaines, aucune non traduite. validators.py utilise lazy_gettext : les champs de schema sont construits a l'import, donc avant qu'une requete existe. Un gettext ordinaire s'y resoudrait une seule fois, dans la langue active au demarrage. Un bug introduit par la conversion, puis corrige Le convertisseur automatique ne voyait que le premier litteral d'un appel flash, ce qui a casse deux chaines concatenees sur plusieurs lignes dans users.py -- le resultat n'etait meme pas du Python valide. Ma premiere verification ne l'a pas vu : elle enchainait py_compile sur head, or head reussit toujours, donc le "OK" s'affichait quoi qu'il arrive. Les deux appels sont reecrits et la verification refaite correctement. Un bug plus interessant, revele par le test de fumee La langue choisie ne survivait pas a la connexion. login() et logout() appellent tous deux session.clear() -- l'un contre la fixation de session, l'autre pour terminer la session -- et le choix de langue partait avec le reste. Concretement : quelqu'un qui lisait la page de connexion en anglais se retrouvait en francais des qu'il se connectait. La langue est une preference d'affichage, pas un etat appartenant au compte. Les deux endroits la reportent maintenant explicitement, a cote du jeton CSRF. Quatre tests couvrent le cas, dont un qui verifie que corriger une cle preservee n'a pas fait tomber l'autre. Detail de nommage : le convertisseur avait genere %(value)s pour une expression conditionnelle, ce qui n'aide pas un traducteur. Renomme en %(player)s. Les 14 traductions ecrites avec une apostrophe droite sont normalisees en apostrophe typographique. Sans consequence en HTML, ou ' s'affiche correctement -- mais les blocs <script> ne decodent pas les entites, et autant que le catalogue soit homogene. 200 tests. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
80bc1413f1 |
feat(i18n): traduire l'ensemble des gabarits
Le site s'affiche desormais integralement en francais. 483 chaines, aucune non traduite, dans les deux catalogues. Couvert : navigation et mise en page partagee, connexion, les cinq pages d'erreur, et les 29 gabarits de pages. Methode Marquage semi-automatique, conservateur par construction : seuls des motifs sans ambiguite sont balises -- contenu de balises de texte, attributs placeholder/title/aria-label, texte suivant une icone, blocs title et page_title -- et tout contenu comportant du Jinja ou du balisage imbrique est laisse de cote. Deux passes, la seconde pour td, li, h6, strong, em et caption. 97 entrees etaient marquees fuzzy par pybabel update, c'est-a-dire devinees par similarite. Une entree fuzzy est **ignoree a l'execution** : elles ont donc ete traitees comme non traduites, et le drapeau retire une fois la traduction ecrite. Deux pieges rencontres, tous deux documentes dans docs/translations.md Une entite HTML n'est pas du texte. ×, utilise comme libelle de bouton de fermeture, a ete balise par la passe automatique. Jinja l'echappait alors en &times; et le bouton aurait affiche le texte litteral × au lieu de la croix. Corrige dans cinq gabarits. Huit chaines subsistent dans des blocs <script>. Elles fonctionnent mais restent fragiles : Jinja echappe & < > " ' dans un bloc script, et ces entites n'y sont pas decodees -- une traduction contenant une apostrophe droite arriverait dans la chaine JavaScript sous la forme '. Le francais retenu utilise des apostrophes typographiques, non echappees, donc l'existant est sur. Toute nouvelle chaine a cet endroit devra passer par un attribut data- ou un bloc <script type="application/json">. Vocabulaire retenu -- a valider avec le club tryout -> selection · manager -> gerant · coach -> coach (conserve, terme d'usage en e-sport) · scout -> recruteur · email -> courriel · scrim -> scrim. Les noms de jeux et les postes (Support, Duelist, AWPer) restent en anglais : ce sont les termes employes par les joueurs. Ces choix vivent dans un seul catalogue, donc chacun se change en un endroit. Reste a faire : les messages flash hors routes/auth.py, et les messages de validation de app/validators.py, qui necessitent lazy_gettext puisque les champs de schema sont construits a l'import. Verifie : 13 pages parcourues dans les deux langues, aucune ne laisse de marqueur Jinja non evalue ni d'entite doublement echappee. 193 tests. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
a92600c305 |
feat(i18n): francais comme langue principale, anglais accessible
Le site s'affiche desormais en francais par defaut, avec un selecteur de langue permettant de basculer vers l'anglais. Choix de conception : les chaines sources restent en anglais Elles servent d'identifiants gettext, et le francais est fourni par catalogue avec BABEL_DEFAULT_LOCALE = 'fr'. Le code reste ainsi dans une seule langue -- la meme que ses commentaires et docstrings -- tandis que ce qu'un membre voit est du francais. Consequence qui rend la migration praticable : une chaine non encore traduite retombe en anglais, pas sur un identifiant brut. Les gabarits peuvent donc etre migres un par un sans jamais laisser le site a moitie casse. Selection de la langue (app/i18n.py) 1. choix explicite via le selecteur, garde en session 2. sinon en-tete Accept-Language du navigateur, restreint a fr et en 3. sinon francais Un choix explicite prime toujours, y compris sur un navigateur anglophone. Selecteur Extrait en partiel et inclus dans les deux branches de la mise en page : barre laterale une fois connecte, ET page d'authentification. Quelqu'un qui ne lit pas la langue courante doit pouvoir en changer AVANT de se connecter -- le laisser derriere l'authentification aurait ete un defaut d'accessibilite. Chaque langue est ecrite dans sa propre langue. La route /lang/<locale> valide le Referer avant de rediriger : sans ce controle, elle constituait une redirection ouverte. Migre dans cette passe navigation complete, page de connexion, les cinq pages d'erreur, et l'integralite des messages flash de routes/auth.py. 64 chaines, dont aucune non traduite. Verification 25 tests, dont deux garde-fous d'integrite : un catalogue .mo manquant ou une entree non traduite font echouer la suite. Sans cela, une compilation oubliee servirait de l'anglais partout, en silence et sans rien dans les journaux. Un test existant a du etre corrige, et c'est instructif test_login_failure_message_does_not_reveal_account_existence cherchait la sous-chaine anglaise 'attempt(s) remaining'. La page etant desormais en francais, elle etait absente des deux cotes, l'assertion passait, et le mode strict a signale le faux succes. Le test comparait donc l'anglais, pas le comportement. Il compare desormais les messages flash rendus, quelle que soit la langue. La faille SEC-AUTH-006 reste ouverte, et le test la documente toujours. Les catalogues .po ET .mo sont versionnes : le deploiement est un simple miroir de fichiers, sans etape de compilation. messages.pot, regenerable, ne l'est pas. docs/translations.md documente le processus, les deux pieges (concatenation de phrases, traduction a l'import), et l'etat de la migration. A noter pour la suite : les chaines dans les blocs <script> ne peuvent pas etre balisees telles quelles, il faudra les passer par des attributs data- -- ce qui rejoint le chantier de sortie de unsafe-inline (OPS-010). Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
7bf428f9a0 |
fix(ops): sauvegarder reellement la base PostgreSQL
DATA-002 / OPS-001. backup.py ciblait SQLite : import sqlite3, DATABASE_PATH par defaut instance/team_tryouts.db, et l'API de sauvegarde sqlite3. La production tourne sur PostgreSQL, donc le fichier n'existait pas. Le script affichait "[WARNING] Database not found... Skipping database backup" -- puis, main() ne suivant que le resultat de la verification, **sortait avec le code 0**. Toute tache planifiee surveillant le code de sortie voyait vert alors qu'aucune sauvegarde n'avait jamais ete produite. Il n'existait donc aucune sauvegarde applicative de la base. Reecriture pg_dump en --format=custom : compresse, et pg_restore permet une restauration selective, ce qu'un dump SQL a plat ne permet pas. parse_database_url accepte les suffixes de dialecte SQLAlchemy (postgresql+psycopg://) que pg_dump ne comprend pas, et refuse explicitement une URL SQLite -- le cas exact qui passait en silence. Le mot de passe ne figure jamais dans la ligne de commande : il serait visible de tout processus capable de lister argv. Il passe par PGPASSWORD. Il est egalement absent des messages affiches, qui atterrissent dans les journaux du planificateur. verify_backup lit l'archive avec pg_restore --list et exige au moins une table : une archive illisible ne se restaure pas, et une archive sans table signifie que le dump a vise la mauvaise cible. Les deux sont des echecs silencieux qu'il vaut mieux attraper maintenant que pendant un incident. Le code de sortie vaut 0 uniquement si le dump a ete produit ET verifie. L'archive des documents est conservee : les contrats signes n'existent que sur disque, la base ne stocke que des chemins. Restaurer l'une sans l'autre laisse des lignes pointant vers des fichiers absents. docs/database-restore.md Procedure de restauration testable sur une base jetable, requetes de controle, demarrage de l'application sur la copie restauree, plan de reprise par scenario. ENABLE_DISCORD_BOT=false y est signale comme non optionnel : sans lui, l'exercice demarre un vrai bot et envoie de vraies notifications a de vraies personnes, a partir de donnees restaurees. Les points ouverts sont listes tels quels : aucune copie hors site, pas de chiffrement au repos, aucune planification, et l'exercice de restauration n'a jamais ete effectue. 17 tests sur ce qui est verifiable sans serveur PostgreSQL : analyse de l'URL, construction de la commande, non-fuite du mot de passe, et surtout codes de sortie -- le silence ne vaut plus succes. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
b80c5b7aa2 | UML avec la derniere version du projet | ||
|
|
90ac3eb804 | Uml et fonctionnel dans un document .html | ||
|
|
666673fa8f |
demander IA de faire tous les modifications pour que le webapp soit pret au déploiement.
Force connection HTTPS, proxy-inversé, WSGI de production, reset cookie de conncection à chaque reconnection, limite sur les mdp, fichiers et One on One par minute, verification d'injection de SQL dans les champs d'entrées. renommage des fichiers lors du téléchargement, fichier de backup quotidien pour la bd et j'ai oublié quelque chose :( |