Cette page rassemble les points de contrôle à passer en revue avant de fusionner un workflow GitHub Actions. Elle s'adresse aux développeurs et aux équipes DevSecOps qui relisent du code de pipeline ou conduisent un audit de sécurité. Utilisez-la comme support de revue de code : chaque case cochée ferme une voie d'attaque connue — permissions trop larges, action non épinglée, secret exposé, injection de commandes. Les sections suivent l'ordre d'un workflow : permissions, actions tierces, secrets, déclencheurs, runners, puis les outils qui automatisent ces vérifications.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Auditer les permissions du
GITHUB_TOKENau niveau workflow et job - Vérifier l'épinglage des actions tierces par SHA de commit
- Contrôler l'usage des secrets et repérer les fuites dans les logs
- Détecter les déclencheurs risqués et les injections de commandes
- Durcir les runners self-hosted et les environnements de déploiement
- Automatiser ces contrôles avec Scorecard, Checkov et actionlint
Permissions
Section intitulée « Permissions »Le GITHUB_TOKEN est un jeton généré automatiquement pour chaque
exécution. Par défaut, son périmètre dépend des paramètres du dépôt —
souvent trop large. Réduire ces droits est la première barrière : un step
compromis ne peut écraser le code ou publier un package que si le jeton le lui
permet.
GITHUB_TOKEN
Section intitulée « GITHUB_TOKEN »Déclarez le bloc permissions: explicitement. Partez de permissions: {}
au niveau workflow — aucun droit — puis accordez à chaque job le strict
nécessaire. Évitez write-all, qui ouvre toutes les portées d'un coup.
- Bloc
permissions:déclaré explicitement au niveau workflow -
permissions: {}par défaut, droits accordés job par job - Aucun
permissions: write-all - Permissions
writeportées au niveau du job qui en a besoin, jamais du workflow
# Aucun droit par défaut, chaque job demande le minimumpermissions: {}
jobs: test: permissions: contents: read publish: permissions: contents: read packages: writeParamètres du repository
Section intitulée « Paramètres du repository »Les permissions par défaut se règlent aussi côté dépôt, dans les réglages
des Actions. Choisir le mode lecture seule garantit qu'un workflow qui
oublie son bloc permissions: hérite quand même d'un jeton restreint.
- Settings > Actions > General : "Read repository contents and packages permissions" sélectionné
- Les workflows déclenchés par des forks nécessitent une approbation manuelle
Actions tierces
Section intitulée « Actions tierces »Chaque uses: exécute du code écrit par quelqu'un d'autre, avec vos
secrets et votre jeton. La compromission de tj-actions/changed-files en 2025
l'a rappelé : une action populaire piégée contamine des milliers de
pipelines. Trois réflexes ferment ce vecteur : épingler, évaluer,
mettre à jour.
Épinglage
Section intitulée « Épinglage »Référencez chaque action par son SHA de commit complet (40 caractères),
pas par un tag comme @v4 qui peut être redéplacé sur un autre commit. Ajoutez
en commentaire la version lisible pour suivre les mises à jour. Le guide
Épingler par SHA détaille la
manœuvre.
- Toutes les actions épinglées par SHA (jamais
@v1,@latest,@main) - Commentaire de version après chaque SHA pour la lisibilité
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2Évaluation des actions
Section intitulée « Évaluation des actions »Avant d'ajouter une action du Marketplace, vérifiez son créateur, sa maintenance et sa popularité. Une action obscure ou abandonnée est un vecteur idéal. Pour les actions critiques, relisez le code source de la version que vous épinglez.
- Actions du Marketplace évaluées avant utilisation
- Actions officielles (
actions/*) ou éditeurs reconnus privilégiées - Code source relu pour les actions critiques
Mise à jour
Section intitulée « Mise à jour »Épingler fige une version — y compris ses failles. Dependabot ouvre
automatiquement des pull requests de mise à jour pour l'écosystème
github-actions, avec le nouveau SHA et le changelog. Reste à les relire
avant de fusionner.
- Dependabot configuré pour l'écosystème
github-actions - Pull requests de mise à jour relues régulièrement
version: 2updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly"Un secret qui fuit dans un log public est compromis pour de bon : il faut le révoquer. Trois angles à contrôler : où les stocker, comment les utiliser sans les exposer, et comment détecter ceux commités par erreur.
Stockage
Section intitulée « Stockage »Placez les secrets sensibles dans des Environments plutôt qu'au niveau dépôt : vous y ajoutez des règles de protection et limitez les branches autorisées. Pour les fournisseurs cloud, préférez OIDC aux credentials statiques longue durée.
- Secrets sensibles rangés dans des Environments, pas au niveau repo
- Aucun secret en clair dans les fichiers de workflow
- OIDC utilisé pour les cloud providers (pas de credentials statiques)
Utilisation
Section intitulée « Utilisation »Passez toujours un secret par un bloc env:, jamais directement dans
run: ni en argument de commande visible. Un echo d'un secret, même
involontaire, l'écrit en clair dans les logs d'exécution.
- Secrets passés via
env:, jamais interpolés dansrun: - Aucun
echo ${{ secrets.XXX }}ni affichage volontaire d'un secret
- name: Déclencher le déploiement run: | curl --fail --silent --show-error \ -X POST https://api.netlify.com/api/v1/sites/blog-stephane-robert/builds \ -H "Authorization: Bearer $NETLIFY_TOKEN" env: NETLIFY_TOKEN: ${{ secrets.NETLIFY_TOKEN }}Détection
Section intitulée « Détection »Un secret commité reste dans l'historique Git même après suppression du fichier. Un scanner comme Gitleaks dans le pipeline et le secret scanning GitHub repèrent ces fuites tôt, avant qu'elles n'atteignent une branche partagée.
- Gitleaks ou un scanner équivalent intégré au pipeline
- Secret scanning GitHub activé sur le dépôt
Déclencheurs et événements
Section intitulée « Déclencheurs et événements »Le déclencheur décide quel code s'exécute et avec quels droits. Certains
événements — notamment pull_request_target — mélangent du code non
fiable et des secrets : c'est là que se logent les failles les plus
graves.
pull_request_target
Section intitulée « pull_request_target »pull_request_target s'exécute avec les secrets du dépôt cible, même pour
une pull request issue d'un fork. Checkouter puis exécuter le code de la PR
revient à lancer le code d'un inconnu avec vos clés. Le guide
pull_request_target
détaille les contournements sûrs.
- Pas de
pull_request_targetsauf nécessité réelle - Si utilisé : jamais de checkout du code de la PR suivi de son exécution
# ❌ pull_request_target qui exécute le code d'un fork : RCE avec vos secretsname: PR Buildon: pull_request_target
jobs: build: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.pull_request.head.sha }} - run: npm ci && npm run build# ✅ Déclencheur pull_request : le code du fork tourne sans accès aux secretsname: PR Buildon: pull_request
permissions: {}
jobs: build: runs-on: ubuntu-24.04 timeout-minutes: 15 permissions: contents: read steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - run: npm ci && npm run buildworkflow_dispatch
Section intitulée « workflow_dispatch »Le déclenchement manuel workflow_dispatch accepte des inputs.
Restreignez-les avec type: choice (liste fermée) quand c'est possible, et
traitez-les comme des données non fiables : via env:, jamais interpolés
directement dans run:.
- Inputs en
type: choicequand le domaine de valeurs est connu - Inputs consommés via
env:, jamais interpolés dansrun: - Utilisateurs autorisés restreints si l'action est sensible
on: workflow_dispatch: inputs: environment: type: choice options: - staging - productionInjection de commandes
Section intitulée « Injection de commandes »L'injection de commandes est la faille la plus courante des workflows.
Interpoler avec ${{ }} une donnée contrôlable par un tiers — titre
d'issue, nom de branche, commentaire — dans un bloc run: permet d'exécuter
du code arbitraire sur le runner. La parade tient en une règle : passer la
donnée par une variable d'environnement.
# ❌ Vulnérable : le titre de l'issue est injecté dans le shell- run: echo "Issue: ${{ github.event.issue.title }}"
# ✅ Sécurisé : le titre passe par une variable d'environnement- run: echo "Issue: $ISSUE_TITLE" env: ISSUE_TITLE: ${{ github.event.issue.title }}Les contextes suivants sont alimentés par des données externes et ne
doivent jamais être interpolés dans un run: :
github.event.issue.titlegithub.event.issue.bodygithub.event.pull_request.titlegithub.event.pull_request.bodygithub.event.comment.bodygithub.head_refgithub.event.*.user.login
Le runner est la machine qui exécute vos jobs. Sur les runners GitHub-hosted, GitHub la recrée à neuf à chaque job. Sur un runner self-hosted, c'est votre infrastructure — et un job malveillant peut y persister d'une exécution à l'autre.
Self-hosted
Section intitulée « Self-hosted »Un runner self-hosted ne doit jamais servir un dépôt public ni traiter les PR de forks : ce serait offrir l'exécution de code arbitraire sur votre réseau. Privilégiez des runners éphémères, détruits après chaque job.
- Jamais branché sur un dépôt public
- Runners éphémères, détruits après chaque job
- Pools séparés par environnement (dev / staging / prod)
- Compte d'exécution sans privilèges sudo
- Logs centralisés et supervision en place
GitHub-hosted
Section intitulée « GitHub-hosted »Pour la majorité des cas, les runners GitHub-hosted sont le meilleur
choix : isolés, jetables, sans maintenance. Ajoutez un timeout-minutes pour
éviter les jobs zombies qui consomment vos minutes sans fin.
- Runners GitHub-hosted préférés par défaut
-
timeout-minutesdéfini sur chaque job
jobs: build: runs-on: ubuntu-24.04 timeout-minutes: 30Environments
Section intitulée « Environments »Un Environment GitHub représente une cible de déploiement — staging,
production. Il porte des règles de protection et des secrets dédiés,
ce qui en fait le bon endroit pour cloisonner les accès de production.
Protection
Section intitulée « Protection »Exigez une approbation manuelle sur l'environment production et limitez
les branches autorisées à main. Un wait timer laisse une fenêtre pour
annuler un déploiement déclenché par erreur.
- Environment
productionavec approbation requise - Branches autorisées limitées (
mainuniquement pour la prod) -
wait timerconfiguré pour permettre une annulation
Rangez les secrets de production uniquement dans l'environment
production. Des jetons distincts par environment limitent l'impact d'une
fuite : un token de staging compromis ne touche pas la prod.
- Secrets de production isolés dans l'environment
production - Jetons différents pour chaque environment
Attestations et provenance
Section intitulée « Attestations et provenance »Une attestation est une preuve cryptographique liant un artefact à son workflow de build et à son code source. Elle permet de vérifier, avant déploiement, qu'un binaire est bien celui qu'on croit.
Génération
Section intitulée « Génération »Générez une attestation de provenance et un SBOM (Software Bill of Materials — l'inventaire des composants) pour chaque release. Le guide Attestations détaille la mise en place.
- Attestation de provenance générée pour chaque release
- SBOM généré et publié avec la release
- name: Générer l'attestation de provenance uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 with: subject-path: my-app.tar.gzVérification
Section intitulée « Vérification »Une attestation non vérifiée ne protège de rien. Contrôlez-la avant tout
déploiement avec gh attestation verify. Le guide
Vérifier les attestations
montre comment l'imposer en CI/CD.
- Attestation vérifiée avant chaque déploiement
gh attestation verify my-app.tar.gz --owner mon-org --repo mon-appOutils d'audit
Section intitulée « Outils d'audit »Ces contrôles se passent en grande partie automatiquement. Trois outils complémentaires couvrent la syntaxe, la configuration et la posture de sécurité globale d'un dépôt.
Scorecard
Section intitulée « Scorecard »OpenSSF Scorecard note un dépôt sur une série de critères de sécurité — permissions du jeton, épinglage des dépendances, workflows dangereux. Visez 10/10 sur les contrôles liés aux Actions.
- Score
Token-Permissionsà 10/10 - Score
Pinned-Dependenciesà 10/10 - Aucun
Dangerous-Workflowdétecté
scorecard --local . --checks Token-Permissions,Pinned-Dependencies,Dangerous-WorkflowCheckov analyse les workflows comme du code d'infrastructure et repère les mauvaises configurations : permissions larges, actions non épinglées, secrets exposés.
- Scan des workflows sans erreur critique
checkov -d .github/workflows/ --framework github_actionsactionlint
Section intitulée « actionlint »actionlint valide la syntaxe des workflows : expressions invalides,
clés inconnues, erreurs shell dans les blocs run:. C'est le filet de sécurité
le plus rapide à mettre en place — voir le guide
actionlint pour l'intégrer en
pre-commit.
- Workflows syntaxiquement valides
actionlint .github/workflows/*.ymlRevue de code
Section intitulée « Revue de code »La checklist prend tout son sens en revue de pull request. Un workflow modifié mérite la même attention qu'un changement de code applicatif — c'est lui qui détient les droits et les secrets du dépôt.
Avant le merge d'un workflow
Section intitulée « Avant le merge d'un workflow »Six points bloquants à vérifier systématiquement avant d'approuver une
pull request qui touche un fichier .github/workflows/.
- Permissions explicites et minimales
- Actions épinglées par SHA
- Aucune injection de commandes possible
- Secrets utilisés via
env:uniquement - Aucun
pull_request_targetdangereux -
timeout-minutesdéfini sur chaque job
Revue régulière
Section intitulée « Revue régulière »La sécurité d'un pipeline se dégrade avec le temps : nouvelles actions, permissions qui s'élargissent, SHA qui prennent du retard sur les correctifs. Un audit périodique rattrape cette dérive.
- Audit trimestriel de l'ensemble des workflows
- Pull requests Dependabot traitées sans accumulation
- Permissions réelles comparées aux permissions nécessaires
Workflow sécurisé type
Section intitulée « Workflow sécurisé type »Voici un workflow CI/CD complet qui applique l'ensemble de cette checklist : permissions minimales, actions épinglées, OIDC pour le déploiement, timeouts et concurrency. Servez-vous-en comme base de départ.
name: Secure CI/CD
on: push: branches: [main] pull_request:
permissions: {}
concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs: test: runs-on: ubuntu-24.04 timeout-minutes: 15 permissions: contents: read steps: - name: Récupérer le code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - name: Installer Node.js uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: 20 cache: npm - name: Installer les dépendances run: npm ci - name: Lancer les tests run: npm test
build: needs: test runs-on: ubuntu-24.04 timeout-minutes: 15 permissions: contents: read steps: - name: Récupérer le code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - name: Installer Node.js uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: 20 cache: npm - name: Installer les dépendances run: npm ci - name: Construire l'application run: npm run build - name: Publier l'artefact de build uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: dist path: dist/
deploy: if: github.ref == 'refs/heads/main' needs: build runs-on: ubuntu-24.04 timeout-minutes: 10 environment: production permissions: contents: read id-token: write steps: - name: Télécharger l'artefact de build uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 with: name: dist path: dist/ - name: Authentification AWS via OIDC uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 with: role-to-assume: ${{ vars.AWS_ROLE_ARN }} aws-region: eu-west-1 - name: Synchroniser le build vers S3 env: S3_BUCKET: ${{ vars.S3_BUCKET }} run: aws s3 sync dist/ "s3://$S3_BUCKET"À retenir
Section intitulée « À retenir »- Une checklist n'a de valeur qu'appliquée en revue : passez-la sur chaque pull request qui modifie un workflow.
- Les permissions minimales et l'épinglage par SHA ferment les deux voies d'attaque les plus exploitées.
- Un secret ne transite jamais par
run:directement : toujours parenv:, pour ne pas le voir fuir dans les logs. - L'injection de commandes se neutralise en passant les données externes par une variable d'environnement.
- Un runner self-hosted sur un dépôt public expose votre infrastructure : réservez-le aux dépôts privés et préférez l'éphémère.
- Scorecard, Checkov et actionlint automatisent ces vérifications — intégrez-les au pipeline pour ne pas dépendre d'une relecture humaine.