Cron SRI en échec : le bug caché dans check_sri_versions

- Date : 22 août 2026
- Sévérité : P2
- Statut : Résolu
- Impact : Cron SRI en échec silencieux depuis mi-août (9 incidents BetterStack accumulés) ; un second bug sous-comptait un diagnostic cosmétique, jamais l’intégrité réelle des hashes
En bref
Ce site épingle chaque bibliothèque hébergée sur CDN (Font Awesome, Mermaid, Lunr, et d’autres) avec des hashes Subresource Integrity, et un job cron hebdomadaire sur la VM Hugo vérifie que ces hashes sont toujours valides et met automatiquement à jour les versions mineures/patch quand une nouvelle release est disponible. Ce job échouait à chaque exécution depuis des semaines, silencieusement, et l’investigation d’aujourd’hui a mis au jour deux bugs distincts — l’un dans l’infrastructure opérateur, l’autre dans mcp-hugo-server-go lui-même.
Le déroulé
check-sri-versions.sh détecte une version mineure/patch obsolète, récupère le nouvel asset, calcule son hash SRI, puis reconstruit le site avec hugo --minify --cleanDestinationDir avant de déployer et de purger Cloudflare. L’étape de rebuild échouait avec chtimes: operation not permitted chaque semaine depuis que l’outil build_site du serveur MCP gère lui-même public/ — cet outil tourne sous un compte système dédié mcp-hugo-server-go, et une fois qu’il a construit le site une fois, chaque fichier de public/ appartient à ce compte. chtimes (utilisé par Hugo pour préserver les mtimes lors de la copie des fichiers statiques) exige spécifiquement que le processus appelant soit propriétaire du fichier de destination — un droit d’écriture sur le répertoire parent ne suffit pas. Le job cron tournait sous un utilisateur différent, il ne pouvait donc plus toucher son propre répertoire de sortie.
Comme le rebuild échouait, le chemin de rollback du script échouait exactement de la même façon, et chaque run se terminait par un incident BetterStack que rien ne résolvait jamais automatiquement — neuf s’étaient accumulés depuis mi-août sans que la cause réelle n’émerge, en partie parce que le fichier de log de diagnostic du script était supprimé juste après la ligne qui indiquait où aller le lire.
Le correctif
Le correctif le plus simple et correct : exécuter les étapes de rebuild et de déploiement sous le même compte que celui qui possède public/, plutôt que d’accorder à l’utilisateur du cron un accès plus large à une arborescence qu’il n’a pas besoin de posséder. Aucune nouvelle élévation de privilège n’a été nécessaire — le compte opérateur avait déjà un sudo sans restriction. Un run réel ensuite a appliqué les deux mises à jour de version en attente, reconstruit, déployé, purgé Cloudflare, vérifié les nouveaux hashes en live, et résolu automatiquement les neuf incidents accumulés en une seule passe.
Ce que ce correctif a révélé
Une fois que le rebuild hugo --minify du job cron a enfin réussi pour la première fois, check_sri_versions — l’outil intégré du serveur MCP prévu exactement pour cette vérification — s’est mis à rapporter une chute nette et reproductible de l’un de ses compteurs de diagnostic : files_with_sri_attributes est passé de 276 à 2.
La cause s’est révélée être un second bug, indépendant et dormant. build_site (l’outil MCP que la plupart des opérateurs utilisent réellement) ne passe jamais --minify à Hugo, donc sa sortie HTML garde toujours des attributs entre guillemets, exactement comme écrits dans les templates : href="https://cdn.jsdelivr.net/...". Le flag --minify propre à Hugo — utilisé seulement par les scripts cron/déploiement externes de ce déploiement, jamais par le serveur MCP — active un minifieur HTML qui retire les guillemets des valeurs d’attribut qui n’en ont pas besoin, ce qui est parfaitement valide en HTML5 : href=https://cdn.jsdelivr.net/... s’interprète à l’identique dans n’importe quel navigateur. Mais la regex d’extraction d’URL de check_sri_versions codait en dur les guillemets :
var sriURLRe = regexp.MustCompile(`(?:src|href)="(https?://[^"]+)"`)Face à une balise sans guillemets, ce motif ne matche rien, et l’outil ignore silencieusement le fichier dans son compteur. La partie sécurité-critique de l’outil — la comparaison de hash réelle contre data/sri.yaml — passe par un chemin de code totalement séparé et est restée correcte tout du long ; il s’agissait d’un sous-comptage cosmétique dans une statistique descriptive, pas d’une vérification d’intégrité cassée. Mais c’est exactement le genre de chose qui érode la confiance dans le reporting d’un outil si ça reste inexpliqué : une chute de 276 à 2 ressemble à une régression sérieuse tant qu’on ne sait pas où regarder.
Ce bug existait vraisemblablement depuis l’écriture de l’outil, mais n’avait jamais été déclenché, parce que le seul chemin de build produisant une sortie --minify échouait sur le bug de permission ci-dessus depuis aussi longtemps que quiconque ait surveillé la chose. Corriger un bug est la seule raison pour laquelle le second est devenu visible.
À retenir
Deux chemins de build produisant du HTML subtilement différent pour le même site mérite d’être su en soi, indépendamment des deux bugs — un opérateur qui débogue un problème de découverte ou de rendu devrait vérifier quel mécanisme a touché public/ en dernier avant de supposer que les templates eux-mêmes ont changé. Et un script de diagnostic qui supprime son propre log d’erreur juste après avoir indiqué où aller le lire coûtera du temps réel la prochaine fois que quelque chose casse d’une façon jamais vue auparavant.
Les deux problèmes sont tracés : le correctif de regex est déposé sous mcp-hugo-server-go#1252 ; le correctif cron/permission et cet incident complet sont documentés sur le wiki du projet.