Aller au contenu
Sécuriser le pipeline

Sécuriser le pipeline

100 Comprendre ⏱ 1 h ci-cddockergithub-actionsgit

À la fin, vous saurez

  • Expliquer pourquoi un pipeline est une cible de choix, et ce qu'un attaquant y gagne
  • Nommer les principaux risques de sécurité d'un pipeline et les relier à une défense
  • Empêcher qu'un contributeur externe fasse exécuter du code privilégié ou vole un secret
  • Protéger les secrets : hors des branches, hors des images, masqués dans les journaux
  • Épingler les dépendances du pipeline par empreinte, et isoler les ressources partagées

Prérequis

Testé avec docker 29.8.1 git 2.43.0 registry 3 , vérifié le 1 octobre 2026

Pourquoi

Reprenons une phrase de la leçon 2 : notre serveur exécute n'importe quelle commande écrite dans .pipeline par quiconque peut pousser une branche. C'est vrai de toutes les plateformes de CI/CD, et c'est ce qui en fait une cible. Un pipeline, c'est :

  • un service d'exécution de code ouvert aux contributeurs, parfois à des inconnus (projets ouverts aux demandes de fusion externes) ;
  • qui détient des secrets (clés de registre, jetons de déploiement, identifiants cloud) ;
  • qui a accès à la production, directement (il déploie) ou indirectement (il produit l'artefact que la production exécute) ;
  • et dont les artefacts sont installés en confiance par d'autres.

Compromettre le pipeline, c'est donc souvent plus rentable que d'attaquer la production de front : on y trouve les clés, et tout ce qui en sort est réputé sûr. L'attaque de tj-actions/changed-files en mars 2025 l'a rappelé à grande échelle : une action GitHub utilisée par des dizaines de milliers de dépôts a été modifiée pour extraire les secrets présents en mémoire des agents et les écrire dans les journaux, publics pour les dépôts ouverts. Selon l'alerte de la CISA du 18 mars 2025, l'incident était lui-même la suite de la compromission d'une autre action, reviewdog/action-setup.

Cette leçon regarde notre pipeline avec les yeux d'un attaquant, risque par risque, et ajoute à notre serveur les défenses correspondantes. Le fil directeur est le référentiel OWASP Top 10 des risques CI/CD, qui classe ces risques et sert de liste de contrôle.

Les concepts

Les dix risques OWASP, en bref

Le projet OWASP CI/CD (Daniel Krivelevich, Omer Gil et d'autres) recense dix familles de risques. Les voici, avec une traduction et un exemple concret appliqué à notre contexte :

CodeRisqueExemple
CICD-SEC-1Contrôle de flux insuffisantPousser directement sur main sans revue ni pipeline vert
CICD-SEC-2Gestion des identités et des accès défaillanteUn jeton unique, tout-puissant, partagé par tous les pipelines
CICD-SEC-3Abus de la chaîne de dépendancesUne action ou une image tierce compromise (le cas tj-actions)
CICD-SEC-4Exécution de pipeline empoisonnée (PPE)Une demande de fusion modifie .pipeline pour y lire les secrets
CICD-SEC-5Contrôles d'accès du pipeline insuffisantsUne étape de test peut atteindre le registre de production
CICD-SEC-6Hygiène des secrets insuffisanteUn secret dans un journal, dans l'image, ou jamais renouvelé
CICD-SEC-7Configuration système non sécuriséeUn agent avec accès au socket Docker de l'hôte
CICD-SEC-8Usage non gouverné de services tiersUne intégration tierce avec un accès large et oublié
CICD-SEC-9Validation d'intégrité des artefacts insuffisanteDéployer une image par étiquette mobile, sans vérifier son origine
CICD-SEC-10Journalisation et visibilité insuffisantesAucune trace de qui a déclenché quoi, ni d'alerte

On ne les traite pas tous à fond ici ; les cours Chaîne d'approvisionnement logicielle et GitOps avec Argo CD, et le chapitre Sécuriser, approfondissent plusieurs d'entre eux. Cette leçon se concentre sur les plus courants et les plus directement liés à la mécanique du pipeline : CICD-SEC-4 (PPE), CICD-SEC-6 (secrets), CICD-SEC-3 (dépendances) et le principe transversal du moindre privilège (CICD-SEC-2 et 5).

La demande de fusion empoisonnée (PPE)

C'est le risque propre à la CI, celui que l'on ne voit pas venir. Puisque la définition du pipeline est dans le dépôt, et qu'une demande de fusion propose une modification du dépôt, une demande de fusion peut proposer une modification du pipeline. Si la plateforme exécute le pipeline de la demande de fusion avec les secrets du dépôt, un contributeur malveillant n'a qu'à soumettre une demande de fusion qui remplace l'étape de tests par une étape qui lit les secrets et les envoie ailleurs.

GitHub appelle cela une pwn request et distingue nettement deux déclencheurs :

  • pull_request : pour une demande venant d'un dépôt dupliqué (fork), le pipeline s'exécute sans les secrets du dépôt cible, et avec un jeton en lecture seule. C'est le réglage sûr par défaut.
  • pull_request_target : le pipeline s'exécute avec les secrets et le jeton du dépôt cible. Combiné à une récupération du code de la demande de fusion, il donne au contributeur l'accès aux secrets. À manier avec une extrême prudence, et jamais pour construire du code proposé.

La défense tient en une règle : le code non relu ne voit jamais les secrets. Les secrets n'arrivent qu'après la fusion, sur la branche protégée.

Le moindre privilège

Un pipeline cumule souvent des droits par commodité : un seul jeton qui peut tout lire, tout écrire, déployer partout, parce que c'était plus simple à configurer. Le jour d'une compromission, l'attaquant hérite de tout. Le moindre privilège inverse la logique : chaque étape ne reçoit que ce dont elle a besoin, et rien de plus.

  • Le jeton par défaut est en lecture seule ; une étape qui doit écrire demande explicitement ce droit (c'est le sens de permissions: dans GitHub Actions, en lecture seule par défaut depuis 2023).
  • La clé du registre ne peut qu'écrire dans un espace de noms précis, pas lire ni supprimer ailleurs.
  • Le droit de déployer en production est séparé du droit de construire.
  • Les droits sont de courte durée quand c'est possible : un jeton à durée de vie de quelques minutes vaut mieux qu'une clé permanente (voir la signature sans clé, cours Construire des images de conteneurs, leçon 9).

L'hygiène des secrets

Trois règles, déjà esquissées aux leçons précédentes :

  1. Hors du dépôt. Un secret versionné reste dans l'historique de tous les clones, pour toujours. Un secret poussé une fois doit être considéré comme compromis et renouvelé.
  2. Hors de l'artefact. Un secret copié dans une image part partout où l'image va, et se lit dans ses couches.
  3. Hors des journaux. Un secret affiché par mégarde (echo, trace de débogage, message d'erreur d'une bibliothèque) se retrouve dans des journaux souvent plus largement lisibles que le secret lui-même. Les plateformes masquent les valeurs de secrets connues dans la sortie, en les remplaçant par ***.

Le masquage est une protection de dernier recours, pas une permission d'être imprudent : il ne connaît que les valeurs exactes qu'on lui a déclarées, et ne rattrape pas un secret transformé (encodé en base64, découpé). La seule garantie solide reste de ne pas exposer le secret du tout.

La chaîne de dépendances du pipeline

Un pipeline ne contient pas que votre code : il tire des images de base, des actions, des paquets, des outils. Chacun est du code tiers, exécuté avec vos droits. Le cas tj-actions l'illustre : le dépôt visé n'avait aucune faille, c'est une dépendance de son pipeline qui a été retournée contre lui.

La défense centrale est l'épinglage par empreinte. Une référence mobile (uses: tj-actions/changed-files@v44, image: outil:latest) suit une étiquette que le mainteneur, ou un attaquant qui l'a compromis, peut déplacer. Une référence par empreinte (@sha256:... pour une image, @<empreinte de commit> pour une action) désigne un contenu immuable : si on le réécrit, l'empreinte ne correspond plus. On met ensuite à jour ces empreintes par des commits délibérés, qu'un outil comme Dependabot ou Renovate propose automatiquement.

En pratique

Attaque 1 : voler un secret par une demande de fusion

Mettons-nous à la place de l'attaquant. Notre serveur de la leçon 4 déploie depuis main, avec un jeton de déploiement. Ajoutons d'abord ce secret, comme le ferait une vraie équipe, sur le serveur, hors du dépôt :

$ printf 'JETON_DEPLOIEMENT=glpat-3ximR7K9vQ2nL8pZ\n' > serveur-ci/secrets.env
$ chmod 600 serveur-ci/secrets.env

Tel quel, notre serveur de la leçon 4 ne gère pas les secrets. Pour le rendre réaliste, et sûr, nous allons lui en ajouter la gestion avec la bonne propriété dès le départ : les secrets ne sont chargés que pour main. Au début d'executer, après registre=... :

# Secrets : disponibles pour les étapes, mais seulement sur main, et masqués
# dans les journaux. Une branche (donc une demande de fusion) n'y a pas accès.
secrets=()        # paires NOM=valeur passées aux étapes
valeurs=()        # valeurs seules, à masquer dans les journaux
if [ "$ref" = refs/heads/main ] && [ -f "$base/secrets.env" ]; then
  while IFS= read -r ligne; do
    case "$ligne" in ''|'#'*) continue ;; esac
    secrets+=(-e "$ligne")
    valeurs+=("${ligne#*=}")
  done < "$base/secrets.env"
fi
# Remplace chaque valeur de secret par *** dans ce qu'on lui passe sur l'entrée
masquer() {
  local filtre='cat'
  for valeur in "${valeurs[@]}"; do
    [ -n "$valeur" ] && filtre="$filtre | sed \"s|$valeur|***|g\""
  done
  eval "$filtre"
}

La condition [ "$ref" = refs/heads/main ] est tout le mécanisme de défense contre la PPE : sur une branche, le tableau secrets reste vide. Modifions l'étape ordinaire pour passer les secrets et masquer le journal :

      docker run --rm --user "$(id -u):$(id -g)" -e HOME=/tmp \
           -e PIP_CACHE_DIR=/cache/pip -e PIP_DISABLE_PIP_VERSION_CHECK=1 \
           --network "$reseau" -v "$base/cache":/cache -v "$travail/src":/src -w /src \
           "${secrets[@]}" "$image" sh -c "$commande" > "$travail/brut" 2>&1 < /dev/null
      code=$?
      masquer < "$travail/brut" > "$travail/journaux/$etape.log"; rm -f "$travail/brut"
      if [ "$code" -eq 0 ]
      then

Maintenant, jouons l'attaque. Ajoutons au pipeline deux étapes : une qui vérifie que le jeton est présent, une qui le divulgue (ici dans le journal ; dans une vraie attaque, elle l'enverrait sur Internet) :

verifie-jeton debian:13-slim      test -n "$JETON_DEPLOIEMENT" && echo "jeton de longueur ${#JETON_DEPLOIEMENT} présent" || { echo "aucun jeton"; exit 1; }
divulgue      debian:13-slim      echo "appel API avec le jeton $JETON_DEPLOIEMENT (erreur de journalisation)"

Poussée sur main (le cas d'une erreur de journalisation par un développeur de confiance), elle montre que le masquage fonctionne :

$ git push -q ci main
remote:   OK     verifie-jeton (0 s)
remote:   OK     divulgue (1 s)
remote:   OK     deployer recette (7 s)
$ cat serveur-ci/travaux/20/journaux/divulgue.log
appel API avec le jeton *** (erreur de journalisation)

Le jeton n'apparaît pas dans le journal : le masquage l'a remplacé par ***. Mais la vraie défense se voit quand la même étape arrive par une branche, c'est-à-dire par une demande de fusion :

$ git switch -c demande-externe
$ git commit --allow-empty -m "Changement proposé par un contributeur externe"
$ git push -q ci demande-externe
remote:   ÉCHEC  verifie-jeton (0 s), fin du journal :
remote: résultat : echec (journaux dans /home/.../serveur-ci/travaux/21/journaux)
$ cat serveur-ci/travaux/21/journaux/verifie-jeton.log
aucun jeton

Sur une branche, JETON_DEPLOIEMENT n'existe pas : l'étape qui l'exige échoue, et l'étape de divulgation, si elle était atteinte, n'aurait rien à divulguer. Le contributeur peut faire exécuter son code (dans un conteneur jetable, sans secret, sur un réseau privé), mais il ne peut pas voler le jeton. C'est la différence exacte entre pull_request et pull_request_target chez GitHub.

Attaque 2 : retourner une dépendance

L'attaquant ne peut pas entrer par la demande de fusion ; il vise une dépendance. Supposons une « action » distribuée sous forme d'image, utilisée par le pipeline sous l'étiquette v1. Montrons, avec notre registre local, ce qui se passe quand l'étiquette est réécrite, selon qu'on l'utilise par étiquette ou par empreinte.

On publie la version saine, et on note son empreinte :

$ printf 'FROM debian:13-slim\nRUN echo "version 1, saine" > /action.txt\n' \
    | docker build -q -t localhost:5000/action:v1 -f - .
$ docker push -q localhost:5000/action:v1
$ docker inspect --format '{{index .RepoDigests 0}}' localhost:5000/action:v1
localhost:5000/action@sha256:ac917a96a731f1e89ddf53f7ef3d290dcc43386b63966cf42b71e84a0b14410c

Puis l'attaquant, ayant compromis le mainteneur, réécrit l'étiquette v1 avec un contenu altéré :

$ printf 'FROM debian:13-slim\nRUN echo "version alteree" > /action.txt\n' \
    | docker build -q -t localhost:5000/action:v1 -f - .
$ docker push -q localhost:5000/action:v1

Le pipeline qui référence v1 par étiquette exécute désormais le code altéré ; celui qui la référence par empreinte continue d'exécuter la version saine :

$ docker run --rm localhost:5000/action:v1 cat /action.txt
version alteree
$ docker run --rm localhost:5000/action@sha256:ac917a96a731f1e89ddf53f7ef3d290dcc43386b63966cf42b71e84a0b14410c cat /action.txt
version 1, saine

C'est précisément ce qui s'est passé avec tj-actions : les dépôts qui épinglaient l'action par empreinte de commit n'ont pas exécuté le code malveillant, puisque l'empreinte qu'ils citaient n'avait pas changé ; ceux qui suivaient une étiquette (@v44, @latest) l'ont exécuté. La règle : épinglez les actions et les images de base par empreinte. Dans notre .pipeline, cela veut dire écrire python@sha256:... plutôt que python:3.14-slim, au prix d'une mise à jour explicite à chaque montée de version.

Isoler le cache partagé

Dernier point concret, lié à CICD-SEC-4. Notre cache de paquets est partagé par toutes les branches :

$ echo "contenu-depose-par-une-branche" > serveur-ci/cache/pip/marqueur.txt
$ docker run --rm -v "$PWD/serveur-ci/cache":/cache debian:13-slim cat /cache/pip/marqueur.txt
contenu-depose-par-une-branche

Une branche (donc une demande de fusion) peut donc déposer dans le cache un fichier que la prochaine construction de main réutilisera : un faux paquet, une couche piégée. La défense est l'isolement : un cache par branche (cache/main/, cache/<branche>/), de sorte qu'une branche ne puisse jamais écrire dans le cache de main. Les plateformes appliquent cette règle : GitHub n'autorise une demande de fusion à lire que les caches de sa propre branche et de la branche de base, et seules les exécutions sur la branche par défaut écrivent dans le cache partagé.

Contrôle de flux et traçabilité

Deux défenses déjà en place dans nos leçons, qu'il faut nommer :

  • Contrôle de flux (CICD-SEC-1). Le crochet pre-receive (exercice 3 de la leçon 2) permet de refuser une poussée sur main dont le pipeline échoue. Sur une forge, c'est la protection de branche : revue obligatoire, vérifications requises, pas de poussée directe. C'est ce qui empêche un commit non vérifié d'entrer dans la branche qui déploie.
  • Traçabilité (CICD-SEC-10). Le registre deploiements.log de la leçon 4 garde qui a déployé quoi, où, quand. La CNIL comme les cadres de sécurité imposent une journalisation des accès et des actions sensibles, conservée et exploitable. Un pipeline sans trace rend une compromission invisible et une enquête impossible.

Sous le capot

Pourquoi un conteneur sans secret ne peut pas « deviner » le secret. Dans notre serveur, les secrets sont passés par -e NOM=valeur à docker run. Un processus ne voit dans son environnement que ce qu'on lui transmet : sur une branche, secrets est vide, donc aucun -e de secret, donc JETON_DEPLOIEMENT est tout simplement absent de l'environnement de l'étape. Il n'y a rien à lire, pas même en mémoire, parce que le secret n'a jamais été chargé par le processus parent pour cette exécution.

Les limites du masquage par substitution. Notre masquer remplace la chaîne exacte du secret. Un journal qui contiendrait le secret encodé (base64), haché, ou découpé en morceaux passerait au travers. Les plateformes font mieux (elles masquent aussi certaines transformations courantes), mais aucune ne masque tout. C'est pourquoi le masquage est classé comme défense en profondeur, et non comme la défense : on empêche d'abord le secret d'arriver dans le journal.

Ce que prouve une empreinte, et ce qu'elle ne prouve pas. Épingler python@sha256:... garantit que l'on exécute toujours les mêmes octets. Cela ne garantit pas que ces octets soient sains : si l'on épingle une version déjà piégée, on exécute fidèlement le piège. L'épinglage protège contre la réécriture a posteriori d'une référence (le mode opératoire de tj-actions), pas contre une dépendance mauvaise dès le départ. Pour ce second risque, il faut une analyse (cours Construire des images de conteneurs, leçon 1) et une vérification de signature (leçon 9 du même cours).

Pièges courants

Croire qu'un dépôt privé est à l'abri. Un dépôt privé réduit l'exposition, il ne l'annule pas : la PPE vaut aussi entre collègues (un compte compromis, un stagiaire mal intentionné), et une dépendance tierce est tierce que le dépôt soit public ou non.

pull_request_target pour « faire tourner les tests des contributeurs ». C'est l'erreur de configuration la plus courante et la plus grave. Si vous devez utiliser pull_request_target (par exemple pour commenter une demande de fusion), ne récupérez jamais le code de la demande de fusion dans le même travail, et n'y exécutez aucun script issu de ce code.

Un secret masqué, donc réputé sûr. Voir plus haut : le masquage ne rattrape pas une transformation. Ne comptez pas dessus pour journaliser tranquillement à côté d'un secret.

Épingler l'action mais pas l'image de base. Un pipeline peut épingler scrupuleusement ses actions par empreinte et tirer FROM python:3.14-slim, une étiquette mobile. Les deux sont des dépendances ; épinglez les deux.

Donner au pipeline un accès permanent au cloud. Une clé d'API cloud à longue durée, stockée en secret, est un trophée. Préférez une fédération d'identité (OIDC) qui échange l'identité du pipeline contre un jeton de quelques minutes, sans secret à stocker.

Oublier de renouveler après une fuite. Un secret qui a pu être exposé (journal, poussée accidentelle, dépendance compromise) est compromis, même si « personne ne l'a sûrement vu ». La seule réponse est de le renouveler, comme l'ont fait les milliers de dépôts touchés par tj-actions.

Sécurité

Cette leçon est la partie sécurité du cours. Deux points de synthèse, tournés vers le contexte de Lyneko :

  • Le pipeline fait partie du périmètre à défendre. Chez un hébergeur de cloud souverain, les clés qui donnent accès aux ressources clients transitent par les pipelines. Un pipeline compromis est un incident de sécurité majeur, à traiter comme tel : moindre privilège, secrets de courte durée, agents jetables, traces conservées.
  • La chaîne d'approvisionnement est une obligation montante. Le Cyber Resilience Act européen et les référentiels de l'ANSSI poussent vers des exigences de traçabilité et d'intégrité de la chaîne de construction (SBOM, provenance, signatures). Le cadre SLSA structure ces exigences en niveaux. Le cours Chaîne d'approvisionnement logicielle y est consacré ; retenez ici que sécuriser le pipeline n'est plus seulement une bonne pratique, mais de plus en plus une exigence.

En production

  • Agents jetables et isolés. Une tâche par machine éphémère, détruite ensuite, sans accès au socket Docker de l'hôte (CICD-SEC-7), avec une sortie réseau restreinte : une étape compromise ne doit pas pouvoir joindre n'importe quel serveur sur Internet pour exfiltrer.
  • Fédération d'identité plutôt que clés stockées. OIDC entre le pipeline et le fournisseur cloud (Scaleway, et les grands fournisseurs) supprime les clés permanentes : le pipeline prouve son identité et reçoit un jeton court. C'est le même principe que la signature sans clé des images.
  • Analyse de la configuration du pipeline elle-même. Des outils (par exemple l'analyse statique des workflows, ou zizmor pour GitHub Actions) détectent les pull_request_target dangereux, les actions non épinglées, les permissions trop larges. Ils ont leur place... dans le pipeline.
  • Détection et réponse. Conservez les journaux d'exécution et de déploiement, alertez sur les anomalies (une étape qui contacte un domaine inconnu, un déploiement hors fenêtre), et ayez une procédure de renouvellement de secrets prête : lors de l'incident tj-actions, les équipes qui ont pu renouveler vite ont limité la casse.

Exercices

1. Classer un incident (niveau 100). Pour chaque situation, donnez le code OWASP CI/CD principal et une défense. (a) Un développeur colle une clé AWS dans un fichier .env versionné. (b) Une demande de fusion externe modifie le workflow pour afficher ${{ secrets.TOKEN }}. (c) Le pipeline tire FROM node:20 et se met un matin à miner de la cryptomonnaie après une mise à jour de l'image de base.

Solution

(a) CICD-SEC-6 (hygiène des secrets) : secret dans le dépôt. Défenses : ne jamais versionner de secret, le renouveler immédiatement (il est compromis), utiliser un gestionnaire de secrets et un détecteur de secrets dans le pipeline. (b) CICD-SEC-4 (PPE) : exécution de pipeline empoisonnée. Défense : ne pas donner les secrets aux exécutions de demandes de fusion externes (pull_request et non pull_request_target), ce que fait notre serveur en ne chargeant les secrets que pour main. (c) CICD-SEC-3 (abus de la chaîne de dépendances) : l'étiquette mobile node:20 a été déplacée vers une image piégée. Défense : épingler l'image de base par empreinte, et l'analyser.

2. Durcir le .pipeline (niveau 100). Reprenez le .pipeline de la leçon 4 et proposez une version épinglée par empreinte pour les images (python, postgres, debian). Comment obtenez-vous l'empreinte d'une image, et comment la met-on à jour par la suite ?

Solution

On récupère l'empreinte avec docker manifest inspect python:3.14-slim (champ digest du manifeste, ou de l'index multi-architecture), ou après un docker pull avec docker inspect --format '{{index .RepoDigests 0}}' python:3.14-slim. On remplace ensuite chaque image:etiquette par image@sha256:.... La mise à jour se fait par un commit délibéré, lorsqu'on décide de monter de version ou d'appliquer les correctifs de l'image de base ; un outil comme Renovate ou Dependabot sait proposer ces commits automatiquement, en lisant et en mettant à jour les empreintes. On garde l'étiquette en commentaire à côté de l'empreinte, pour la lisibilité.

3. Le secret transformé (niveau 100). Notre fonction masquer remplace la valeur exacte du secret. Un contributeur de confiance ajoute par erreur, sur main, une étape echo "$JETON_DEPLOIEMENT" | base64. Le jeton apparaît-il en clair dans le journal ? Que faut-il en conclure sur le masquage, et quelle est la bonne réaction ?

Solution

Le journal ne contient pas le jeton en clair, mais sa version encodée en base64, que masquer ne reconnaît pas (il ne cherche que la chaîne exacte). Un lecteur du journal peut la décoder d'un base64 -d et retrouver le jeton. Conclusion : le masquage est une défense en profondeur, pas une garantie ; il ne dispense pas de ne jamais afficher un secret, même transformé. La bonne réaction est double : retirer l'étape, et considérer le jeton comme compromis, donc le renouveler, puisqu'on ne sait pas qui a lu le journal entre-temps.

Récapitulatif

  • Un pipeline est une cible : il exécute du code à la demande de contributeurs, détient des secrets et a accès à la production. L'attaque tj-actions (2025) l'a montré à grande échelle.
  • Le référentiel OWASP Top 10 CI/CD sert de liste de contrôle ; les risques les plus directs sont la PPE (CICD-SEC-4), les secrets (CICD-SEC-6) et les dépendances (CICD-SEC-3).
  • Le code non relu ne voit jamais les secrets : notre serveur ne charge les secrets que pour main, donc une demande de fusion ne peut pas les voler.
  • Les secrets restent hors du dépôt, hors de l'artefact, hors des journaux (masquage en dernier recours, jamais une permission d'être imprudent).
  • On applique le moindre privilège (jetons en lecture par défaut, droits séparés, courte durée) et on épingle les dépendances par empreinte, en isolant les ressources partagées comme le cache.
  • Contrôle de flux (branche protégée) et traçabilité (journal des déploiements) complètent le dispositif.

Pour aller plus loin

  • Le projet OWASP Top 10 CI/CD Security Risks, pour le détail de chaque risque et ses mesures.
  • Le guide Security hardening for GitHub Actions de GitHub, et l'article Preventing pwn requests du GitHub Security Lab, à lire avant d'ouvrir un dépôt aux contributions.
  • L'analyse de l'incident tj-actions par Wiz, et l'alerte de la CISA, pour une étude de cas complète.
  • Le cours Chaîne d'approvisionnement logicielle de ce chapitre, et le cadre SLSA, pour la suite : SBOM, provenance, signatures et niveaux de maturité.
  • La leçon suivante, qui conclut le cours : mesurer ce que le pipeline apporte, avec les indicateurs DORA.
Voir ma constellation →

Sources