Repository navigation
fix(ci): sécurise le workflow de versionnage - #1
Merged
Merged
Conversation
Le titre de PR, le message et l'auteur du commit de tête, la liste des commits depuis le dernier tag et l'entrée dry_run étaient interpolés directement dans les scripts run. Un message de commit contenant un guillemet suivi d'une commande suffisait à exécuter du code dans le job, avec le GITHUB_TOKEN. Ces valeurs passent désormais par le bloc env de chaque step et sont lues entre guillemets dans le shell. Le calcul du bump (majeur, mineur, patch) analyse la variable avec printf et grep, sans interpolation. Le eval de retry_push est remplacé par une exécution directe des arguments. Les valeurs du contexte github utilisent les variables GITHUB_* du runner.
…teur aléatoire Le message de commit, l'auteur et la liste des commits étaient écrits dans GITHUB_ENV sous la forme nom=valeur ou avec le délimiteur fixe EOF. Un message multi-ligne pouvait donc définir d'autres variables pour tous les steps suivants, par exemple BASH_ENV, que bash évalue au démarrage de chaque step. Ces valeurs sont maintenant écrites avec la syntaxe nom<<délimiteur et un délimiteur aléatoire de 128 bits, impossible à prévoir dans un message.
Aucun bloc permissions n'était défini : le token recevait les droits par défaut du dépôt, potentiellement en écriture sur tous les scopes. Le workflow passe en contents: read par défaut. Le job versioning, qui pousse le commit du changelog et le tag, reçoit uniquement contents: write, les permissions d'un job remplaçant celles du workflow. Aucun autre droit n'est accordé : le workflow n'utilise que git push.
La boucle de push réessayait le même git push sans récupérer la branche distante. Si main avait avancé entre-temps, chaque tentative était rejetée (non fast-forward) et le job échouait après trois essais. Avant chaque nouvelle tentative, le commit du changelog est rejoué avec git pull --rebase sur origin/main. En cas de conflit, le rebase est annulé et la tentative suivante échoue normalement. Le nombre d'essais reste borné par max_attempts, sans force push, et l'attente inutile après le dernier essai est supprimée.
Quand le tag calculé existait déjà sur un autre commit, le step le supprimait puis le recréait sur HEAD. Les push étaient en double : suppression distante, push du nouveau tag, nouvelle suppression via retry_push (qui effaçait le tag tout juste poussé), puis nouveau push. Résultat : un tag publié était déplacé, notamment à chaque push sans bump de version, où le dernier tag était avancé sur HEAD. Désormais, si le tag existe déjà, sa création est sautée et il n'est jamais modifié (pas de git tag -f ni de push --force). Un avertissement est émis si le tag existe sur un autre commit alors qu'un bump était attendu. Les push en double sont supprimés et le tag est poussé avec la référence explicite refs/tags/.
Le tag mobile v4 peut être déplacé vers n'importe quel commit par le dépôt de l'action. L'action est désormais référencée par le SHA complet 11d5960a326750d5838078e36cf38b85af677262, vérifié contre le tag de release officiel v4.4.0 du dépôt actions/checkout. C'est le commit vers lequel pointe v4 aujourd'hui : le comportement du checkout est inchangé.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Contexte
Le workflow
.github/workflows/versionning.yml(Semantic Versioning) présentait plusieurs défauts de sécurité et de robustesse. Cette PR les corrige, avec un commit par sujet. Les déclencheurs, le format semver, les règles de bump et le contenu du CHANGELOG ne changent pas.Corrections
1. Injection de script (5ef4a09)
Le titre de PR, le login de l'auteur de la PR, le message, le nom et l'email du commit de tête,
env.commits,env.user_nameet l'entréedry_runétaient interpolés via${{ }}directement dans les blocsrun:. Un message de commit du typefix: x"; <commande>; echo "exécutait la commande dans le job, avec leGITHUB_TOKEN.env:du step et sont lues entre guillemets ("$COMMITS","$USER_NAME"…)."$COMMITS"avecprintf '%s\n' … | grep -E, sans interpolation.evalderetry_pushest remplacé par une exécution directe des arguments ("$@").githubutilisent les variables du runner ($GITHUB_EVENT_NAME,$GITHUB_REF_NAME,$GITHUB_SHA…).${{ }}dans un blocrun:. Les expressions restantes se trouvent uniquement dans lesif:, lesenv:et lesoutputs.2. Injection dans
GITHUB_ENV(f657926)Ce point n'était pas dans la liste initiale, mais il est du même ordre.
user_name,user_emailetcommit_messageétaient écrits sous la formenom=valeur, etcommitsavec le délimiteur fixeEOF. Un message de commit sur plusieurs lignes pouvait donc définir des variables pour tous les steps suivants, par exempleBASH_ENV=$(…), que bash évalue au démarrage de chaque step. Ces valeurs sont maintenant écrites avecnom<<délimiteuret un délimiteur aléatoire de 128 bits (openssl rand -hex 16). Il en va de même pourCHANGELOG.3. Permissions minimales (5c49a5e)
contents: read.versioning(le seul job, qui pousse le commit du changelog et le tag) :contents: write. Les permissions d'un job remplacent celles du workflow.git push.4. Boucle de push du changelog (136e28d)
La boucle réessayait le même
git push. Simainavait avancé, les 3 tentatives étaient toutes rejetées (non fast-forward). Désormais :git pull --rebase origin mainavant chaque nouvelle tentative ;git rebase --abort, puis la tentative suivante échoue normalement ;max_attempts(3), sans force push ;5. Tag déjà existant (4f4617f)
Quand le tag calculé existait sur un autre commit, l'ancien code exécutait 4 push : suppression distante, push du nouveau tag, nouvelle suppression via
retry_push(qui effaçait le tag tout juste poussé), puis un nouveau push. Les deux opérations étaient donc en double, et le tag publié finissait déplacé sur HEAD.Désormais, si le tag existe déjà, sa création est sautée et le tag n'est jamais modifié (ni
git tag -d/-f, ni suppression distante, ni--force) :::warning::dans le run, le tag n'est pas touché.L'output
new_versionest renseigné dans tous les cas, comme avant. Le push utilise la référence expliciterefs/tags/$TAG.6. Action épinglée par SHA (2073407)
actions/checkout@v4devientactions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0.v4.4.0du dépôtactions/checkout(git ls-remote: tag léger, donc SHA de commit) et contre la page de la releasev4.4.0.v4aujourd'hui : le comportement est identique.allow-unsafe-pr-checkout) ne concerne quepull_request_targetetworkflow_run, que ce workflow n'utilise pas.Changement de comportement (nécessaire pour le point 5)
Avant, chaque push sur
mainsans bump (par exempledocs:ouchore:) déplaçait le dernier tag (ex.v0.8.0) sur le nouveau commit du changelog. Désormais, ce tag reste sur le commit où il a été publié. La section## v0.8.0du CHANGELOG continue d'agréger ces commits (logique inchangée), mais le tag ne les inclut plus. C'est la conséquence directe de « ne jamais déplacer un tag publié ». Incrémenter automatiquement aurait modifié les règles semver (undocs:aurait produit un patch). La création du tag est donc simplement sautée.Validation
actionlint1.7.7 avecshellcheck0.11.0 : aucune erreur ni avertissement sur les 4 workflows. Surmain, actionlint signale l'injection viagithub.event.pull_request.title. Il ne reste que desinfo/styleshellcheck déjà présents avant (>> $GITHUB_ENVsans guillemets).bash -ecomme sur le runner, surmainpuis sur cette branche, avec un dépôt distant local. Résultats :fix: x"; touch …: commande exécutée surmain, pas sur la branche ;BASH_ENV=$(…), et ligneEOFdans un commit : variable injectée surmain, pas sur la branche ;main: 3 échecs surmain; rebase puis succès sur la branche ; conflit sur CHANGELOG.md : rebase annulé et échec propre, distant intact ;main, intact sur la branche ; course (tag publié après le checkout) : push refusé sans écrasement ;fix:: même version, même tag et CHANGELOG identique àmain.Hors périmètre (non modifié, à traiter séparément si besoin)
concurrency:: deux runs surmainpeuvent encore se croiser (changelog et tag). Un groupeconcurrencysans annulation sérialiserait les runs.pull_requestaveccontents: write. Pour une PR depuis un fork, le token reste en lecture seule ; pour une branche interne, il est en écriture, mais aucun step d'écriture ne s'exécute sur une PR. Séparer l'analyse et la publication en deux jobs permettrait de limiter l'écriture au seul job de publication.$DRY_RUN(step Setup) n'est jamais défini : le message d'audit du mode dry-run ne s'affiche jamais.force_versionn'est utilisée nulle part. Enworkflow_call,dry_runn'est pas lu (github.event.inputsau lieu deinputs).ci-debian.yml,ci-freebsd.yml,ci-rocky.yml) utilisent encore des actions non épinglées par SHA.