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.
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 :
- 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).
gh extension install github/gh-actions-lock, puisgh actions-lock .github/workflows/ci.ymlpour un seul workflow, le moins critique.- Relisez tout le diff, pas seulement le lockfile : l’extension réécrit aussi les références, par exemple
@v3en@v3.1.0, sans changer le commit résolu. - Ajoutez
gh actions-lock --verifycomme check obligatoire sur les PR. Il ne modifie rien et interroge GitHub ;--verify-local, hors ligne, vérifie seulement qu’une entrée existe. - 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
- 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. - Traitez d’abord les actions archivées ou dont le dépôt a disparu, puisqu’elles bloqueront la génération.
- Intégrez un workflow non critique, mesurez le diff, puis ajoutez
--verifyen check obligatoire. - Si vous avez un dépôt de workflows partagés, intégrez-le avant les dépôts qui l’appellent.
- 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
- GitHub Actions workflow lockfiles in public preview, GitHub Changelog, 14 octobre 2026
- Workflow dependency lockfiles, GitHub Docs, consulté le 19 octobre 2026
- Locking action dependencies in a repository’s workflows, GitHub Docs, consulté le 19 octobre 2026
- Enforcing lockfiles for action dependencies, GitHub Docs, consulté le 19 octobre 2026
- github/gh-actions-lock, GitHub, consulté le 19 octobre 2026
- GitHub Actionsのworkflow lockfileを試す, Classmethod, 16 octobre 2026
- GitHub Actions workflow lockfiles are generally available, GitHub Changelog, 28 octobre 2026
- gh-actions-lock v0.3.0, GitHub, 22 octobre 2026
- tj-actions changed-files through 45.0.7 allows remote attackers to discover secrets by reading actions logs, GitHub Advisory Database, 15 mars 2025
Sur ce sujet, l’offre de l’atelier : Audit gratuit