Commit Graph
7 Commits
Author SHA1 Message Date
GGThed 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.
2026-08-11 14:42:23 -04:00
GGThedandClaude Opus 5 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. &times;, utilise comme libelle de
  bouton de fermeture, a ete balise par la passe automatique. Jinja
  l'echappait alors en &amp;times; et le bouton aurait affiche le texte
  litteral &times; 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 &#39;. 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]>
2026-08-07 21:43:29 -04:00
GGThedandClaude Opus 5 fcb58e8a17 feat(csp): retirer unsafe-inline de script-src
SEC-WEB-001 / OPS-010, ferme. C'est cette directive qui laissait s'executer
le XSS stocke de SEC-XSS-001 au lieu de le bloquer.

Les cinq derniers gabarits sont migres : match_form 13, calendar 11,
teams 11, evaluate_player 9, view_tryout 8. Total sur le chantier : 82
gestionnaires en ligne retires dans 17 gabarits. Il n'en reste aucun.

Deux motifs generiques de plus dans main.js
  data-mirror             affichage direct de la valeur d'un curseur.
                          evaluate_player repetait le meme
                          oninput="this.nextElementSibling.textContent = ..."
                          sur ses neuf curseurs de note.
  data-submit-on-change   remplace onchange="this.form.submit()"

Markup genere dans des chaines JavaScript
  match_form construisait sept gestionnaires par concatenation, en y
  injectant l'identifiant du joueur. Le markup portait deja data-player-id :
  returnToPool et assignToTeam lisent desormais leurs arguments depuis
  l'element clique. Cela supprime a la fois l'attribut en ligne et la
  concatenation qui l'alimentait. Meme motif que dans coach_availability.

Bascule
  CSP_ALLOW_INLINE_SCRIPT passe a false. script-src vaut maintenant
  'self' 'nonce-<aleatoire par requete>' https://cdn.jsdelivr.net.
  La variable d'environnement reste, comme issue de secours si un
  deploiement rencontrait un gestionnaire oublie -- mais la laisser active
  revient a renoncer a la protection.

Le cliquet devient une garde
  Le budget par gabarit est vide et les tests deviennent absolus : aucun
  gestionnaire en ligne, et tout bloc <script> inline doit porter son
  nonce. Sans nonce, un bloc n'est simplement pas execute, et rien dans les
  journaux ne le signale -- d'ou le test.

Verifications
  22 pages parcourues avec les trois roles : toutes rendent en 200, aucune
  ne contient de gestionnaire en ligne, et chaque bloc inline porte bien le
  nonce de sa propre reponse. Syntaxe JavaScript de chaque gabarit verifiee
  par node --check.

193 tests. Le dernier xfail de SEC-WEB-001 reussissait, le marqueur est
retire. Il n'en reste qu'un : SEC-AUTH-006, enumeration de comptes.

style-src conserve 'unsafe-inline' : les attributs style="" sont partout et
ne sont pas un vecteur XSS a eux seuls. Migration distincte, non prioritaire.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-07 21:03:31 -04:00
GGThedandClaude Opus 5 15bfebf4fc feat(csp): infrastructure de sortie de unsafe-inline, et couche partagee migree
SEC-WEB-001 / OPS-010. script-src porte toujours 'unsafe-inline' : c'est
pour cela que le XSS stocke de SEC-XSS-001 s'executait au lieu d'etre
bloque. Le retirer n'est pas un changement d'une ligne.

Ce qui bloque reellement
  Un nonce autorise des elements <script> ; il ne peut rien pour un
  attribut onclick="...". Mesure faite : 76 gestionnaires en ligne repartis
  dans 15 gabarits. Tant qu'il en reste un, la politique ne peut pas etre
  durcie.

  Piege supplementaire, documente dans build_csp() : en CSP niveau 3, un
  navigateur ignore 'unsafe-inline' des qu'un nonce est present. Emettre
  les deux ne serait donc pas une transition douce -- ce serait couper
  d'un coup tous les scripts en ligne et tous les onclick, et uniquement
  sur les navigateurs recents. La bascule doit etre atomique, d'ou un
  drapeau unique : CSP_ALLOW_INLINE_SCRIPT.

Infrastructure posee
  build_csp() assemble l'en-tete selon le drapeau. Un nonce est genere par
  requete et n'est emis que lorsque l'inline est interdit. Les 15 blocs
  <script> portent deja nonce="{{ csp_nonce }}", inerte aujourd'hui : la
  bascule finale sera un changement de configuration, pas de gabarits.

Couche partagee migree en premier
  base.html et macros.html sont rendus sur absolument toutes les pages. Six
  gestionnaires retires, remplaces par des attributs data-action et un
  ecouteur delegue unique dans main.js. La delegation plutot qu'un
  ecouteur par widget : le contenu injecte dynamiquement herite du
  comportement sans re-attachement.

Un cliquet plutot qu'une promesse
  tests/test_csp.py fixe un budget par gabarit qui ne peut que baisser.
  Ajouter un gestionnaire en ligne fait echouer la suite ; en retirer sans
  mettre le budget a jour aussi, ce qui force a enregistrer la progression
  dans le diff. A zero, il ne reste qu'a basculer le drapeau.

  Le cliquet a d'ailleurs corrige mon propre relevé : mon grep initial
  comptait 83 gestionnaires, la mesure exacte en donne 76 -- le motif ne
  verifiait pas l'espace avant l'attribut.

style-src conserve 'unsafe-inline' : les attributs style="" sont partout et
ne constituent pas un vecteur XSS a eux seuls. Migration distincte.

192 tests.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-07 20:44:16 -04:00
GGThedandClaude Opus 5 d05e9cde32 fix(security): supprimer le XSS stocke du calendrier
SEC-XSS-001. Chaine complete : un nom d'utilisateur libre arrivait dans le
DOM d'un coach ou d'un administrateur, en meme origine, avec sa session.
La CSP autorisant 'unsafe-inline', rien ne l'arretait.

Cote serveur - la cause
  /matches/api/events construisait de la presentation dans un champ JSON :
      match_desc = participants_str + f"<br>{match.description}"
  Le navigateur deposait cette valeur telle quelle dans innerHTML. Les noms
  de joueurs y transitaient sans echappement -- et il ne pouvait pas y en
  avoir : c'est du JSON, pas du HTML.

  Les deux valeurs etaient deja des cles distinctes du payload. La
  concatenation faisait donc aussi afficher les participants deux fois dans
  le modal : une fois dans "Teams", une fois en tete de "Description".
  Corriger la faille corrige l'affichage.

Cote navigateur - le sink
  showEventModal assemblait une chaine HTML puis l'affectait a innerHTML.
  Remplace par une construction de noeuds : makeEl / detailItem /
  multilineNode / teamNode passent tout texte par textContent. Les retours
  a la ligne d'une description restent rendus, via des <br> crees en dur.

  Les deux listes deroulantes concatenaient egalement titres de tryout et
  noms d'equipe dans innerHTML. Remplacees par new Option(), dont le
  premier argument est pose en texte.

Verification
  5 tests sur le contrat de l'API, dont un avec un nom d'utilisateur
  hostile ecrit directement en base -- ce que la validation refuse
  desormais, mais que des lignes anterieures peuvent contenir.
  Le JavaScript inline extrait passe `node --check`.

Reste ouvert : la CSP autorise toujours 'unsafe-inline' (SEC-WEB-001), donc
la defense en profondeur manque encore. Suivi en OPS-010.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-07 19:56:27 -04:00
cedrick2711 68e3da6601 fix probleme avec dispos 2026-08-06 13:41:53 -04:00
cedrick2711 3feae80767 remodulation du projet et des classes 2026-07-28 23:33:31 -04:00