Aller au contenu
CI/CD & Automatisation medium

Attaques supply chain sur GitHub Actions : cas réels et protections

45 min de lecture

Ce guide vous permet de comprendre et de bloquer les attaques supply chain qui ont compromis des dizaines de milliers de dépôts en 2025-2026 via GitHub Actions. Vous apprendrez à reconnaître les vecteurs d'exploitation, à auditer vos workflows existants, et à déployer une défense en profondeur. Prérequis : connaître les bases de GitHub Actions et avoir lu le guide Sécurité GitHub Actions.

  • Reconnaître les 5 vecteurs d'attaque exploités contre GitHub Actions
  • Décortiquer les attaques réelles 2025-2026 : tj-actions, GhostAction, Shai Hulud v2, hackerbot-claw
  • Auditer un dépôt existant à la recherche de configurations vulnérables
  • Corriger chaque vecteur : pull_request_target, tags mutables, permissions, injection, exfiltration
  • Déployer une défense en profondeur sur cinq couches complémentaires

Un workflow GitHub Actions mal configuré donne à un attaquant un accès privilégié à toute votre chaîne de livraison. Ce n'est pas théorique : en 2025, des attaques réelles ont exploité ces failles pour voler des secrets, injecter du code malveillant et compromettre des packages distribués à des milliers d'utilisateurs.

Voici ce qu'un attaquant peut obtenir en compromettant un seul workflow :

RessourceImpactCas réel
Secrets (tokens, credentials)Vol et réutilisation pour accéder à vos systèmes cloud, registres, APIsGhostAction : 3 325 secrets volés (AWS, PyPI, npm)
Code sourceInjection de backdoors invisibles dans le codeShai Hulud v2 : ver qui se réplique dans les paquets publiés
Artefacts (images, binaires)Distribution de malware à vos utilisateursNx s1ngularity : packages npm infectés
Registres (npm, Docker Hub)Publication de versions compromises sous votre nom1 092 versions npm infectées par Shai Hulud v2
ProductionDéploiement de code malveillantAccès cloud via tokens volés

L'action la plus populaire pour détecter les fichiers modifiés (23 000+ dépôts). Un attaquant a obtenu le PAT (Personal Access Token) d'un bot disposant d'un accès en écriture au dépôt, très probablement à la faveur de la compromission d'une autre action, reviewdog/action-setup (CVE-2025-30154). Il a ensuite réécrit les tags existants, de v1 à v45.0.7, pour les faire pointer vers un commit malveillant qui exfiltrait tous les secrets dans les logs publics du workflow. L'incident est suivi sous la référence CVE-2025-30066.

Vecteurs exploités :

  • Tags Git mutables (les utilisateurs faisaient confiance à @v1)
  • Pas d'épinglage par SHA
  • Secrets visibles dans les logs de workflow

Leçon : un tag @v1 n'est qu'une étiquette mobile. Seul un SHA garantit que le code exécuté est celui que vous avez validé.

327 comptes compromis, 817 dépôts infiltrés, 3 325 secrets volés. Le mode opératoire ne repose sur aucune faille de workflow : les attaquants ont d'abord pris le contrôle de comptes de mainteneurs, puis lu les workflows légitimes pour inventorier les noms des secrets disponibles dans chaque dépôt. Ils ont ensuite commité un workflow malveillant maquillé en « Github Actions Security », qui recopiait ces secrets nommément et les envoyait par requête HTTP POST vers un point de collecte. Les identifiants volés couvraient des clés AWS, des tokens PyPI, npm, DockerHub et GitHub.

Vecteurs exploités :

  • Compromission de compte de mainteneur (pas de second facteur robuste)
  • Absence de protection de branche sur .github/workflows/
  • Aucune revue obligatoire sur l'ajout d'un fichier de workflow
  • Pas de contrôle des connexions réseau sortantes du runner

Un ver auto-réplicant à grande échelle sur l'écosystème npm. L'analyse de Datadog recense 796 paquets npm uniques porteurs du ver, soit 1 092 versions publiées, et plus de 14 000 dépôts GitHub créés pour l'exfiltration ; d'autres décomptes montent au-delà de 25 000 dépôts. La propagation ne passe pas par les workflows : le ver injecte deux fichiers (setup_bun.js et bun_environment.js) déclenchés par un script preinstall, moissonne les identifiants présents sur la machine, puis utilise les tokens npm volés pour republier automatiquement d'autres paquets piégés avec npm publish.

Le lien avec GitHub Actions est réel mais indirect. Sur les machines compromises, le ver installe un runner self-hosted et y déploie une action volontairement vulnérable : l'attaquant reprend ensuite la main en ouvrant une GitHub Discussion contenant les commandes à exécuter, ce qui lui donne un canal de commande et contrôle sans serveur externe. Le ver injecte également un workflow shai-hulud-workflow.yml qui exfiltre les secrets à chaque push.

Vecteurs exploités :

  • Scripts preinstall npm exécutés sans bac à sable à l'installation
  • Tokens npm et PAT GitHub longue durée stockés en clair sur les postes
  • Enregistrement d'un runner self-hosted non maîtrisé
  • Absence de contrôle réseau (exfiltration libre)

Un bot IA autonome qui a ciblé 7 dépôts majeurs de l'écosystème open-source entre le 20 février et le 2 mars 2026. Le bot identifiait automatiquement les workflows vulnérables, créait des pull requests malveillantes, et exploitait selon les cibles soit pull_request_target avec checkout du fork, soit une injection de commande via le nom de branche ou un nom de fichier. Chaque exploitation livrait le même payload distant.

Sur Trivy, l'attaque a conduit au vol du PAT d'un mainteneur, puis au vidage complet du dépôt GitHub et à la suppression de toutes les releases de la v0.27.0 à la v0.69.1 dans la nuit du 1er mars 2026.

Vecteurs exploités :

  • pull_request_target sans garde-fous
  • GITHUB_TOKEN avec permissions d'écriture
  • Workflow qui exécutait du code provenant du fork
  • Interpolation ${{ }} de noms de branche et de fichiers dans un bloc run:
  • Absence de contrôle author_association sur les commandes en commentaire

Pour le récit complet de l'incident, voir le billet Trivy vidé après une attaque supply chain par un bot IA.

Ces quatre incidents n'ont pas le même point d'entrée : deux passent par un workflow vulnérable, deux par un identifiant volé. Mais tous franchissent ensuite les mêmes portes, et ce sont ces 5 vecteurs que vous pouvez fermer dans vos propres dépôts. Les corriger ne rend pas invulnérable à un vol de PAT, mais supprime la quasi-totalité de la surface exploitable depuis l'extérieur et limite fortement ce qu'un identifiant volé permet d'obtenir.

C'est le vecteur le plus dévastateur. L'événement pull_request_target a été conçu pour permettre aux mainteneurs de réagir aux PRs de forks avec accès aux secrets (par exemple, pour labelliser). Le problème : il exécute le workflow dans le contexte du dépôt cible, avec ses secrets et un GITHUB_TOKEN potentiellement en écriture.

ÉvénementCode exécutéAccès aux secretsRisque
pull_requestCode du forkNon (forks)Faible
pull_request_targetCode de la branche cibleOuiCritique si checkout du fork

Le scénario d'exploitation :

  1. L'attaquant fork le dépôt cible

  2. Il modifie un script (build, test, linter) pour injecter du code malveillant

  3. Il ouvre une PR, ce qui déclenche pull_request_target

  4. Le workflow checkout le code de la PR (ref: ${{ github.event.pull_request.head.sha }}) et l'exécute avec les privilèges du dépôt cible

  5. Le code malveillant exfiltre les secrets, modifie le code, ou se propage

Comment se protéger :

# ❌ DANGEREUX : checkout du code du fork avec accès aux secrets
on:
pull_request_target:
jobs:
test:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.pull_request.head.sha }}
- run: npm test # Exécute le code du fork avec les secrets du repo

La version sûre bascule sur pull_request : les forks n'ont alors aucun accès aux secrets.

# ✅ SÉCURISÉ : utiliser pull_request (pas d'accès aux secrets pour les forks)
on:
pull_request:
types: [opened, synchronize]
jobs:
test:
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- run: npm test

Si vous avez absolument besoin de pull_request_target (rare), appliquez ces garde-fous :

  • Ne faites jamais de checkout du code de la PR
  • Ajoutez une condition if restrictive (label spécifique, auteur autorisé)
  • Limitez les permissions au strict minimum

Guide détaillé : Sécuriser pull_request_target.

Vecteur 2, Tags mutables et supply chain des actions

Section intitulée « Vecteur 2, Tags mutables et supply chain des actions »

Quand vous écrivez uses: some/action@v1, GitHub résout le tag vers un commit. Mais un tag est une étiquette mobile : le mainteneur (ou un attaquant) peut le déplacer vers n'importe quel commit à tout moment.

C'est le mécanisme exact exploité par tj-actions/changed-files : les tags @v1, @v2, etc. ont été réécrits pour pointer vers du code qui exfiltrait les secrets.

Comment se protéger :

# ❌ Tag mutable : le contenu peut changer sans préavis
- uses: actions/checkout@v4
# ❌ Version précise, mais le tag reste mutable
- uses: actions/checkout@v4.2.2
# ✅ SHA complet : immuable et vérifiable
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
  1. Convertir tous vos tags en SHA

    Utilisez StepSecurity (interface web) ou l'outil CLI :

    Fenêtre de terminal
    npm install -g pin-github-action
    pin-github-action .github/workflows/ci.yml
  2. Activer Dependabot pour les mises à jour automatiques

    .github/dependabot.yml
    version: 2
    updates:
    - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
    interval: "weekly"
  3. Auditer avec Scorecard

    Fenêtre de terminal
    scorecard --local . --checks Pinned-Dependencies --show-details

Guide détaillé : Épingler les actions par SHA.

Le GITHUB_TOKEN est généré automatiquement pour chaque workflow. Avec des permissions trop larges, un workflow compromis peut modifier le code source, créer des releases, publier des packages, et persister dans le dépôt.

C'est un accélérateur de toutes les autres attaques. Comme le souligne l'analyse d'Arctiq, combiné avec pull_request_target, c'est une "combinaison explosive" utilisée par Shai Hulud v2 et GhostAction.

Comment se protéger :

# Permissions zéro par défaut au niveau workflow
permissions: {}
jobs:
test:
runs-on: ubuntu-24.04
permissions:
contents: read # Seulement ce qui est nécessaire
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- run: npm test
publish:
needs: test
runs-on: ubuntu-24.04
permissions:
contents: read
packages: write # Write uniquement pour le job qui publie
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- run: npm publish

Guide détaillé : Permissions GitHub Actions.

L'interpolation ${{ }} dans un bloc run: se fait avant l'exécution du shell, sans échappement. Un attaquant qui contrôle un titre de PR, un nom de branche ou un corps d'issue peut injecter du code arbitraire.

La faille CVE-2025-53104 de gluestack/gluestack-ui, publiée en juin 2025, illustre exactement ce vecteur : le workflow discussion-to-slack.yml interpolait le titre et le corps d'une GitHub Discussion dans un bloc run:, ce qui permettait à n'importe qui d'exécuter du code sur le runner en ouvrant une discussion contenant $(...). Elle a été découverte en marge d'un incident distinct, la compromission des paquets npm du projet, qui provenait elle du vol du PAT d'un développeur. Le workflow vulnérable a été supprimé.

Données dangereuses (ne jamais interpoler directement) :

SourceVariable
Titre de PRgithub.event.pull_request.title
Corps de PRgithub.event.pull_request.body
Titre d'issuegithub.event.issue.title
Commentairegithub.event.comment.body
Nom de branchegithub.event.pull_request.head.ref
Message de commitgithub.event.head_commit.message
Input dispatchgithub.event.inputs.*

Comment se protéger :

# ❌ Injection possible
- run: echo "PR: ${{ github.event.pull_request.title }}"
# ✅ Variable d'environnement : le shell traite la valeur comme une chaîne
- run: echo "PR: $PR_TITLE"
env:
PR_TITLE: ${{ github.event.pull_request.title }}

Tous les vecteurs précédents ont besoin d'un canal de sortie pour envoyer les secrets volés. Si vous bloquez les connexions réseau sortantes, même un workflow compromis ne peut pas exfiltrer vos données.

Comment se protéger :

steps:
# DOIT être la première étape du job
- uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit # D'abord observer, puis bloquer
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- run: npm ci && npm test
  1. Déployer en mode audit pour observer les connexions légitimes

  2. Analyser le tableau de bord StepSecurity (lien dans les logs du job)

  3. Passer en mode block avec la liste des endpoints autorisés

    - uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
    with:
    egress-policy: block
    allowed-endpoints: >
    github.com:443
    registry.npmjs.org:443
    objects.githubusercontent.com:443

Aucune mesure isolée ne suffit. La sécurité de vos workflows repose sur la combinaison de plusieurs couches de protection :

CoucheObjectifOutils et mesures
1, PréventionDétecter les problèmes avant le commitactionlint, zizmor, poutine, Semgrep
2, ConfigurationRéduire la surface d'attaquepermissions: {}, épinglage SHA, OIDC
3, DétectionSurveiller pendant l'exécutionHarden Runner, Gitleaks, Scorecard
4, ConfinementLimiter le rayon d'explosionegress: block, runners éphémères, environnements gatés
5, RéponseSe préparer au pireRotation des secrets, audit logs, SIEM

Couche 1, Prévention : détecter les problèmes avant le commit

Section intitulée « Couche 1, Prévention : détecter les problèmes avant le commit »

Cette couche est la moins coûteuse et celle qui rend le plus : elle refuse un workflow dangereux avant qu'il n'existe en production. Les quatre premiers outils du tableau ne se remplacent pas les uns les autres, ils ont des angles distincts. actionlint valide la syntaxe et les expressions, zizmor raisonne sur la sécurité du workflow lui-même, poutine couvre plusieurs plateformes de CI et repère les schémas propres aux pipelines, Semgrep porte vos règles maison. Enchaînez-les dans le même job, chacun échouant indépendamment, plutôt que d'en choisir un.

OutilCe qu'il détecte
actionlintErreurs de syntaxe, patterns dangereux
zizmorAudit de sécurité statique des workflows
poutineVulnérabilités spécifiques GitHub Actions
SemgrepRègles custom (ban pull_request_target, etc.)
CheckovMauvaises pratiques dans les workflows

Couche 2, Configuration : réduire la surface d'attaque

Section intitulée « Couche 2, Configuration : réduire la surface d'attaque »

Ce sont les mesures décrites dans les 5 vecteurs ci-dessus. Elles ne demandent aucun outil supplémentaire, seulement de la rigueur dans le YAML, et elles ont un effet cumulatif : chacune retire une capacité à un attaquant qui aurait déjà pris pied dans le pipeline. La plus rentable des quatre est OIDC, qui supprime la catégorie entière des identifiants cloud volables.

  • permissions: {} au niveau workflow
  • Épinglage SHA systématique
  • OIDC pour les déploiements cloud
  • Secrets scopés par step et environnement

Les deux couches précédentes supposent que vous avez pensé à tout. Celle-ci part du principe inverse et cherche les écarts au comportement attendu pendant que le job tourne. Elle a un coût de mise en route : chaque outil produit du bruit tant que la ligne de base n'est pas établie. Commencez par Harden Runner en mode observation, laissez tourner deux semaines, et vous disposerez de la liste des destinations réseau réellement utilisées par vos pipelines. C'est cette liste qui rend la couche 4 applicable.

  • Harden Runner : surveille les connexions réseau pendant l'exécution
  • Gitleaks / TruffleHog : scan continu des commits pour les secrets
  • Scorecard : audit périodique de la posture sécurité

Couche 4, Confinement : limiter le rayon d'explosion

Section intitulée « Couche 4, Confinement : limiter le rayon d'explosion »

Le confinement ne cherche pas à empêcher la compromission mais à la rendre stérile. Un attaquant qui exécute du code sur un runner sans sortie réseau autorisée et détruit après le job repart sans rien. Ces trois mesures se posent dans cet ordre : le blocage réseau d'abord, parce qu'il coupe l'exfiltration, source de valeur de la quasi-totalité des attaques recensées plus haut ; l'approbation manuelle en dernier, parce qu'elle coûte du temps humain à chaque déploiement.

  • egress: block : empêcher l'exfiltration réseau
  • Runners éphémères : pas de persistance entre les jobs
  • Environnements avec approbation : gate humain avant la production

Cette couche se juge à une question : combien de temps vous faut-il pour révoquer et remplacer tous les secrets d'un dépôt ? Si la réponse dépasse l'heure ou si personne ne la connaît, vous n'avez pas de plan de réponse. Les attaques de 2025-2026 montrent que la fenêtre utile est courte, l'exfiltration des secrets Trivy et leur exploitation ont tenu dans la même nuit. Écrivez la procédure de rotation, testez-la une fois, et gardez les journaux d'audit ailleurs que dans le dépôt compromis.

  • Rotation des secrets : procédure documentée et testée
  • Audit logs : centraliser dans un SIEM pour investigation
  • Dependabot : alertes automatiques sur les actions vulnérables

Si vous partez de zéro, voici par où commencer :

  1. Auditer vos workflows existants

    Fenêtre de terminal
    # Lister tous les workflows
    find .github/workflows -name '*.yml' -o -name '*.yaml'
    # Chercher les patterns dangereux
    grep -rn 'pull_request_target' .github/workflows/
    grep -rn 'permissions:.*write-all' .github/workflows/
    grep -rn '\${{.*github\.event\.' .github/workflows/
    grep -rn '@v[0-9]' .github/workflows/
    # Audit complet avec Scorecard
    scorecard --local . --format json | jq '.checks[] | {name, score}'
  2. Corriger les failles critiques en priorité

    • Supprimer tout pull_request_target avec checkout du fork
    • Ajouter permissions: {} à chaque workflow
    • Remplacer ${{ github.event.* }} dans les run: par des env:
  3. Épingler toutes les actions par SHA

    Fenêtre de terminal
    npm install -g pin-github-action
    find .github/workflows -name '*.yml' -exec pin-github-action {} \;
  4. Ajouter Harden Runner et Gitleaks

    Déployer Harden Runner en mode audit sur tous les jobs, et Gitleaks pour bloquer le merge si un secret est détecté.

  5. Mettre en place le suivi continu

    Activer Dependabot pour github-actions, planifier un audit Scorecard mensuel, et configurer les alertes dans votre SIEM.

Vérifiez que l'essentiel de ce guide 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

Les attaques supply chain sur GitHub Actions se ramènent à 5 vecteurs : pull_request_target mal sécurisé, tags mutables, permissions excessives, injection de commandes et exfiltration réseau non contrôlée. Ce sont les cinq portes que vous contrôlez dans vos dépôts.

Les incidents de 2025-2026 se répartissent en deux familles. tj-actions et hackerbot-claw partent d'une faiblesse du workflow et sont donc évitables par les cinq corrections ci-dessus. GhostAction et Shai Hulud v2 partent d'un identifiant volé, compte de mainteneur ou token npm : contre eux, les mesures utiles sont l'authentification forte, les identifiants éphémères (OIDC) et la protection des fichiers de workflow par revue obligatoire.

La bonne nouvelle : corriger ces 5 vecteurs ne nécessite pas d'outils sophistiqués. C'est une question de discipline, permissions minimales, SHA partout, inputs jamais interpolés, secrets isolés, réseau surveillé.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn