Aller au contenu
English
CI/CD & Automatisation medium

Maintenir des actions épinglées par SHA à l'échelle

Read this page in English

30 min de lecture

Épingler chaque action sur le SHA de son commit ferme la porte aux tags déplacés. Mais un SHA fige aussi les correctifs : sans mécanisme de mise à jour, la bonne pratique devient une dette. Cette page montre comment tenir un parc épinglé dans la durée, avec Dependabot ou Renovate, et pourquoi le point critique n'est pas l'outil mais le tri de la file.

  • Mesurer la dette que crée un épinglage non maintenu
  • Configurer Dependabot pour les actions, les images et les dépendances
  • Réduire le bruit par le groupement et la fenêtre de quarantaine
  • Trier la file sans l'accumuler, et décider quoi fusionner automatiquement
  • Rejouer les digests d'images de base, que Dependabot ne couvre pas toujours

Le raisonnement abstrait convainc mal. Voici l'état d'un dépôt de référence exemplaire par ailleurs (permissions minimales, scanners en CI, provenance SLSA, signature Cosign, note OpenSSF Scorecard soignée), audité en septembre 2026, dont la file Dependabot n'avait pas été traitée depuis le 16 juillet, soit sept semaines.

Élément épingléVersion en placeVersion couranteÉcart
actions/checkoutv7.0.0v7.0.11 correctif
docker/login-actionv4.4.0v4.5.01 version mineure
getplumber/plumberv0.4.6v0.4.6054 versions
python:3.11-slimdigest de juilletdigest du 2 septembreimage non corrigée

Trois conséquences, toutes constatées :

  • La CI est tombée d'elle-même. Le job de scan d'image échouait sur chaque pull request, y compris celles qui ne touchaient ni le Dockerfile ni les dépendances. Le digest épinglé ne recevant aucun correctif, il a suffi qu'une CVE corrigée en amont soit publiée pour que le seuil HIGH,CRITICAL soit franchi.
  • La documentation a divergé du code. Les guides citaient des épinglages que le dépôt ne portait plus, alors qu'ils annonçaient commenter ses fichiers réels.
  • Dix pull requests s'étaient empilées, au point de rendre la file elle-même décourageante à traiter.

Dependabot lit le commentaire de version placé après le SHA pour savoir où vous en êtes. C'est la raison pour laquelle ce commentaire n'est pas décoratif : sans lui, l'outil ne sait pas comparer.

- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

La configuration minimale couvre les trois écosystèmes d'un dépôt conteneurisé :

.github/dependabot.yml
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"

Notez la déclaration docker : sans elle, l'image de base reste sur son digest indéfiniment, et c'est exactement le scénario qui a fait tomber la CI mesurée plus haut.

Une file de dix pull requests ne se traite pas, elle s'ignore. Deux réglages divisent le volume sans rien perdre.

Le groupement rassemble plusieurs mises à jour dans une seule pull request :

.github/dependabot.yml
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
groups:
actions-patch:
update-types: ["patch"]
actions-minor:
update-types: ["minor"]

La fenêtre de quarantaine (cooldown) retarde la proposition d'une version fraîchement publiée. L'intérêt est de sécurité, pas de confort : une version compromise est généralement retirée en quelques heures ou quelques jours, et attendre suffit à ne jamais la voir passer.

.github/dependabot.yml
cooldown:
default-days: 7

Renovate offre un réglage que Dependabot n'a pas, et qui compte pour un parc épinglé : pinDigests, qui convertit et maintient les références par digest, y compris pour les images.

renovate.json
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"github-actions": {
"enabled": true,
"pinDigests": true
},
"docker": {
"pinDigests": true
},
"minimumReleaseAge": "7 days"
}

Le choix entre les deux se résume ainsi : Dependabot est intégré, sans service tiers à autoriser, et suffit si vos actions portent déjà leurs commentaires de version. Renovate se configure plus finement, sait épingler lui-même ce qui ne l'est pas encore, et regroupe mieux. Sur un dépôt unique, Dependabot suffit ; sur un parc de dizaines de dépôts, Renovate devient rentable.

L'outillage n'est jamais le problème. Dans le cas mesuré plus haut, Dependabot fonctionnait parfaitement : il avait proposé toutes les mises à jour manquantes, en temps voulu. Personne ne les avait regardées.

Une règle de tri tient en trois lignes, et la difficulté est de s'y tenir.

Type de mise à jourDécisionCondition
Patch sur une action ou une imageFusion, éventuellement automatiqueCI verte, y compris le scan de workflows
MineureFusion après lecture du changelogCI verte
MajeureRevue explicite, jamais en lotCI verte et changelog lu

L'auto-fusion des patchs n'est défendable que si votre CI est réellement bloquante. Un dépôt qui fait passer actionlint, zizmor, poutine et un gate de posture à seuil strict peut se le permettre : la porte est gardée par les scanners, pas par l'attention humaine.

.github/workflows/dependabot-automerge.yml
name: Dependabot auto-merge
on: pull_request_target
permissions: {}
jobs:
automerge:
if: github.actor == 'dependabot[bot]'
runs-on: ubuntu-24.04
permissions:
contents: write
pull-requests: write
steps:
- name: Récupérer les métadonnées de la mise à jour
id: meta
uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0
- name: Activer l'auto-fusion pour les patchs
if: steps.meta.outputs.update-type == 'version-update:semver-patch'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_URL: ${{ github.event.pull_request.html_url }}
run: gh pr merge --auto --squash "$PR_URL"

Deux points de vigilance sur cet exemple. Le déclencheur est pull_request_target, nécessaire pour que le jeton dispose des droits d'écriture sur une pull request de Dependabot ; le workflow ne checkoute jamais le code de la branche, ce qui le maintient dans le cas d'usage sûr décrit dans Sécuriser pull_request_target. Et gh pr merge --auto n'outrepasse rien : la fusion n'a lieu que lorsque toutes les protections de branche sont satisfaites.

Les images de base méritent une mention séparée, parce que leur mode de défaillance est le plus déroutant : la CI tombe sans qu'aucune ligne du dépôt n'ait changé.

FROM python:3.11-slim@sha256:9534e5a8e315485d4061ed659af0fd78a284c015f9b73661b41d6bab25604534

Ce digest est immuable, donc reproductible, donc jamais corrigé. Pendant ce temps, la base de vulnérabilités de votre scanner est mise à jour quotidiennement. Un job configuré ainsi finira par échouer :

with:
severity: HIGH,CRITICAL
ignore-unfixed: true
exit-code: "1"

Le réglage ignore-unfixed: true est pourtant le bon : il ne signale que les vulnérabilités pour lesquelles un correctif existe. Autrement dit, quand ce job devient rouge, il vous dit quelque chose d'exact et d'actionnable : une correction est disponible en amont et vous ne l'avez pas prise.

La remédiation est le rejeu de l'épinglage :

Fenêtre de terminal
# Relever le digest courant du tag suivi
docker buildx imagetools inspect python:3.11-slim --format '{{.Manifest.Digest}}'
  1. Relever le digest courant du tag que vous suivez.

  2. Remplacer le digest dans tous les stages du Dockerfile, y compris le stage de build : un FROM oublié laisse la vulnérabilité dans l'image intermédiaire.

  3. Laisser la CI trancher. Si le scan repasse au vert, la CVE venait bien de la couche de base. S'il reste rouge, elle vient de vos dépendances applicatives, et c'est un autre chantier.

Le mécanisme importe moins que le rendez-vous. Trois formats fonctionnent, par ordre de coût croissant :

  • L'auto-fusion des patchs ne coûte aucun temps humain, à condition que la CI soit un vrai garde-fou.
  • Un créneau hebdomadaire de quinze minutes suffit à vider une file groupée. C'est le format qui échoue le plus souvent, parce qu'il repose sur la discipline.
  • Une revue mensuelle rattrape les majeures et les changements de fond, avec lecture des changelogs.

Le signal d'alarme est simple à surveiller, et il est objectif :

Fenêtre de terminal
# Nombre de pull requests Dependabot en attente
gh pr list --repo OWNER/REPO --author "app/dependabot" --json number --jq 'length'

Au-delà de cinq, la file n'est plus traitée, elle est subie. C'est le moment de grouper davantage, d'automatiser les patchs, ou d'assumer que certaines mises à jour n'ont pas besoin d'être proposées chaque semaine.

Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

  • Un SHA épinglé ne reçoit aucun correctif : sans mécanisme de mise à jour, l'épinglage transforme une protection en dette.
  • Le commentaire de version après le SHA n'est pas décoratif, c'est ce que Dependabot lit pour comparer.
  • Déclarez l'écosystème docker en plus de github-actions : sinon l'image de base reste figée et finit par faire échouer votre scan.
  • Le groupement et la fenêtre de quarantaine rendent la file traitable, et la quarantaine évite d'être dans la première vague d'adoption d'un paquet compromis.
  • Renovate apporte pinDigests, utile pour épingler et maintenir ce qui ne l'est pas encore ; Dependabot suffit sur un dépôt unique.
  • L'auto-fusion des patchs n'est défendable que si la CI est réellement bloquante, et gh pr merge --auto n'outrepasse aucune protection.
  • Une pull request Dependabot fermée à la main ne revient pas : il faut @dependabot reopen.
  • Un scan d'image qui devient rouge sans changement dans le dépôt signale un correctif disponible en amont, pas un faux positif : rejouez le digest.
  • Auditer avec zizmor : Le scanner qui repère les actions restées sur un tag mutable, et celles dont le commentaire de version ne correspond plus au SHA.
  • Durcir le runner avec Harden-Runner : La couche qui limite ce qu'une dépendance compromise peut exfiltrer, quand elle passe malgré la quarantaine.
  • Checklist sécurité : Le point de contrôle qui vérifie que la file de mises à jour est réellement traitée, et pas seulement configurée.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn