Un flux de travail de CI exécute du code venu de nombreuses sources : votre dépôt, des actions tierces et des demandes de tirage (pull requests) de personnes que vous ne connaissez pas. Chacune est une porte d’entrée. Cet article présente six contrôles qui ferment les lacunes courantes. Les exemples proviennent de rzv-ci-plb, le guide de CI de Runzivo.

1. Commencer chaque flux de travail avec des permissions en lecture seule
Erreur : aucune clé permissions:. Le GITHUB_TOKEN reçoit alors l’ensemble par défaut défini pour l’entreprise, l’organisation ou le dépôt, qui peut autoriser l’écriture.
À faire : définissez la lecture seule en haut du fichier, comme le fait chaque flux de travail du guide. Extrait de ci.yml :
permissions:
contents: readÉlevez les permissions seulement sur la tâche qui en a besoin. Un exemple simplifié :
jobs:
label:
runs-on: ubuntu-24.04
permissions:
contents: read
pull-requests: write
steps:
- run: echo "This job can label pull requests."Dès que vous définissez une permission, toutes celles que vous omettez passent à none. La référence de la syntaxe des flux de travail le documente. Voir aussi Permissions dans docs/security.md.
2. Épingler les actions tierces à un SHA de commit complet
Erreur : uses: some-org/some-action@v3. Une étiquette peut être déplacée vers du nouveau code à tout moment.
À faire : épinglez au SHA complet et gardez la version dans un commentaire.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1Vérifiez le SHA avant de l’utiliser. Extrait de docs/security.md :
gh api repos/actions/checkout/git/ref/tags/v7.0.1 --jq '.object.type + " " + .object.sha'
# If the type is "tag", dereference it:
gh api repos/OWNER/REPO/git/tags/TAG_OBJECT_SHA --jq .object.shaAssurez-vous que le SHA appartient au dépôt de l’action elle-même, et non à un fork. Dependabot peut garder les SHA épinglés à jour. GitHub documente aussi les fichiers de verrouillage des dépendances de flux de travail, actuellement en préversion publique, comme une option plus solide que l’épinglage par SHA.
3. Ne jamais coller le texte d’un événement dans du code shell
À ne pas faire. Cette étape est abrégée à partir du guide de GitHub sur l’injection de script :
- name: Check PR title
run: |
title="${{ github.event.pull_request.title }}"
if [[ $title =~ ^octocat ]]; then
echo "PR title starts with 'octocat'"
fiGitHub remplace ${{ }} avant le démarrage du shell. Une demande de tirage intitulée a"; ls $GITHUB_WORKSPACE" devient une commande.
À faire : transmettez la valeur par une variable d’environnement et mettez-la entre guillemets. Le shell la traite alors comme une donnée. Adapté de Entrées non fiables dans docs/security.md :
- name: Show title
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
printf 'Title: %s\n' "$PR_TITLE"actionlint et zizmor signalent tous deux le modèle dangereux. J’ai exécuté les deux sur l’exemple ci-dessus pour le confirmer.
4. Traiter les demandes de tirage issues de forks comme non fiables
GitHub limite déjà les demandes de tirage issues de forks. Selon la référence des événements, les secrets autres que GITHUB_TOKEN ne sont pas transmis à l’exécuteur, et le jeton est en lecture seule.
Le risque revient lorsque vous utilisez pull_request_target ou workflow_run. Ces déclencheurs s’exécutent avec les privilèges du dépôt de base. Le guide d’utilisation sécurisée de GitHub recommande de les éviter si vous n’en avez pas besoin, et de ne jamais y extraire ni y exécuter du code non fiable provenant d’une demande de tirage.
Empêchez aussi actions/checkout de conserver le jeton pour les étapes suivantes :
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: falseLes exécuteurs autohébergés exigent une règle de plus : n’y exécutez pas les demandes de tirage issues de forks. runzivo.yml, dans le guide, ne s’exécute que sur push vers main et sur déclenchement manuel. Voir Exécuteurs autohébergés dans docs/security.md.
5. Déployer depuis une tâche distincte et protégée
Erreur : le flux de travail des demandes de tirage détient aussi les identifiants de déploiement.
À faire : déployez uniquement depuis main, une fois les vérifications réussies, par un environnement GitHub. Ajoutez des réviseurs obligatoires lorsque votre forfait le permet, n’autorisez que la branche main et stockez les secrets de déploiement dans l’environnement.
Je nomme les environnements <NN>-<env>-<NN> : 01-dev-01, 02-qat-01, 03-stg-01 et 04-prd-01, avec les codes dev, qat, stg et prd. Les listes sont ainsi triées du développement à la production. Cette tâche de déploiement, tirée de la barrière de déploiement dans docs/environments.md, cible le premier environnement de production :
deploy:
needs: [test, build]
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 15
environment:
name: 04-prd-01
permissions:
contents: read
concurrency:
group: deploy-04-prd-01
cancel-in-progress: false
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- run: bash "${GITHUB_WORKSPACE}/scripts/ci/03-deploy.sh"03-deploy.sh représente votre propre script de déploiement, numéroté après la compilation et les tests. Les tâches des demandes de tirage ne nomment jamais d’environnement : elles ne voient donc jamais ses secrets.
Pour l’accès infonuagique, préférez OpenID Connect à une clé de longue durée : ajoutez id-token: write à cette tâche seulement, et GitHub émet un jeton de courte durée à chaque exécution. Voir Identifiants dans docs/security.md.
6. Garder les secrets hors des journaux
- N’affichez pas les secrets, même partiellement.
- Masquez les valeurs dérivées avec
::add-mask::, comme le décrit le guide des secrets. - Ne stockez pas de blocs JSON ou YAML comme un seul secret. La rédaction repose sur la correspondance de valeurs exactes : les secrets structurés peuvent donc fuir.
- Si un secret se retrouve dans un journal, supprimez le journal et faites la rotation du secret.
Vérifier automatiquement
Le guide exécute zizmor à chaque demande de tirage, à chaque push vers main et une fois par semaine, par security.yml. En local :
bash scripts/ci/01-validate.sh securityzizmor propose des audits pour l’injection de gabarit, les permissions excessives, les actions non épinglées et les déclencheurs dangereux. Le fichier zizmor.yml du dépôt exige un épinglage par SHA pour chaque action.
Exécuter sur Runzivo
Runzivo est en accès anticipé. Il attribue à chaque tâche admissible une microVM neuve à usage unique, ce qui limite ce qu’une tâche peut laisser derrière elle pour la suivante. Il ne remplace aucun des contrôles ci-dessus. Les permissions, l’épinglage et la protection contre les injections relèvent de votre flux de travail. Les permissions de son application GitHub sont énumérées dans la documentation de Runzivo.
Dans le dépôt
docs/security.md: permissions, SHA épinglés, entrées non fiables, identifiants, exécuteurs autohébergésdocs/environments.md: identifiants d’environnement et barrière de déploiement04-prd-01.github/workflows/security.ymlet.github/zizmor.yml.github/workflows/runzivo.yml: tâches autohébergées tenues à l’écart des demandes de tirage- Règles de flux de travail dans
AGENTS.md: les mêmes règles, écrites pour les agents de programmation
Pour exécuter ces flux de travail sur des exécuteurs GitHub Actions gérés hébergés au Canada, consultez les tarifs des exécuteurs et des bacs à sable Runzivo.
Chaque exemple de cette série est testé dans rzv-ci-plb, the Runzivo CI playbook, fixé à l’étiquette v0.1.0.