Aller au contenu

Sécuriser les mises à jour automatiques de dépendances et semver

Sécuriser les mises à jour automatiques de dépendances et semver

Section intitulée « Sécuriser les mises à jour automatiques de dépendances et semver »

Le versionnement sémantique (SemVer, Semantic Versioning) est une promesse de compatibilité faite par un mainteneur, pas une propriété vérifiée par qui que ce soit. Les plages de versions et les robots de mise à jour transforment pourtant cette promesse en autorisation implicite d’exécuter le code du prochain publieur. Le sujet : qui décide qu’une nouvelle version entre, et à quel moment.

→ Lire l’explication complète de la marmotte

Ce que SemVer garantit et ce qu’il ne garantit pas

Section intitulée « Ce que SemVer garantit et ce qu’il ne garantit pas »

SemVer 2.0.0 découpe une version en MAJEURE.MINEURE.CORRECTIVE : la majeure signale une rupture d’interface, la mineure un ajout compatible, la corrective une correction compatible. C’est tout. Rien ne vérifie la compatibilité, ni l’identité du publieur, et rien n’empêche une version 1.4.2 de contenir un voleur de jetons.

Les gestionnaires de paquets traduisent cette promesse en plages. Sous npm, ^1.2.0 accepte tout jusqu’à <2.0.0, ~1.2.0 tout jusqu’à <1.3.0, et ^0.2.3 se limite à <0.3.0 car la série 0.x n’offre aucune garantie. Cargo applique ^ par défaut. Une plage est donc une délégation : elle autorise d’avance des versions qui n’existent pas encore.

Les incidents publics suivent tous le même schéma. event-stream (2018) a reçu une dépendance malveillante après un transfert de maintenance ; ua-parser-js (2021) a publié trois versions piégées depuis un compte compromis ; colors et node-ipc (2022) ont été sabotés par leurs propres auteurs ; en 2025, un hameçonnage de mainteneur a suffi à pousser des versions correctives piégées de paquets téléchargés des milliards de fois par semaine. Chaque fois, le numéro était parfaitement conforme à SemVer.

Plages de versions, fichiers de verrouillage et reproductibilité

Section intitulée « Plages de versions, fichiers de verrouillage et reproductibilité »

La règle tranche selon la nature du projet. Une bibliothèque publie des plages larges, sinon ses consommateurs croulent sous les conflits. Une application ou un service déployé fixe tout : fichier de verrouillage versionné, installation stricte, et chaque changement de version passe par une revue de code comme n’importe quel autre changement.

Le fichier de verrouillage (package-lock.json, Cargo.lock, poetry.lock, go.sum) fige l’arbre complet, dépendances transitives comprises, avec une empreinte d’intégrité par paquet (SHA-512, Secure Hash Algorithm, encodé en SRI, Subresource Integrity, pour npm). Il n’a de valeur que si l’installation le respecte : npm ci échoue si le verrou et le manifeste divergent, alors que npm install réécrit le verrou sans prévenir. Une chaîne d’intégration continue (CI) qui lance npm install n’a pas de verrou, elle a un fichier décoratif.

Deuxième verrou souvent oublié : les scripts d’installation. Un paquet npm peut exécuter du code à postinstall, donc sur le poste du développeur et dans la CI, avant tout test. --ignore-scripts par défaut, avec une liste blanche pour les rares paquets qui compilent du code natif, ferme cette porte.

Robots de mise à jour : le compromis fraîcheur contre exposition

Section intitulée « Robots de mise à jour : le compromis fraîcheur contre exposition »

Renovate et Dependabot ouvrent des demandes de fusion (pull requests) à chaque nouvelle version. Le compromis central est là : fusionner vite réduit la fenêtre d’exposition aux vulnérabilités corrigées ; fusionner vite augmente l’exposition aux versions malveillantes, qui sont presque toujours détectées et retirées du registre en quelques heures ou quelques jours. Les deux risques n’ont pas la même forme : une vulnérabilité connue demande encore un exploit adapté au contexte, une version piégée s’exécute à l’installation.

La recommandation : délai de quarantaine de sept jours sur toute nouvelle version, fusion automatique réservée aux correctives et mineures des dépendances de développement si la suite de tests passe, revue humaine pour tout ce qui part en production, validation explicite des majeures. Les correctifs de sécurité contournent la quarantaine sur avis de sécurité, pas par impatience. Un robot qui fusionne seul les dépendances d’exécution ne met pas à jour le projet : il délègue le droit de livrer en production à toute personne qui obtient le mot de passe d’un mainteneur.

Point d’architecture rarement vu : les branches créées par Renovate vivent dans le dépôt lui-même, donc la CI qui les construit reçoit les secrets du dépôt. Une dépendance piégée qui s’exécute pendant npm ci peut les lire. Les exécutions déclenchées par Dependabot sur GitHub reçoivent au contraire un jeton en lecture seule et seulement les secrets dédiés à Dependabot. Dans les deux cas, les jobs qui installent des dépendances non revues ne doivent disposer d’aucun secret de déploiement.

Il faut aussi épingler les actions de CI par empreinte de commit plutôt que par étiquette : en 2025, les étiquettes de l’action tj-actions/changed-files ont été redirigées vers un commit malveillant (CVE, Common Vulnerabilities and Exposures, 2025-30066), et tous les dépôts qui référençaient @v45 l’ont exécuté.

Exemple : politique Renovate avec quarantaine et vérification des signatures

Section intitulée « Exemple : politique Renovate avec quarantaine et vérification des signatures »

Configuration à placer à la racine du dépôt sous le nom renovate.json5 (JSON5, variante de JavaScript Object Notation qui accepte les commentaires), suivie des commandes d’installation stricte pour la CI.

{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended", "helpers:pinGitHubActionDigests"],
// Applications : versions exactes dans le manifeste, en plus du verrou
"rangeStrategy": "pin",
// Quarantaine : aucune version de moins de 7 jours n'est proposée
"minimumReleaseAge": "7 days",
"internalChecksFilter": "strict",
"packageRules": [
{
// Seules les correctives et mineures de développement fusionnent seules
"matchDepTypes": ["devDependencies"],
"matchUpdateTypes": ["patch", "minor"],
"automerge": true
},
{
// Toute majeure attend une validation explicite dans le tableau de bord
"matchUpdateTypes": ["major"],
"dependencyDashboardApproval": true
}
],
// Les avis de sécurité passent outre la quarantaine
"vulnerabilityAlerts": { "minimumReleaseAge": null, "labels": ["securite"] }
}
Fenêtre de terminal
# Installation stricte : échoue si package.json et le verrou divergent
npm ci --ignore-scripts
# Vérifie les signatures du registre et les attestations de provenance
npm audit signatures

npm audit signatures vérifie la signature du registre et, si elle existe, l’attestation reliant l’archive à un dépôt et à une chaîne de construction : elle prouve d’où vient le paquet, pas qu’il est sain.

Ce que ces contrôles ne garantissent pas, et l’exploitation

Section intitulée « Ce que ces contrôles ne garantissent pas, et l’exploitation »

Le verrou garantit la reproductibilité, la quarantaine réduit l’exposition aux publications malveillantes détectées vite, la provenance lie un paquet à sa source. Aucun ne détecte un mainteneur légitime qui publie volontairement du code malveillant avec une provenance parfaite, ni une porte dérobée patiente comme celle de xz-utils (CVE-2024-3094), introduite sur deux ans par un contributeur de confiance. Ce reste relève de l’isolement des constructions, de la réduction du nombre de dépendances et de la détection à l’exécution, sujets traités dans la rubrique développement et côté menaces.

En exploitation : conserver les verrous de chaque version livrée pour répondre en quelques minutes à « cette version tournait-elle en production ? », surveiller le tableau de bord du robot (une file de demandes jamais traitées finit en mise à jour massive, donc en revue bâclée), faire tourner les jetons de publication des paquets internes et exiger l’authentification à deux facteurs sur les comptes de registre.

Sans contrôle des plages de versions ni revue des mises à jour automatiques, une contrainte comme ^1.2.0 accepte toute version mineure ou corrective publiée ensuite : une version piégée, diffusée depuis un compte de mainteneur compromis, entre dans la chaîne de construction dès l’installation suivante, sans qu’aucune modification du dépôt ne le signale. Un outil de mise à jour configuré pour fusionner seul les correctifs transforme cette confiance dans le numéro de version en exécution de code tiers dans l’intégration continue, avec accès aux secrets qui y sont exposés. Les CMS ont industrialisé le même pari : la mise à jour automatique des extensions WordPress installe en production, de nuit, le code d’un inconnu que personne n’a relu. Sans fichier de verrouillage versionné, deux constructions du même commit peuvent embarquer des dépendances différentes. Il devient alors impossible d’établir quelle version tournait en production au moment d’un incident, et le retour à un état sain exige de reconstituer l’arbre de dépendances à la main.

Un logiciel moderne réutilise des centaines de petits morceaux de code écrits par d’autres, appelés dépendances. Chaque morceau porte un numéro en trois parties : le premier change quand le morceau casse ce qui marchait avant, le deuxième quand il ajoute quelque chose, le troisième quand il corrige un défaut. Ce numéro est choisi par l’auteur du morceau, et personne ne vérifie qu’il dit vrai.

Beaucoup de projets acceptent automatiquement toute nouvelle version dont seul le deuxième ou le troisième chiffre change. Si le compte d’un auteur est volé, le voleur publie une nouvelle version avec un numéro normal, et elle est installée partout à la prochaine construction. Certains morceaux exécutent même du code dès leur installation.

Pour se protéger, on enregistre dans un fichier la liste exacte des versions utilisées, avec une empreinte de chacune, et l’installation refuse tout écart. Un programme propose ensuite les nouvelles versions, mais seulement après une semaine d’attente, le temps que les versions piégées soient repérées et retirées. Les changements destinés au service réel sont relus par une personne. Les machines qui construisent le logiciel n’ont pas accès aux mots de passe de production pendant qu’elles installent du code non relu.

Rien de tout cela n’arrête un auteur de confiance qui décide lui-même de tricher.

À lire aussi

Pages voisines

: les deux pages se citent l’une l’autre.