Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English
Read in English

La confiance implicite dans les pipelines CI/CD

Par mis à jour le

Améliorer cette page

Un pipeline CI/CD exécute du code qu'il n'a pas écrit : des actions tierces, des dépendances, des scripts téléchargés. Chacun de ces éléments est une décision de confiance, et la plupart sont prises sans que personne ne les ait décidées. Cette leçon, qui ouvre le module sur les pipelines, montre ce que coûte cette confiance implicite et comment la rendre explicite. Deux mesures la portent : un tag Git déplacé après coup, qui fait exécuter un autre code au même @v1, et un workflow de tutoriel où zizmor relève 7 constats sans signaler le curl | bash qu'il contient.

  • Définir la confiance implicite et la distinguer de la confiance explicite
  • Situer les sept zones de confiance d'un pipeline
  • Démontrer qu'un tag ne garantit pas le code exécuté, contrairement à un SHA
  • Inventorier les confiances d'un workflow avec zizmor, et ses angles morts
  • Appliquer les cinq principes de la confiance explicite

La confiance implicite, c'est accorder une autorisation sans l'avoir consciemment décidée. Dans un pipeline, elle prend la forme d'une action tierce utilisée sans lire son code, d'une dépendance téléchargée sans vérifier son intégrité, d'un script récupéré sur Internet et exécuté tel quel, ou d'un secret accessible à toutes les étapes d'un job alors qu'une seule en a besoin.

Ce qui la rend dangereuse est son invisibilité. Personne n'a pris la décision de faire confiance à ce composant : il est là, hérité d'un tutoriel, copié d'un autre projet, ajouté « parce que ça marchait ». La confiance explicite fait la même chose, utiliser un composant externe, mais en connaissant le risque accepté et la personne qui l'a accepté.

Confiance impliciteConfiance explicite
« On a toujours utilisé cette action »« On a lu cette action et accepté ses risques »
« Ça vient d'un mainteneur connu »« On vérifie la signature de chaque version »
« C'est populaire, donc c'est sûr »« On épingle le SHA et on surveille les changements »
« Le script est sur notre dépôt interne »« Le script est revu, et sa provenance vérifiable »

Une question suffit à repérer la confiance implicite : pour chaque composant du pipeline, qui a décidé de lui faire confiance, et sur quels critères ? Si la réponse est « personne » ou « je ne sais pas », la décision existe quand même, mais personne n'en répond.

Les trois attaques ci-dessous n'ont exploité aucune faille de code au sens classique. Elles ont exploité la confiance accordée à un composant externe, et chacune touche une zone différente du pipeline.

XZ Utils, 2024. Un contributeur a gagné pendant des années la confiance du mainteneur de cette bibliothèque de compression, avant d'introduire une porte dérobée dans les versions 5.6.0 et 5.6.1, publiées dans la CVE CVE-2024-3094 le 29 mars 2024. Le code malveillant se trouvait dans les archives de version, pas dans le dépôt Git : relire le dépôt ne suffisait pas. Elle a été repérée avant une large diffusion, et seules des distributions de développement l'ont reçue, Fedora Rawhide et une partie de Fedora 40 ; aucune version de RHEL n'était touchée.

Polyfill.io, 2024. Le domaine cdn.polyfill.io, embarqué par plus de 100 000 sites selon Sansec, a été racheté en février 2024 avec son compte GitHub. En juin, le service servait du code qui redirigeait certains visiteurs vers des sites malveillants. Les sites concernés n'avaient rien modifié : ils chargeaient simplement un script depuis un domaine tiers.

tj-actions/changed-files, 2025. Entre le 14 et le 15 mars 2025, un attaquant a déplacé les tags de version de cette action GitHub, utilisée par plus de 23 000 dépôts selon l'avis de sécurité GHSA-mrrh-fwg8-r2c3 (CVE-2025-30066), vers un commit malveillant. Le code ajouté lisait la mémoire du runner et imprimait les secrets dans les journaux du workflow, lisibles par tous sur un dépôt public. Les workflows épinglés par SHA de commit n'ont pas été touchés.

Oui, et c'est le mécanisme exact de l'attaque tj-actions. Un tag Git est une étiquette posée sur un commit, que le propriétaire du dépôt peut déplacer. Un SHA de commit, lui, est l'empreinte du contenu : il ne peut pas désigner autre chose. La démonstration se rejoue sur un poste, avec un dépôt local qui joue le rôle de l'action.

Fenêtre de terminal
# Le mainteneur publie une action propre, étiquetée v1
printf 'echo "build propre"\n' > action.sh
git add action.sh && git commit -m "v1 propre" && git tag v1
git push origin main v1
# Le compte du mainteneur est compromis : nouveau commit, tag déplacé
printf 'echo "build propre"\nenv | base64 # exfiltration\n' > action.sh
git commit -am "maj" && git tag -f v1
git push origin v1 # refusé : ! [rejected] v1 -> v1 (already exists)
git push -f origin v1 # accepté

Mesuré : sans -f, Git refuse de déplacer un tag déjà publié, et le garde-fou s'arrête là, car un compte compromis dispose du push -f. Deux consommateurs de l'action récupèrent ensuite leur version. Celui qui suit le tag v1 exécute désormais la ligne d'exfiltration ; celui qui a épinglé le SHA du commit d'origine exécute toujours echo "build propre", et lui seul.

--- après : le tag v1 exécute
echo "build propre"
env | base64 # exfiltration
--- après : le SHA épinglé exécute
echo "build propre"

Rien, côté consommateur, ne signale le changement : le workflow référence toujours @v1, le run passe au vert, et seuls les journaux révèlent ce qui s'est exécuté. C'est pourquoi la règle d'épinglage vise le SHA du commit, jamais un tag, même « stable ».

Quelles sont les zones de confiance d'un pipeline ?

Section intitulée « Quelles sont les zones de confiance d'un pipeline ? »

Un pipeline réunit, dans un même job, votre code, ses secrets et une série de composants externes. Chacun peut modifier ce que le job exécute, et donc ce qu'il livre : un seul maillon compromis suffit à produire un artefact compromis, signé par votre propre chaîne.

Zones de confiance d'un pipeline : les dépendances npm, PyPI et Go, les actions et plugins tiers et les scripts téléchargés par curl | bash alimentent directement le job CI, qui détient le code et les secrets ; le cache de build et les CDN ou services externes l'alimentent indirectement ; le job produit les artefacts livrés et détient les jetons et identités, la cible la plus recherchée.Dépendancesnpm, PyPI, GoActions etplugins tiersScripts téléchargéscurl | bashCachede buildCDN etservices externesJob CI :code + secretsJetons etidentitésArtefactslivrés
Source · SVG
Échap pour fermer, + et − pour zoomer, défilement pour se déplacer. 100 %

Le schéma se lit de gauche à droite, et chaque zone appelle sa propre question :

  1. Dépendances : chaque npm install ou pip install exécute du code tiers, parfois dès l'installation. Qui les maintient, et que se passe-t-il si son compte est volé ?
  2. Actions et plugins : ils s'exécutent avec les permissions du job et lisent ses secrets. Une action compromise peut tout exfiltrer.
  3. Scripts téléchargés : un curl | bash exécute ce que le serveur sert ce jour-là, sans version ni empreinte.
  4. Cache : un cache empoisonné réinjecte du code malveillant à chaque build, même après correction de la source.
  5. CDN et services externes : le cas Polyfill.io, un domaine qui change de propriétaire et de contenu.
  6. Jetons et identités : un jeton volé donne accès à tout ce qu'il permet. S'il est large, l'attaquant l'est aussi.
  7. Artefacts : si le job est compromis, ce qu'il produit l'est aussi, et sans signature on ne distingue pas l'artefact piégé du légitime.

Repérer à l'œil chaque décision de confiance d'un workflow est fastidieux. zizmor, un analyseur statique de workflows GitHub Actions, en automatise une bonne partie. Le workflow ci-dessous assemble des lignes courantes dans les tutoriels : chacune accorde une confiance que personne n'a décidée.

.github/workflows/ci.yml
# ❌ Workflow de tutoriel : chaque ligne accorde une confiance implicite
name: CI
on:
push:
pull_request_target:
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- uses: tj-actions/changed-files@v45
id: changed
- run: curl -sSL https://example.com/install.sh | bash
- run: echo "Titre de la PR ${{ github.event.pull_request.title }}"
- run: npm install
- run: npm test
Fenêtre de terminal
zizmor --offline .

Mesuré avec zizmor 1.30.0 : le scan sort en code 14 et annonce 10 findings (3 suppressed), soit 7 constats affichés, 5 de sévérité haute et 2 moyens.

Constat zizmorLigne viséeConfiance implicite
unpinned-uses (x3)checkout@v4, setup-node@v4, changed-files@v45Le code de l'action peut changer sous le même tag
dangerous-triggerspull_request_targetLe workflow tourne avec les secrets sur du code de PR
template-injection${{ github.event.pull_request.title }} dans runLe titre de la PR devient du shell
excessive-permissionsle job buildLe jeton par défaut garde des droits larges
artipackedactions/checkoutLe jeton Git reste dans .git/config

Le tableau dit autant par ce qu'il omet. Deux confiances implicites du workflow n'apparaissent dans aucun constat : le curl | bash, qui exécute un script non versionné, et le NPM_TOKEN déclaré au niveau du workflow, donc exposé à toutes les étapes, y compris aux trois actions tierces. Un scanner réduit la relecture, il ne la remplace pas. La leçon dédiée à zizmor détaille ses audits et son intégration en CI.

Malgré les incidents répétés, la confiance implicite reste la norme, parce qu'elle s'appuie sur des biais que tout le monde partage. Les nommer aide à les reconnaître en revue de code, au moment où quelqu'un justifie un @v4 ou un curl | bash par l'habitude.

BiaisCe qu'on se ditCe qui se passe
Automatisation« C'est automatisé, donc fiable »Le pipeline exécute aveuglément, code malveillant compris
Familiarité« On a toujours fait comme ça »L'absence d'incident passé ne garantit rien
Illusion de contrôle« Le code est open source »Personne ne lit chaque dépendance
Popularité« Des milliers de projets l'utilisent »Plus c'est utilisé, plus l'impact est massif : 23 000 dépôts pour tj-actions
Pression du temps« On vérifiera après la livraison »La vérification n'a jamais lieu

Comment un attaquant exploite-t-il cette confiance ?

Section intitulée « Comment un attaquant exploite-t-il cette confiance ? »

Les attaques par la chaîne d'approvisionnement suivent un schéma constant, que l'affaire XZ Utils illustre de bout en bout. Elles sont patientes : la phase la plus longue n'est pas technique, c'est l'installation de la confiance.

  1. Identification : l'attaquant repère un composant très utilisé mais peu surveillé, un mainteneur seul, un projet utilitaire.
  2. Infiltration : il gagne la confiance par des contributions utiles, ou compromet le compte d'un mainteneur existant.
  3. Injection : le code malveillant est noyé dans un gros changement, obfusqué, ou conditionnel, par exemple actif uniquement en CI.
  4. Propagation : les pipelines téléchargent automatiquement la nouvelle version, et accélèrent la diffusion.
  5. Exploitation : exfiltration de secrets, porte dérobée dans les artefacts, accès persistant.

Les cibles idéales partagent quelques traits : un mainteneur unique ou épuisé, un projet utilitaire moins scruté qu'un framework majeur, des permissions élevées sur le système ou le réseau, et peu de revue sur les contributions extérieures.

Passer de la confiance implicite à la confiance explicite ne consiste pas à se passer de composants externes, mais à décider consciemment de chacun. Les cinq principes ci-dessous résument la démarche ; les leçons du module en outillent plusieurs, des permissions à l'épinglage.

Vérifier plutôt que supposer. Un composant externe se vérifie avant usage : sa signature (est-il signé par son auteur légitime ?), sa provenance (peut-on remonter à son code source ?) et son intégrité (a-t-il été modifié depuis sa publication ?).

Minimiser les permissions. Chaque étape n'accède qu'à ce dont elle a besoin, selon le principe du moindre privilège. Un job de test n'a pas besoin des secrets de production, et une action de formatage n'a pas besoin d'écrire dans le dépôt.

Épingler par SHA. Un tag comme @v4 accepte d'avance tout changement futur, la démonstration plus haut l'a montré. Le SHA du commit, avec la version en commentaire pour le lecteur, fixe ce qui s'exécute.

# ❌ Confiance implicite : le tag peut être déplacé
- uses: actions/checkout@v4
# Confiance explicite : le commit est figé, la version reste lisible
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

Surveiller les changements. Un audit initial ne suffit pas : on est alerté des nouvelles versions, on lit leurs changements avant de mettre à jour, et on surveille les comportements anormaux d'un job, comme une connexion réseau inattendue.

Documenter les décisions. Quand l'équipe adopte un composant, elle écrit pourquoi lui, quelles alternatives ont été écartées, quel risque est accepté et qui surveille. Une décision écrite se relit en revue ; une décision implicite ne se relit jamais.

  • Chaque action, dépendance ou script d'un pipeline est une décision de confiance, souvent prise par personne.
  • XZ Utils, Polyfill.io et tj-actions ont exploité la confiance, pas une faille de code.
  • Un tag se déplace : mesuré, le même @v1 a exécuté un autre code ; seul le SHA du commit est figé.
  • Un pipeline compte sept zones de confiance, des dépendances aux artefacts livrés.
  • zizmor relève 7 constats sur un workflow de tutoriel, mais ni le curl | bash ni le secret exposé à tout le job.
  • La confiance explicite repose sur cinq principes : vérifier, minimiser les permissions, épingler, surveiller, documenter.

Vérifiez que l'essentiel de cette leçon 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

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn