Blog / DevOps

Lockfiles GitHub Actions : figer ses actions sans SHA à la main

GitHub généralise le fichier actions.lock qui fige le commit de chaque action d’un workflow : comment l’adopter dans un vieux dépôt, et ce qu’il ne protège pas.

Par Romain Barberi Mis à jour le 29 octobre 2026 8 min de lecture 9 sources

Le 14 octobre, GitHub a ouvert en préversion publique les lockfiles de workflows : un fichier .github/workflows/actions.lock, généré par une extension de la CLI, qui enregistre le commit exact de chaque action utilisée, y compris les actions qu’appellent vos actions. Une fois un workflow intégré, Actions exécute le commit enregistré, même si le tag a bougé entre-temps. C’est la réponse officielle à un conseil que tout le monde connaît et que presque personne n’applique : épingler ses actions par SHA. Dans un dépôt qui a quelques années, avec des @v2 et des @master posés par des gens partis depuis, je pense que c’est le premier outil qui rend cet épinglage tenable. À une condition : comprendre qu’il fige une référence, pas qu’il rend sûr le code qu’il fige.

Un tag n’est pas une version, c’est une étiquette déplaçable

Quand un workflow écrit uses: actions/checkout@v4, il ne désigne pas un code précis. Il désigne ce vers quoi le tag v4 pointe au moment de l’exécution, et le propriétaire du dépôt de l’action peut le déplacer quand il veut. Le plus souvent, c’est voulu : v4 suit les correctifs de la branche 4. Mais si le compte du mainteneur est compromis, le même mécanisme distribue du code malveillant à tous les dépôts qui utilisent ce tag, sans qu’un seul commit n’apparaisse chez eux.

C’est exactement ce qui s’est passé en mars 2025 avec tj-actions/changed-files. Selon l’avis GHSA-mrrh-fwg8-r2c3, les attaquants ont réécrit plusieurs tags existants pour les faire pointer vers un commit malveillant qui lisait la mémoire du runner et affichait les secrets, encodés en base64, dans les journaux des workflows. L’action était utilisée par plus de 23 000 dépôts. Ceux qui l’avaient épinglée par SHA n’ont rien exécuté de nouveau.

Pourquoi l’épinglage par SHA n’a-t-il jamais pris ? Parce qu’une ligne uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 est illisible en revue, pénible à mettre à jour, et qu’elle ne règle qu’un étage : une action composite épinglée peut elle-même appeler une autre action par son tag. Sur les pipelines de projets anciens que j’ai repris, l’épinglage est au mieux partiel, et personne ne sait dire quelles actions transitives tournent réellement.

Ce que contient actions.lock, et ce qu’Actions vérifie

Le fichier est généré par l’extension gh actions-lock et la documentation précise qu’il ne doit jamais être modifié à la main. Pour chaque référence, il enregistre le tag ou la branche (ref), le commit résolu (commit) et l’identité numérique du dépôt source (owner_id, repo_id), que GitHub Actions contrôle à l’exécution. Les dépendances transitives sont listées sous chaque action. Voici la forme publiée par Classmethod, qui a testé la version 0.2.0 de l’extension le 16 octobre (extrait abrégé) :

# This file is machine-generated by `gh actions-lock`.
version: 'v0.0.3'
workflows:
    '.github/workflows/ci.yml':
        - 'actions/checkout@v5.1.0'
        - 'aquasecurity/trivy-action@v0.36.0'
dependencies:
    'aquasecurity/trivy-action@v0.36.0':
        ref: 'v0.36.0'
        commit: 'sha1-ed142fd0673e97e23eac54620cfb913e5ce36c25'
        owner_id: 12783832
        repo_id: 268833180
        uses:
            - 'actions/cache@27d5ce7f...'

Le contrôle de owner_id et repo_id compte plus qu’il n’en a l’air. Si un dépôt d’action est supprimé puis recréé sous le même nom par quelqu’un d’autre, le nom est identique mais l’identifiant numérique change : la documentation demande d’enquêter avant de fusionner tout changement de ces deux champs.

Le comportement à l’exécution, tel que Classmethod l’a observé, est net. Une action ajoutée au workflow sans mise à jour du fichier donne un startup_failure : aucun job ne démarre, avec l’annotation « Invalid lockfile ». Un tag déplacé par un force-push n’est pas suivi : la course suivante télécharge le commit enregistré. Le verrou est « collant » par conception, et seul gh actions-lock --relock accepte un déplacement légitime.

Intégrer un vieux dépôt, un workflow à la fois

Avant de générer quoi que ce soit, faites l’inventaire. Un dépôt ancien mélange en général cinq formes de uses:, et elles ne se traitent pas de la même façon. Ce petit script les classe :

grep -rhoE '^\s*(- )?uses:\s*[^ #]+' .github/workflows \
  | sed -E 's/^\s*(- )?uses:\s*//' \
  | awk '{ ref=$0; sub(/.*@/, "", ref)
           if ($0 ~ /^\.\//)                      t="locale"
           else if (ref ~ /^[0-9a-f]{40}$/)       t="sha"
           else if ($0 ~ /\.github\/workflows\//) t="workflow-reutilisable"
           else                                   t="tag-ou-branche"
           print t "\t" $0 }' | sort | uniq -c | sort -rn

Le script ci-dessus a été exécuté sur un dépôt de test qui contient les cinq formes ; voici sa sortie :

      1 workflow-reutilisable    mon-org/workflows/.github/workflows/deploy.yml@main
      1 tag-ou-branche    shivammathur/setup-php@v2
      1 tag-ou-branche    actions/checkout@v4
      1 sha    actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9
      1 locale    ./.github/actions/composer-install

Chaque ligne appelle une décision. Les tags et branches sont le cœur du sujet : vérifiez d’abord que chaque action pointe vers une version encore maintenue, puisque le lockfile figera ce que la référence désigne aujourd’hui, archivée ou non. Les workflows réutilisables d’un autre dépôt sont couverts par le lockfile du dépôt qui les définit : si votre organisation a un dépôt de workflows partagés, c’est lui qu’on intègre en premier. Les actions locales (./) ne sont jamais suivies ni verrouillées : depuis la version 0.3.0 de l’extension, celles qui appellent des actions distantes bloquent la génération et la vérification par défaut. L’option --migrate-local-actions les réécrit en uses: $/…, une syntaxe qui résout toujours au commit en cours et n’a pas besoin d’entrée ; --ignore-local-actions les laisse en simple avertissement.

Ensuite, la séquence que je retiens :

  1. Activez Dependabot pour les actions (recommandé par la documentation, il régénère l’entrée du lockfile dans la même PR quand il monte une version).
  2. gh extension install github/gh-actions-lock, puis gh actions-lock .github/workflows/ci.yml pour un seul workflow, le moins critique.
  3. Relisez tout le diff, pas seulement le lockfile : l’extension réécrit aussi les références, par exemple @v3 en @v3.1.0, sans changer le commit résolu.
  4. Ajoutez gh actions-lock --verify comme check obligatoire sur les PR. Il ne modifie rien et interroge GitHub ; --verify-local, hors ligne, vérifie seulement qu’une entrée existe.
  5. Passez au workflow suivant, puis au déploiement en dernier.

Un piège à connaître avant l’étape 3 : si votre organisation utilise une liste blanche d’actions écrite par tags (actions/checkout@v6), elle est désormais comparée aux SHA verrouillés et ne correspond plus. La documentation conseille un joker ou le SHA complet. Autre détail utile : les écritures sont tout ou rien. Si une seule référence ne se résout pas, typiquement une action dont le dépôt a disparu, rien n’est écrit, ce qui force à traiter les actions mortes avant tout le reste.

Imposer le verrou à l’échelle de l’organisation

La politique « Require lockfile » bloque les exécutions des workflows ciblés qui ont des dépendances d’actions mais ne sont pas intégrés au lockfile. Elle se règle au niveau de l’entreprise, de l’organisation ou du dépôt, peut viser certains dépôts et certains workflows, et propose deux modes : Evaluate, qui mesure sans bloquer et n’existe que sur GitHub Enterprise, puis Active.

Je ne l’activerais qu’une fois les dépôts intégrés, et jamais sans le check --verify sur les PR, que la documentation recommande d’ailleurs. La politique bloque l’exécution ; le check prévient l’auteur pendant qu’il travaille encore. Sans lui, la première personne à découvrir un lockfile périmé est celle qui attend un déploiement.

Les limites

La plus importante est écrite dans le changelog lui-même : verrouiller une dépendance ne dit pas qu’elle est sûre. Si vous générez le fichier pendant qu’une action est compromise, vous figez la version compromise, et --verify passera au vert. Le lockfile protège contre ce qui change après votre revue, pas contre ce que vous n’avez pas relu.

Il ne protège pas non plus contre le scénario du compte volé. Un attaquant qui peut pousser un workflow dans votre dépôt peut aussi régénérer le lockfile dans le même commit. Contre cela, ce sont les environnements protégés, les relecteurs obligatoires et la durée de vie des jetons qui comptent ; le lockfile vient en plus, pas à la place.

Le périmètre a des trous. Le fichier fige des commits Git : il ne dit rien d’une action qui télécharge un binaire à l’exécution, et la documentation ne mentionne pas les images docker://, que je continue à épingler par digest. GitHub Enterprise Server n’est pas pris en charge. L’extension reste en version 0.x et bouge vite : le format est passé en v0.0.3 avec la 0.2.0, puis la 0.3.0 a rendu --verify plus strict encore, au point qu’un lockfile plus ancien peut échouer en incomplete-lockfile et doit être régénéré par gh actions-lock --relock. Enfin, Evaluate est réservé à Enterprise, ce qui rend le déploiement progressif moins confortable sur un plan Team. Cet article s’appuie sur la documentation de GitHub, le dépôt de l’extension et les essais publiés par Classmethod ; je n’ai pas fait tourner gh actions-lock moi-même, seul le script d’inventaire l’a été.

Concrètement, par où commencer

  1. Lancez le script d’inventaire sur vos dépôts qui déploient et comptez les lignes tag-ou-branche : c’est la taille réelle du chantier.
  2. Traitez d’abord les actions archivées ou dont le dépôt a disparu, puisqu’elles bloqueront la génération.
  3. Intégrez un workflow non critique, mesurez le diff, puis ajoutez --verify en check obligatoire.
  4. Si vous avez un dépôt de workflows partagés, intégrez-le avant les dépôts qui l’appellent.
  5. Posez-vous la question qui reste ouverte : qui, dans l’équipe, relit le commit d’une action avant de le verrouiller ?

Le lockfile transforme une bonne pratique que personne ne tenait en un fichier qu’on relit en PR, et c’est déjà beaucoup pour un pipeline que personne n’avait ouvert depuis sa création.

Mise à jour du 29 octobre 2026

Le 28 octobre, GitHub a annoncé la disponibilité générale des lockfiles de workflows, sur les plans Free, Team et Enterprise Cloud ; la préversion décrite plus haut est donc devenue la fonctionnalité standard, sans changement de syntaxe annoncé. Entre-temps, l’extension est passée en 0.3.0 puis 0.4.0 : la 0.3.0 bloque par défaut les actions locales qui appellent des actions distantes et peut faire échouer --verify sur un lockfile généré avec une version antérieure. Si vous avez suivi cet article pendant la préversion, lancez gh actions-lock --relock, relisez le diff et commitez avant de rendre le check obligatoire. Les passages sur les actions locales et sur la stabilité de l’extension ont été corrigés plus haut.

Sources

Sur ce sujet, l’offre de l’atelier : Audit gratuit

À lire aussi

Un socle à migrer, une architecture à fiabiliser ?

Renfort senior en régie, ou forfait à périmètre et prix fixés : l’atelier intervient à distance sur PHP, Symfony, les systèmes distribués et l’adoption des agents de code, avec des tests de caractérisation avant chaque migration.