Aller au contenu principal
pipelinebrut.fr — CI/CD · automatisation · terrain
Sécurité CI

Actions CI tierces : les durcir sans bloquer les équipes

Les actions CI réutilisables accélèrent les pipelines, mais élargissent la surface d’attaque. Méthode concrète pour les encadrer sans les interdire.

Par Julien Mercier 8 min de lecture
Actions CI tierces : les durcir sans bloquer les équipes

Les actions CI réutilisables sont devenues une commodité : une ligne dans un workflow permet de récupérer un dépôt, configurer un runtime, publier une image, analyser du code ou notifier une équipe. GitHub Actions, les composants GitLab CI/CD, les images Docker et les scripts récupérés à l’exécution évitent de réécrire sans cesse les mêmes étapes.

Cette rapidité a un coût : chaque dépendance ajoutée au pipeline peut exécuter du code dans un contexte parfois très privilégié. Un workflow doté d’un jeton d’écriture, de secrets de déploiement ou d’identifiants cloud n’est pas un simple fichier YAML. C’est un point d’entrée dans la chaîne de livraison.

La réponse n’est pas d’interdire toute action tierce. Ce serait irréaliste, et cela pousserait souvent les équipes vers des scripts copiés-collés, moins maintenus et encore moins visibles. L’objectif est plutôt de savoir ce qui s’exécute, avec quels droits, sur quels événements et pour quelle finalité. Voici une méthode praticable pour durcir les actions CI sans casser le rythme de livraison.

Pourquoi les actions CI tierces sont devenues un risque opérationnel concret

Une action CI ne se limite pas toujours au dépôt que l’on référence dans le YAML. Une action JavaScript peut embarquer des dépendances npm ; une action Docker s’appuie sur une image et ses couches ; une action composite appelle potentiellement des scripts shell et d’autres binaires. De même, un composant GitLab peut inclure d’autres fichiers, télécharger un outil ou s’appuyer sur une image de conteneur.

Le premier problème est celui de la mutabilité. Une référence telle que uses: fournisseur/action@v4 est lisible, mais elle désigne généralement un tag Git qui peut être déplacé par le mainteneur du dépôt. Le contenu exécuté aujourd’hui n’est donc pas nécessairement celui qui sera exécuté au prochain pipeline. Ce mécanisme est pratique pour recevoir des correctifs, mais il ouvre aussi la voie à l’exécution d’un changement inattendu.

Le second problème est celui du contexte. Dans GitHub Actions, le jeton GITHUB_TOKEN, les secrets du dépôt, les clés utilisées pour publier un package ou les identifiants fédérés vers un cloud peuvent devenir accessibles à une étape compromise. Dans GitLab CI/CD, les variables protégées, les jetons de job, les variables masquées et les environnements protégés demandent la même vigilance.

Ce risque n’est pas théorique. En 2021, l’incident touchant l’uploader Bash de Codecov a montré qu’un script téléchargé dans des pipelines pouvait devenir un vecteur de compromission. En 2025, une compromission ayant affecté l’action tj-actions/changed-files a rappelé qu’une action largement utilisée peut exposer des secrets lorsqu’elle est exécutée dans des workflows concernés. Ces cas ne signifient pas que toutes les actions communautaires sont dangereuses. Ils confirment qu’une dépendance de CI mérite un traitement similaire à une dépendance applicative, avec une attention renforcée sur ses privilèges.

Dans un pipeline, la question utile n’est pas seulement « cette action est-elle populaire ? », mais « que pourrait-elle faire avec les droits et les secrets disponibles à cet instant ? »

Identifier les dépendances invisibles dans les workflows

Avant de définir des règles, il faut établir une vue factuelle. Commencez par chercher les points d’exécution externes dans tous les dépôts, y compris les dépôts de templates, d’infrastructure et d’outillage interne.

Pour GitHub Actions, l’inventaire doit couvrir les fichiers situés dans .github/workflows, mais aussi les actions locales de .github/actions. Recherchez notamment :

  • les références uses: vers des actions publiques ou internes ;
  • les actions Docker référencées avec docker:// ;
  • les commandes curl, wget, pip install, npm install ou go install lancées pendant le job ;
  • les images indiquées dans les conteneurs de job ou de service ;
  • les workflows réutilisables appelés via workflow_call.

Dans GitLab CI/CD, cherchez les include:, les composants CI/CD, les images déclarées par image:, les services, ainsi que les scripts qui téléchargent un exécutable. Les inclusions distantes et les projets de templates internes méritent une attention particulière : ils peuvent modifier le comportement de nombreux projets en une seule mise à jour.

Un simple tableau suffit pour démarrer. Pour chaque dépendance, notez le dépôt ou registre source, la référence utilisée, le ou les projets consommateurs, le type d’exécution, les secrets accessibles, les permissions demandées et le propriétaire interne. L’objectif n’est pas de produire une CMDB parfaite. Il est de rendre visibles les dépendances qui ne le sont pas dans les revues de code habituelles.

Des outils peuvent accélérer cette étape. GitHub Dependency Graph aide à suivre certaines dépendances de paquets, tandis que Renovate sait mettre à jour des références GitHub Actions et de nombreuses images ou dépendances de configuration. Pour les dépôts GitHub, OpenSSF Scorecard inclut des contrôles sur le pinning des actions ; notre article sur l’audit simple des pipelines avec Scorecard peut servir de point de départ.

Évaluer la criticité sans inventer une matrice ingérable

Il est inutile de soumettre chaque action à un audit exhaustif. En revanche, toutes ne méritent pas le même niveau de confiance. Une action qui formate un commentaire sur une pull request ne présente pas le même impact qu’une action qui publie une image de production ou qui assume un rôle AWS, Azure ou Google Cloud.

Classez les usages selon trois critères simples :

  • Le niveau de privilège : lecture seule du dépôt, écriture sur le dépôt, accès à un registre, déploiement, administration d’infrastructure.
  • La sensibilité des données : aucune donnée sensible, jeton de lecture, secret de publication, identifiant cloud, clé de signature.
  • L’exposition du déclencheur : branche protégée, exécution manuelle, planification interne, pull request issue d’un fork, commentaire ou événement externe.

Une action exécutée sur une pull request provenant d’un fork doit être regardée avec une prudence particulière. Sur GitHub, les événements pull_request et pull_request_target n’ont pas le même modèle de sécurité. Le second s’exécute dans le contexte de la branche cible : il ne doit pas servir à exécuter sans précaution du code fourni par la pull request. GitHub détaille ce point dans sa documentation de durcissement des GitHub Actions.

Dans la pratique, créez trois niveaux : faible, sensible et critique. Au niveau faible, une action peut être autorisée avec des garde-fous standards. Au niveau sensible, elle doit utiliser une version approuvée et recevoir des permissions explicitement limitées. Au niveau critique, ajoutez une revue par un référent sécurité ou infra, une séparation des jobs, des environnements protégés et, si possible, une authentification fédérée plutôt qu’un secret de longue durée.

Cette approche est plus utile qu’une liste figée de « bonnes » et de « mauvaises » actions. Une même action peut être acceptable dans un job de lint isolé et inadaptée dans un job qui signe un artefact ou déploie en production.

Le pinning : figer ce qui s’exécute réellement

Pour GitHub Actions, la mesure la plus robuste consiste à référencer une action par son SHA de commit complet, et non seulement par une branche ou un tag. Une référence par SHA garantit que le workflow exécute le même commit tant que vous ne modifiez pas le YAML.

Voici la différence :

steps:
  - uses: actions/checkout@v4

Cette forme est pratique mais le tag reste mutable. Une référence durcie ressemble à ceci :

steps:
  - uses: actions/checkout@<SHA_DE_COMMIT_COMPLET> # v4.x

Le commentaire conserve une information lisible pour les humains, tandis que le SHA apporte l’immuabilité. Il ne faut pas recopier un SHA depuis une source incertaine : récupérez-le depuis le dépôt officiel de l’action, puis faites-le passer par votre processus de revue.

Le pinning ne veut pas dire ne plus mettre à jour. Il déplace la mise à jour d’un comportement implicite vers une modification explicite, diffable et revue. C’est là que Renovate est particulièrement utile : il peut proposer des pull requests pour actualiser les références, au lieu de laisser un tag évoluer silencieusement. Consultez aussi notre retour sur l’automatisation des mises à jour avec Renovate sans créer de bruit.

Le même raisonnement vaut pour les images de conteneur. Un tag comme node:22 est plus prévisible que node:latest, mais reste mutable. Pour les jobs les plus sensibles, le pinning par digest d’image fournit une référence immuable. Il faut toutefois organiser les mises à jour de ces digests, faute de quoi l’équipe finit avec des environnements figés et des correctifs de sécurité retardés.

Pour GitLab CI/CD, référencez les composants et les fichiers inclus à une version précise plutôt qu’à une branche évolutive. Lorsqu’un fichier distant est inclus, GitLab propose aussi un mécanisme d’intégrité pour les inclusions distantes ; vérifiez sa compatibilité avec votre instance et votre version avant de le standardiser. L’idée reste identique : éviter qu’un contenu externe change sans modification visible dans le projet consommateur.

Réduire les permissions et isoler les secrets

Le pinning limite le risque de changement inattendu, mais une action validée peut aussi contenir une erreur, être compromise ou faire un usage excessif de ses droits. La seconde ligne de défense est donc le moindre privilège.

Dans GitHub Actions, déclarez les permissions du GITHUB_TOKEN explicitement. Un workflow qui n’a besoin que de lire le contenu du dépôt peut commencer ainsi :

permissions:
  contents: read

Accordez ensuite des droits supplémentaires uniquement au job qui en a réellement besoin. Par exemple, la publication d’un package peut nécessiter une permission dédiée, tandis que les jobs de tests, de lint et de génération de documentation restent en lecture seule. Évitez les permissions globales trop larges, car elles sont héritées par des étapes qui n’en ont pas l’usage.

Les secrets doivent suivre la même logique. Ne rendez pas les identifiants de production disponibles dans un job exécuté sur chaque pull request. Utilisez les environnements protégés, les règles de déploiement et la séparation entre validation et publication. Sur GitHub, l’authentification par OIDC dans les pipelines CI/CD permet, lorsque le fournisseur cloud le prend en charge, de remplacer certains secrets statiques par des jetons temporaires.

Une bonne architecture sépare souvent les responsabilités :

  • un job de build produit un artefact sans identifiants de production ;
  • un job de test analyse cet artefact avec des permissions réduites ;
  • un job de publication, déclenché dans un contexte contrôlé, reçoit les droits nécessaires ;
  • un job de déploiement utilise un environnement protégé et des identités dédiées.

Cette séparation limite le rayon d’action d’une dépendance défaillante. Elle rend aussi les incidents plus faciles à diagnostiquer, car l’accès aux secrets et les opérations de publication sont concentrés dans peu de jobs clairement identifiés.

Éviter les contournements dangereux dans les scripts communautaires

Interdire une action sans proposer d’alternative conduit souvent à un curl | bash ajouté dans un coin de workflow. Or, télécharger et exécuter un script depuis une URL mobile n’est pas plus sûr qu’utiliser une action tierce. C’est même souvent moins traçable.

Lorsqu’une équipe a besoin d’un outil, privilégiez cet ordre de décision :

  • une fonctionnalité native de la forge ou du runner ;
  • une action ou un composant officiellement maintenu, épinglé et revu ;
  • un binaire ou une image provenant d’un registre identifié, avec une version fixe ;
  • un petit wrapper interne si le besoin se répète et que le comportement doit être maîtrisé.

Un wrapper interne ne doit pas devenir une réécriture complète d’un outil populaire. Son rôle est de stabiliser une interface, imposer des paramètres sûrs et centraliser quelques choix : image autorisée, version, permissions attendues, journalisation ou validation des entrées. Par exemple, une action interne de publication peut encapsuler l’authentification OIDC et refuser toute publication depuis une branche non protégée.

Documentez aussi les exceptions. Il peut être raisonnable d’utiliser une action non épinglée pendant une expérimentation courte, dans un dépôt sans secrets ni permissions d’écriture. Mais l’exception doit avoir un responsable, une échéance et un périmètre explicite. Sans cela, le provisoire devient l’état permanent du pipeline.

Construire un catalogue d’actions validées sans équipe plateforme-goulot

Un catalogue ne nécessite pas une grande plateforme interne ni un comité qui approuve chaque ligne YAML. Son but est de réduire le coût de la bonne décision. Une page dans le dépôt d’ingénierie, un fichier versionné ou un portail interne léger peuvent suffire.

Pour chaque entrée, indiquez :

  • le besoin couvert : checkout, setup de runtime, build, publication, scan, notification ;
  • la référence approuvée et sa politique de mise à jour ;
  • les permissions minimales attendues ;
  • les contextes autorisés et interdits ;
  • un exemple de configuration ;
  • le propriétaire qui suit les mises à jour et les alertes.

Le catalogue doit proposer des chemins simples. Si publier une image OCI exige de recopier cinquante lignes de YAML, les équipes contourneront la règle. Préparez plutôt des workflows réutilisables ou des composants avec des entrées claires, une documentation courte et des valeurs par défaut restrictives.

Sur GitHub, les workflows réutilisables permettent de partager une implémentation entre dépôts. Sur GitLab, les composants CI/CD offrent un mécanisme comparable pour réutiliser une configuration paramétrable. Dans les deux cas, versionnez ces briques, conservez des exemples d’usage et traitez leurs changements comme ceux d’une API interne : compatibilité, notes de version et période de migration si nécessaire.

La gouvernance peut rester distribuée. Une petite équipe sécurité ou DevOps définit les règles minimales et maintient les briques communes à fort impact. Les équipes produit gardent la possibilité de proposer une nouvelle action, via une pull request qui contient le cas d’usage, la référence épinglée, les permissions nécessaires et le plan de mise à jour. Ce modèle évite à la fois l’anarchie et le ticket obligatoire pour chaque expérimentation.

Automatiser le contrôle plutôt que compter sur la mémoire

Une convention non vérifiée finit inévitablement par dériver. Ajoutez des contrôles automatiques dans les dépôts et, si votre organisation le permet, au niveau de la forge.

Quelques contrôles apportent déjà beaucoup de valeur :

  • signaler les actions GitHub référencées par tag ou branche plutôt que par SHA ;
  • refuser l’usage de latest pour les images des jobs sensibles ;
  • détecter les permissions d’écriture globales et demander une justification ;
  • repérer les téléchargements de scripts non versionnés ;
  • alerter lorsqu’un secret de production est utilisé hors d’un environnement protégé ;
  • vérifier que les actions critiques sont mises à jour via des pull requests revues.

Commencez en mode rapport. Publiez les écarts, corrigez les cas les plus exposés, puis transformez progressivement les règles les plus stables en blocages. Une règle qui casse cinquante dépôts sans chemin de migration ne renforce pas durablement la sécurité : elle crée de la dette et des contournements.

Le suivi doit également couvrir les évolutions des actions approuvées. Une action épinglée protège contre le déplacement d’un tag, mais elle ne vous avertit pas d’une vulnérabilité ou d’un correctif disponible. Des mises à jour automatisées, revues dans des branches dédiées, maintiennent cet équilibre entre contrôle et maintenance.

Conclusion : sécuriser les dépendances CI sans ralentir la livraison

Les actions tierces ne sont ni un détail de configuration ni un mal à bannir. Elles font partie de votre chaîne d’approvisionnement logicielle et doivent être gérées en fonction de leurs privilèges réels. Inventorier les usages, évaluer les contextes sensibles, épingler les références, limiter les permissions et isoler les secrets constituent une base solide et immédiatement applicable.

Le catalogue d’actions validées vient ensuite rendre cette base agréable à utiliser. Il offre aux équipes des solutions rapides, documentées et maintenues, sans les enfermer dans une gouvernance lourde. Commencez par les workflows qui publient, déploient ou manipulent des secrets : ce sont eux qui offrent le meilleur retour sur effort. Puis automatisez les contrôles au fil des corrections, pour que le durcissement devienne une propriété normale de vos pipelines plutôt qu’une opération ponctuelle.