outillage : la chaîne borne npm audit et cesse de réinstaller le tableau de bord (#148) #151

Closed
olivier wants to merge 1 commit from olivier/148-chaine-tableau-de-bord into develop
Member

Ferme le #148. Suite du #133, qui avait réuni les jobs sans pouvoir traiter ce
qui restait.

Le constat, mesuré

Sur les treize dernières exécutions de ci.yml, relevées via l'API
actions/tasks de la forge :

Job Durées
Python — qualité, tests, dépendances 124-129 s, 0 échec sur 13
Tableau de bord 26 · 65 · 89 · 238 · 256 · 317 · 322 s, et 5 échecs sur 13, tous à 433-435 s
Contrôles statiques, secrets 3-5 s
Total ci.yml 158 s → 688 s, médiane ≈ 6 min

Le job Python tient 127 s sans un échec : ce n'était donc ni la machine ni le
réseau en général.

Chronométré en local sur les 244 paquets du tableau de bord :

npm ci  (cache npm vide) ........  5,9 s
npm ci  (cache npm chaud) .......  0,8 s
vitest run (42 tests) ...........  1,9 s
vite build ......................  0,6 s
npm audit --audit-level=high ....  > 4 min, tué
   idem --fetch-timeout=30000 --fetch-retries=1 .... 12,5 s

Ce n'est pas npm ci. C'est npm audit, reproduit ici avec le journal npm
à l'appui :

npm warn audit network timeout at: https://registry.npmjs.org/-/npm/v1/security/audits/quick
npm error audit endpoint returned an error

fetch-timeout vaut 300 000 ms par défaut, fetch-retries 2, et l'appel en
fait deux successifs (bulk puis quick) : d'où le plafond de 433-435 s, qui
est une signature de délai d'attente et non de travail variable.

Personne ne pouvait le voir : une seule étape, « Audit et inventaire du tableau
de bord », enchaînait npm ci, npm audit et npm sbom. Le journal imputait
quatre minutes à une ligne unique — d'où l'attribution à npm i.

Ce que fait cette demande

  1. services/dashboard/.npmrc borne les appels réseau de npm : 60 s par
    tentative au lieu de cinq minutes. Versionné, donc valable aussi sur les
    postes de travail — la même pendaison s'y produit.
  2. L'étape est éclatée en trois — installer, auditer, inventorier — pour que
    le journal dise qui coûte quoi.
  3. node_modules entre en cache, clé exacte portée par package-lock.json
    et la version de node, sans repli. Sur une clé qui tombe juste, npm ci
    ne tourne pas.
  4. Le job n'installe plus openssl par apt. Il téléchargeait un index apt
    complet pour un openssl x509 -checkend que le job Python de la même
    exécution fait déjà sur le même fichier.
  5. L'audit distingue ses deux pannes — voir le point à trancher ci-dessous.

Le point à trancher en relecture

npm audit sortait 1 pour deux choses qui n'ont rien à voir, avec le même
message qui ne nomme ni paquet ni réseau :

  • une CVE : le scan a eu lieu, il a trouvé. Rejouer n'y changera rien ;
  • le service d'avis est injoignable : le scan n'a pas eu lieu.

.forgejo/scripts/verdict-audit-npm.js les sépare sur la structure du
rapport — un rapport sans metadata.vulnerabilities est un scan qui n'a pas eu
lieu — et non sur un motif du texte d'erreur, qui change de version en version.

J'ai gardé le cas réseau bloquant, avec un réessai et un message qui nomme
la cause. Raisonnement : c'est celui du --strict de pip-audit déjà en place
côté Python, dont le commentaire dit « une dépendance non auditée est une
dépendance inconnue ». Le désarmer serait une décision de politique de
sécurité, pas un réglage de performance, et ENF-11 exige un scan bloquant.

Mais c'est discutable, et c'est le vrai arbitrage de cette demande : en
l'état, un point d'accès npm injoignable continue de bloquer les fusions de tout
le monde — plus vite (2 min au lieu de 7) et avec un message clair, mais il
bloque. Si l'équipe préfère un avertissement non bloquant sur les demandes de
fusion, le changement est d'une ligne dans la branche *) de l'étape, et
test-chaine-tableau-de-bord.sh devra être ajusté. Je ne l'ai pas fait seul.

Comment c'est vérifié

Deux tests, joués par la chaîne :

  • tests/ci/test-verdict-audit-npm.sh — test de comportement du verdict sur
    sept cas, dont le rapport d'échec réel capturé pendant l'analyse
    (tests/ci/fixtures/audit-npm/endpoint-injoignable.json, sortie authentique
    de npm 10.9.8 en délai d'attente). Joué dans le job node, seul job dont
    l'image porte node à coup sûr.
  • tests/ci/test-chaine-tableau-de-bord.sh — verrouille statiquement les
    quatre correctifs, qui se défont sans le vouloir.

Les deux ont été éprouvés par mutation : huit régressions plausibles
essayées (repli remis sur le cache, borne effacée, borne relâchée, apt-get
remis, audit désarmé, étapes refondues, verdict remplacé par un appel nu, test
du verdict retiré de la chaîne), les huit sont détectées. Deux contrôles ont dû
être corrigés au passage, parce qu'un commentaire du workflow les satisfaisait
sans que le code correspondant existe.

Joués en local, tous verts : les cinq contrôles statiques, les 42 tests du
tableau de bord, la construction, npm ci avec le nouveau .npmrc, le SBOM
(244 composants), et bash -n sur les 28 scripts run: du workflow.

Ce que je n'ai pas fait

  • La sortie anticipée par périmètre, laissée ouverte par le #133. Une
    demande de documentation paie toujours l'installation, l'audit et la
    construction du tableau de bord. C'est le prochain gain, et il est plus gros
    que celui-ci sur les demandes qui ne touchent pas au front.
  • La capacité de l'exécuteur. Les quatre jobs se sérialisent — sur
    l'exécution #255, node démarre à la seconde où python se termine. Ça
    relève du #141 et de ta demande #145, que je ne voulais pas croiser.
  • Le critère « durée médiane sous 90 s sur dix exécutions » du #148 ne peut se
    vérifier qu'après passage sur develop.

Contexte de relecture

.forgejo/ et tests/ci/ sont à @lenaic dans CODEOWNERS, et le #133 était le
sien. La demande #145 touche la configuration de l'exécuteur : si elle passe
d'abord, cette branche n'entre pas en conflit avec elle (aucun fichier commun).

develop est actuellement rouge sur ce job (exécution #240) : c'est aussi
ce que cette demande devrait remettre au vert.

Ferme le #148. Suite du #133, qui avait réuni les jobs sans pouvoir traiter ce qui restait. ## Le constat, mesuré Sur les treize dernières exécutions de `ci.yml`, relevées via l'API `actions/tasks` de la forge : | Job | Durées | |---|---| | Python — qualité, tests, dépendances | 124-129 s, **0 échec sur 13** | | **Tableau de bord** | 26 · 65 · 89 · 238 · 256 · 317 · 322 s, et **5 échecs sur 13, tous à 433-435 s** | | Contrôles statiques, secrets | 3-5 s | | **Total `ci.yml`** | 158 s → 688 s, médiane ≈ 6 min | Le job Python tient 127 s sans un échec : ce n'était donc ni la machine ni le réseau en général. Chronométré en local sur les 244 paquets du tableau de bord : ``` npm ci (cache npm vide) ........ 5,9 s npm ci (cache npm chaud) ....... 0,8 s vitest run (42 tests) ........... 1,9 s vite build ...................... 0,6 s npm audit --audit-level=high .... > 4 min, tué idem --fetch-timeout=30000 --fetch-retries=1 .... 12,5 s ``` **Ce n'est pas `npm ci`.** C'est `npm audit`, reproduit ici avec le journal npm à l'appui : ``` npm warn audit network timeout at: https://registry.npmjs.org/-/npm/v1/security/audits/quick npm error audit endpoint returned an error ``` `fetch-timeout` vaut 300 000 ms par défaut, `fetch-retries` 2, et l'appel en fait deux successifs (`bulk` puis `quick`) : d'où le plafond de 433-435 s, qui est une signature de délai d'attente et non de travail variable. Personne ne pouvait le voir : une seule étape, « Audit et inventaire du tableau de bord », enchaînait `npm ci`, `npm audit` et `npm sbom`. Le journal imputait quatre minutes à une ligne unique — d'où l'attribution à `npm i`. ## Ce que fait cette demande 1. **`services/dashboard/.npmrc`** borne les appels réseau de npm : 60 s par tentative au lieu de cinq minutes. Versionné, donc valable aussi sur les postes de travail — la même pendaison s'y produit. 2. **L'étape est éclatée en trois** — installer, auditer, inventorier — pour que le journal dise qui coûte quoi. 3. **`node_modules` entre en cache**, clé exacte portée par `package-lock.json` et la version de node, **sans repli**. Sur une clé qui tombe juste, `npm ci` ne tourne pas. 4. **Le job n'installe plus openssl par apt.** Il téléchargeait un index apt complet pour un `openssl x509 -checkend` que le job Python de la même exécution fait déjà sur le même fichier. 5. **L'audit distingue ses deux pannes** — voir le point à trancher ci-dessous. ## Le point à trancher en relecture `npm audit` sortait `1` pour deux choses qui n'ont rien à voir, avec le même message qui ne nomme ni paquet ni réseau : - une **CVE** : le scan a eu lieu, il a trouvé. Rejouer n'y changera rien ; - le **service d'avis est injoignable** : le scan n'a pas eu lieu. `.forgejo/scripts/verdict-audit-npm.js` les sépare sur la **structure** du rapport — un rapport sans `metadata.vulnerabilities` est un scan qui n'a pas eu lieu — et non sur un motif du texte d'erreur, qui change de version en version. **J'ai gardé le cas réseau bloquant**, avec un réessai et un message qui nomme la cause. Raisonnement : c'est celui du `--strict` de `pip-audit` déjà en place côté Python, dont le commentaire dit « une dépendance non auditée est une dépendance inconnue ». Le désarmer serait une décision de politique de sécurité, pas un réglage de performance, et ENF-11 exige un scan bloquant. **Mais c'est discutable**, et c'est le vrai arbitrage de cette demande : en l'état, un point d'accès npm injoignable continue de bloquer les fusions de tout le monde — plus vite (2 min au lieu de 7) et avec un message clair, mais il bloque. Si l'équipe préfère un avertissement non bloquant sur les demandes de fusion, le changement est d'une ligne dans la branche `*)` de l'étape, et `test-chaine-tableau-de-bord.sh` devra être ajusté. Je ne l'ai pas fait seul. ## Comment c'est vérifié Deux tests, joués par la chaîne : - **`tests/ci/test-verdict-audit-npm.sh`** — test de comportement du verdict sur sept cas, dont **le rapport d'échec réel capturé pendant l'analyse** (`tests/ci/fixtures/audit-npm/endpoint-injoignable.json`, sortie authentique de npm 10.9.8 en délai d'attente). Joué dans le job `node`, seul job dont l'image porte node à coup sûr. - **`tests/ci/test-chaine-tableau-de-bord.sh`** — verrouille statiquement les quatre correctifs, qui se défont sans le vouloir. Les deux ont été **éprouvés par mutation** : huit régressions plausibles essayées (repli remis sur le cache, borne effacée, borne relâchée, apt-get remis, audit désarmé, étapes refondues, verdict remplacé par un appel nu, test du verdict retiré de la chaîne), les huit sont détectées. Deux contrôles ont dû être corrigés au passage, parce qu'un commentaire du workflow les satisfaisait sans que le code correspondant existe. Joués en local, tous verts : les cinq contrôles statiques, les 42 tests du tableau de bord, la construction, `npm ci` avec le nouveau `.npmrc`, le SBOM (244 composants), et `bash -n` sur les 28 scripts `run:` du workflow. ## Ce que je n'ai pas fait - **La sortie anticipée par périmètre**, laissée ouverte par le #133. Une demande de documentation paie toujours l'installation, l'audit et la construction du tableau de bord. C'est le prochain gain, et il est plus gros que celui-ci sur les demandes qui ne touchent pas au front. - **La capacité de l'exécuteur.** Les quatre jobs se sérialisent — sur l'exécution #255, `node` démarre à la seconde où `python` se termine. Ça relève du #141 et de ta demande #145, que je ne voulais pas croiser. - Le critère « durée médiane sous 90 s sur dix exécutions » du #148 ne peut se vérifier qu'après passage sur `develop`. ## Contexte de relecture `.forgejo/` et `tests/ci/` sont à @lenaic dans CODEOWNERS, et le #133 était le sien. La demande #145 touche la configuration de l'exécuteur : si elle passe d'abord, cette branche n'entre pas en conflit avec elle (aucun fichier commun). `develop` est actuellement **rouge** sur ce job (exécution #240) : c'est aussi ce que cette demande devrait remettre au vert.
outillage: la chaîne borne npm audit et cesse de réinstaller le tableau de bord (#148)
Some checks failed
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 2m10s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Failing after 2m13s
4e0ff3cbd6
Le job « Tableau de bord » échouait cinq fois sur treize exécutions, toujours à
433-435 s, et durait de 26 s à 322 s quand il passait. Le job Python, lui, tient
124-129 s sans un échec : ce n'était donc ni la machine ni le réseau en général.

Ce n'était pas non plus « npm ci », à qui la lenteur était imputée. Chronométré
en local sur les 244 paquets du tableau de bord : « npm ci » met 5,9 s cache
vide et 0,8 s cache chaud, les 42 tests 1,9 s, la construction 0,6 s. C'est
« npm audit » qui se pendait — reproduit ici, plus de quatre minutes, journal
npm à l'appui : « network timeout at .../security/audits/quick », puis « audit
endpoint returned an error ». « fetch-timeout » vaut cinq minutes par défaut,
« fetch-retries » 2, et l'appel en fait deux successifs (« bulk » puis
« quick ») : d'où le plafond de 433-435 s, qui est une signature de délai
d'attente et non de travail.

Personne ne pouvait le voir : une seule étape, « Audit et inventaire du tableau
de bord », enchaînait « npm ci », « npm audit » et « npm sbom ». Le journal
imputait quatre minutes à une ligne unique.

Quatre changements, plus un cinquième qui est un vrai choix :

1. services/dashboard/.npmrc borne les appels réseau de npm — 60 s par
   tentative au lieu de cinq minutes. Versionné, donc valable aussi sur les
   postes : la même pendaison s'y produit.

2. L'étape est éclatée en trois — installer, auditer, inventorier — pour que le
   journal dise qui coûte quoi.

3. node_modules entre en cache, clé exacte portée par package-lock.json et la
   version de node, SANS repli. Sur une clé qui tombe juste, « npm ci » ne
   tourne pas. Pas de « restore-keys » ici volontairement : un arbre
   partiellement restauré ne rejouerait pas l'installation et ferait construire
   sur un mélange de deux verrous.

4. Le job n'installe plus openssl par apt. Il téléchargeait un index apt complet
   pour un « openssl x509 -checkend » que le job Python de la même exécution
   fait déjà sur le même fichier. Le contrôle d'expiration de la racine Caddy
   reste donc, une fois au lieu de deux ; l'étape qui pose NODE_EXTRA_CA_CERTS
   n'a besoin que du fichier PEM, pas du binaire.

5. L'audit distingue enfin ses deux pannes, qui sortaient toutes deux « 1 » avec
   le même message : une CVE (le scan a eu lieu) et un service d'avis
   injoignable (le scan n'a pas eu lieu). La logique vit dans
   .forgejo/scripts/verdict-audit-npm.js et tranche sur la structure du rapport
   — un rapport sans « metadata.vulnerabilities » est un scan qui n'a pas eu
   lieu — et non sur un motif du texte d'erreur, qui change de version en
   version.

   Le cas réseau reste BLOQUANT, avec un réessai et un message qui nomme la
   cause. C'est le même raisonnement que le « --strict » de pip-audit côté
   Python, déjà en place : une dépendance non auditée est une dépendance
   inconnue. Ce point mérite d'être tranché en relecture, il est discutable.

Deux tests couvrent tout ça, joués par la chaîne :

- tests/ci/test-verdict-audit-npm.sh éprouve le verdict sur sept cas, dont le
  rapport d'échec réel capturé pendant l'analyse (fixture
  endpoint-injoignable.json). Joué dans le job « node », seul job dont l'image
  porte node à coup sûr.
- tests/ci/test-chaine-tableau-de-bord.sh verrouille statiquement les quatre
  correctifs, qui se défont sans le vouloir. Éprouvé par mutation : les huit
  régressions essayées sont détectées, et deux contrôles ont été corrigés parce
  qu'un commentaire du workflow les trompait.

docs/runbooks/ci.md est réaligné : il décrivait encore les six tâches d'avant le
#133 alors que la chaîne n'en a plus que quatre, et il gagne la conduite à tenir
quand l'audit échoue pour cause de réseau et non de CVE.

Le critère « durée médiane sous 90 s sur dix exécutions » du #148 ne peut se
vérifier qu'après passage sur develop.

Refs #148

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Author
Member

Fermée au profit de la #149, qui traite le #148 et couvre les quatre mêmes correctifs. Travail redondant de ma part, désolé du bruit.

Deux choses de celle-ci qui ne sont pas dans la #149, à prendre ou à laisser :

  1. .forgejo/scripts/verdict-audit-npm.jsnpm audit sort 1 pour deux pannes qui n'ont rien à voir : une CVE (le scan a eu lieu) et le point d'accès injoignable (le scan n'a pas eu lieu). Le script les sépare sur la structure du rapport — un rapport sans metadata.vulnerabilities est un scan qui n'a pas eu lieu — et non sur le texte d'erreur, qui change de version en version. Couvert par tests/ci/test-verdict-audit-npm.sh, sept cas, dont la sortie réelle de npm 10.9.8 en délai d'attente capturée pendant l'analyse (tests/ci/fixtures/audit-npm/endpoint-injoignable.json).

  2. Une mesure qui concerne directement la #149 : la chaîne jouée sur cette branche (exécution #278) a fini le job en 133 s au lieu de 433-435 s — les bornes font leur travail — mais elle a échoué quand même, le point d'accès npm étant injoignable au même moment. Reproduit en local dans la même demi-heure, journal npm à l'appui : network timeout at .../security/audits/quick. La #149 rencontrera la même chose : borner l'appel rend la panne rapide et lisible, elle ne la supprime pas. Le vrai arbitrage reste entier, et il est à l'équipe : un point d'accès npm en panne doit-il bloquer les fusions ?

Branche olivier/148-chaine-tableau-de-bord conservée pour l'instant si quelque chose vaut d'être repris ; à supprimer sinon.

Fermée au profit de la #149, qui traite le #148 et couvre les quatre mêmes correctifs. Travail redondant de ma part, désolé du bruit. Deux choses de celle-ci qui ne sont pas dans la #149, à prendre ou à laisser : 1. **`.forgejo/scripts/verdict-audit-npm.js`** — `npm audit` sort `1` pour deux pannes qui n'ont rien à voir : une CVE (le scan a eu lieu) et le point d'accès injoignable (le scan n'a pas eu lieu). Le script les sépare sur la structure du rapport — un rapport sans `metadata.vulnerabilities` est un scan qui n'a pas eu lieu — et non sur le texte d'erreur, qui change de version en version. Couvert par `tests/ci/test-verdict-audit-npm.sh`, sept cas, dont **la sortie réelle de npm 10.9.8 en délai d'attente** capturée pendant l'analyse (`tests/ci/fixtures/audit-npm/endpoint-injoignable.json`). 2. **Une mesure qui concerne directement la #149** : la chaîne jouée sur cette branche (exécution #278) a fini le job en **133 s au lieu de 433-435 s** — les bornes font leur travail — mais **elle a échoué quand même**, le point d'accès npm étant injoignable au même moment. Reproduit en local dans la même demi-heure, journal npm à l'appui : `network timeout at .../security/audits/quick`. La #149 rencontrera la même chose : borner l'appel rend la panne rapide et lisible, elle ne la supprime pas. Le vrai arbitrage reste entier, et il est à l'équipe : un point d'accès npm en panne doit-il bloquer les fusions ? Branche `olivier/148-chaine-tableau-de-bord` conservée pour l'instant si quelque chose vaut d'être repris ; à supprimer sinon.
olivier closed this pull request 2026-09-04 09:49:00 +00:00
Some checks failed
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 2m10s
Required
Details
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 3s
Required
Details
Intégration / Aucun secret commité (pull_request) Successful in 3s
Required
Details
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Failing after 2m13s
Required
Details

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
g2/enervision!151
No description provided.