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.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- 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
Qu'est-ce que la confiance implicite ?
Section intitulée « Qu'est-ce que la confiance implicite ? »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 implicite | Confiance 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.
Trois incidents qui l'ont exploitée
Section intitulée « Trois incidents qui l'ont exploitée »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.
Un tag peut-il changer de contenu ?
Section intitulée « Un tag peut-il changer de contenu ? »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.
# Le mainteneur publie une action propre, étiquetée v1printf 'echo "build propre"\n' > action.shgit add action.sh && git commit -m "v1 propre" && git tag v1git push origin main v1
# Le compte du mainteneur est compromis : nouveau commit, tag déplacéprintf 'echo "build propre"\nenv | base64 # exfiltration\n' > action.shgit commit -am "maj" && git tag -f v1git 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écuteecho "build propre"env | base64 # exfiltration--- après : le SHA épinglé exécuteecho "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.
Le schéma se lit de gauche à droite, et chaque zone appelle sa propre question :
- Dépendances : chaque
npm installoupip installexécute du code tiers, parfois dès l'installation. Qui les maintient, et que se passe-t-il si son compte est volé ? - Actions et plugins : ils s'exécutent avec les permissions du job et lisent ses secrets. Une action compromise peut tout exfiltrer.
- Scripts téléchargés : un
curl | bashexécute ce que le serveur sert ce jour-là, sans version ni empreinte. - Cache : un cache empoisonné réinjecte du code malveillant à chaque build, même après correction de la source.
- CDN et services externes : le cas Polyfill.io, un domaine qui change de propriétaire et de contenu.
- Jetons et identités : un jeton volé donne accès à tout ce qu'il permet. S'il est large, l'attaquant l'est aussi.
- 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.
Inventorier les confiances d'un workflow
Section intitulée « Inventorier les confiances d'un workflow »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.
# ❌ Workflow de tutoriel : chaque ligne accorde une confiance implicitename: 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 testzizmor --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 zizmor | Ligne visée | Confiance implicite |
|---|---|---|
unpinned-uses (x3) | checkout@v4, setup-node@v4, changed-files@v45 | Le code de l'action peut changer sous le même tag |
dangerous-triggers | pull_request_target | Le workflow tourne avec les secrets sur du code de PR |
template-injection | ${{ github.event.pull_request.title }} dans run | Le titre de la PR devient du shell |
excessive-permissions | le job build | Le jeton par défaut garde des droits larges |
artipacked | actions/checkout | Le 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.
Pourquoi la confiance aveugle persiste-t-elle ?
Section intitulée « Pourquoi la confiance aveugle persiste-t-elle ? »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.
| Biais | Ce qu'on se dit | Ce 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.
- Identification : l'attaquant repère un composant très utilisé mais peu surveillé, un mainteneur seul, un projet utilitaire.
- Infiltration : il gagne la confiance par des contributions utiles, ou compromet le compte d'un mainteneur existant.
- Injection : le code malveillant est noyé dans un gros changement, obfusqué, ou conditionnel, par exemple actif uniquement en CI.
- Propagation : les pipelines téléchargent automatiquement la nouvelle version, et accélèrent la diffusion.
- 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.
Les cinq principes de la confiance explicite
Section intitulée « Les cinq principes de la confiance explicite »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.1Surveiller 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.
À retenir
Section intitulée « À retenir »- 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
@v1a 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 | bashni le secret exposé à tout le job. - La confiance explicite repose sur cinq principes : vérifier, minimiser les permissions, épingler, surveiller, documenter.
Contrôle de connaissances
Section intitulée « Contrôle de connaissances »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
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
Vérification
(0/0)Score par domaine
Un domaine regroupe plusieurs guides et reçoit plusieurs questions : son score est plus fiable qu'un score par thème, qui porte souvent sur une seule question.
Bilan
Points forts
Aucun sujet réussi à 80 % sur au moins deux questions pour l’instant.
Points faibles
Aucun sujet sous le seuil de réussite.
À faire ensuite
Des indices pour retenter votre chance ?
Continuer le cours : Bloquer une pipeline n'améliore pas la sécurité : gates et garde-fous
Continuer le cours : Sécuriser une pipeline CI/CD : les quatre surfaces de contrôle
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications