Un job GitHub Actions est un bon endroit pour voler des secrets. Il détient des clés de déploiement, des tokens de registre et des identifiants cloud, et il exécute du code que quelqu'un d'autre a écrit il y a une minute : des actions tierces, des paquets et leurs scripts d'installation. La gestion des secrets de GitHub protège contre les accidents. Elle n'a pas été conçue pour arrêter du code qui s'exécute déjà dans le job.
Cet article explique comment fonctionnent les secrets dans GitHub Actions, les cinq façons dont ils fuient en pratique et les contrôles qui ferment chacune d'elles.
Comment fonctionnent les secrets GitHub Actions
Les secrets existent à trois niveaux : organisation, dépôt et environnement. Quand le même nom existe à plusieurs niveaux, le plus spécifique l'emporte : l'environnement sur le dépôt, le dépôt sur l'organisation.
Quelques limites et règles à connaître :
- Taille et nombre. Jusqu'à 1 000 secrets d'organisation, 100 secrets de dépôt et 100 secrets d'environnement, de 48 Ko au maximum chacun. Un workflow voit tous les secrets de dépôt et d'environnement, mais seulement les 100 premiers secrets d'organisation par ordre alphabétique.
- Les forks n'obtiennent rien. À part
GITHUB_TOKEN, les secrets ne sont pas transmis aux workflows déclenchés par une pull request provenant d'un fork. - Dependabot est traité comme un fork. Les workflows déclenchés par Dependabot reçoivent un token en lecture seule et uniquement les secrets Dependabot, pas les secrets Actions.
- Les workflows réutilisables doivent recevoir les secrets.
secrets: inheritles transmet tous implicitement, au sein de la même organisation ou entreprise. Les secrets d'environnement ne peuvent pas être transmis viaworkflow_call. - Les environnements contrôlent l'accès. Avec des relecteurs obligatoires, un job ne peut pas lire les secrets d'un environnement tant que quelqu'un ne l'a pas approuvé. Les règles de branches de déploiement limitent les branches qui peuvent utiliser l'environnement.
Ce que fait le masquage, et ce qu'il ne fait pas
GitHub retire les valeurs des secrets des logs, mais cette suppression repose sur une correspondance exacte. La documentation de GitHub elle-même liste les cas où elle échoue :
- un secret à l'intérieur de données structurées comme du JSON ou du YAML
- une valeur dérivée d'un secret, comme un token échangé contre un autre token, à moins de l'enregistrer avec
::add-mask:: - un secret transformé, par exemple encodé en base64
Le masquage protège contre l'affichage accidentel d'un secret. Il ne fait rien contre du code qui encode d'abord le secret.
Comment les secrets fuient des workflows
1. Une action compromise les affiche
En mars 2025, un attaquant a compromis tj-actions/changed-files (CVE-2025-30066), une action utilisée par plus de 23 000 dépôts. Il a déplacé les tags de version existants de l'action vers un commit malveillant : chaque workflow qui référençait un tag comme @v45 a exécuté le code de l'attaquant. Seuls les workflows épinglés à un SHA de commit complet étaient à l'abri.
La charge utile lisait les secrets dans la mémoire du runner et les affichait dans le log du job, encodés deux fois en base64 pour que le masquage ne les détecte pas. Sur les dépôts publics, ces logs sont lisibles par tout le monde. La compromission est partie d'une autre action, reviewdog/action-setup (CVE-2025-30154), et avant cela d'un personal access token volé via un workflow pull_request_target.
2. Un workflow pull_request_target exécute du code non fiable
pull_request_target s'exécute dans le contexte du dépôt de base, avec ses secrets et un token qui peut écrire, même quand la pull request vient d'un fork. Ce n'est sûr que tant que le workflow n'exécute jamais de code provenant de la pull request. Quand un workflow récupère la tête de la pull request et la construit, toute personne capable d'ouvrir une pull request peut lire chaque secret du workflow. GitHub Security Lab les appelle des « pwn requests ».
GitHub resserre progressivement les règles. Depuis décembre 2025, pull_request_target utilise toujours le fichier de workflow de la branche par défaut. Depuis juillet 2026, actions/checkout refuse de récupérer le code d'un fork dans les workflows pull_request_target qui utilisent des schémas connus comme dangereux, et les protections d'exécution des workflows, disponibles pour tous depuis septembre 2026, désactiveront pull_request_target par défaut dans les dépôts publics sans politique d'événements à partir du 2 novembre 2026.
3. Une entrée non fiable est injectée dans un script
Les expressions de workflow sont développées avant l'exécution du shell. Une étape comme celle-ci :
- run: echo "Title: ${{ github.event.issue.title }}"
exécute tout ce que contient le titre de l'issue. Un attaquant ouvre une issue dont le titre est une commande shell et le job l'exécute, avec les secrets du job à portée de main. Faites plutôt passer les valeurs non fiables par une variable d'environnement, pour que le shell les traite comme des données :
- env:
TITLE: ${{ github.event.issue.title }}
run: echo "Title: $TITLE"
4. Les caches et les artefacts les emportent
- Artefacts. En 2024, la recherche ArtiPACKED de Unit 42 a montré que
actions/checkoutécritGITHUB_TOKENdans.git/configpar défaut : envoyer l'espace de travail comme artefact divulgue donc le token à quiconque peut le télécharger.upload-artifactexclut les fichiers cachés par défaut depuis septembre 2024, etactions/checkoutv6 stocke l'identifiant hors du répertoire de checkout, mais les anciennes versions épinglées et les étapes d'envoi personnalisées laissent encore fuir le token. - Caches. Un attaquant capable d'exécuter du code dans n'importe quel workflow d'un dépôt peut remplir son cache, en chasser les entrées réelles et en placer une empoisonnée sous la même clé. Un workflow privilégié la restaure ensuite. En mai 2026, cette chaîne, partie de
pull_request_targetet terminée par un token OIDC lu dans la mémoire du runner, a permis à un attaquant de publier 84 versions malveillantes de 42 paquets TanStack.
5. Une dépendance les envoie à un serveur
Les scripts d'installation s'exécutent avec les mêmes accès que le reste du job. Le ver Shai-Hulud, observé pour la première fois sur npm en septembre 2025, s'exécutait pendant npm install, collectait des tokens npm, des tokens GitHub et des clés cloud dans l'environnement et les publiait dans des dépôts GitHub publics. Sa vague de novembre 2025 s'exécutait au preinstall et enregistrait la machine infectée comme runner auto-hébergé, pour que l'attaquant puisse revenir plus tard.
Cette voie ne nécessite aucune erreur dans votre workflow. Une seule version compromise n'importe où dans l'arbre de dépendances suffit, et les secrets partent par le réseau.
Comment les verrouiller
Remplacez les clés cloud stockées par OIDC. Au lieu de conserver une clé AWS, Azure ou Google Cloud comme secret, laissez le job demander un token à courte durée de vie à l'émetteur OIDC de GitHub et l'échanger auprès du fournisseur cloud. La politique de confiance décide quel dépôt, quelle branche ou quel environnement peut assumer le rôle, et l'identifiant expire avec le job. Le job a besoin de permissions: id-token: write. Les tokens OIDC peuvent quand même être volés depuis un job en cours, comme l'a montré TanStack, mais un token volé vaut bien moins qu'une clé qui n'expire jamais.
Épinglez les actions à un SHA de commit complet. La documentation de GitHub présente cela comme le seul moyen d'utiliser une action comme une release immuable. Un tag peut être déplacé ; un SHA, non. Depuis août 2025, la politique des actions autorisées peut imposer l'épinglage par SHA et bloquer des actions précises dans toute une organisation. Dependabot et Renovate peuvent tenir à jour les SHA épinglés.
Donnez à GITHUB_TOKEN le strict nécessaire. Mettez la valeur par défaut en lecture seule et accordez les droits d'écriture job par job. Les nouvelles organisations et les nouveaux dépôts sont en lecture seule par défaut depuis février 2023, mais les plus anciens ont conservé la valeur permissive à moins que quelqu'un ne l'ait changée. Consultez Permissions de GITHUB_TOKEN : le moindre privilège pour chaque workflow.
Limitez les secrets à l'étape qui en a besoin. Un secret dans le env: d'une étape n'existe que dans l'environnement de cette étape. Un secret défini au niveau du job est visible par chaque étape, y compris chaque action tierce.
Placez les secrets de production derrière des environnements. Avec des relecteurs obligatoires et des règles de branches de déploiement, un workflow sur une branche de fonctionnalité, ou déclenché par une pull request, ne peut pas les lire.
Traitez pull_request_target et les entrées non fiables comme de l'exécution de code. Évitez pull_request_target sauf si vous en avez besoin, n'y construisez jamais le code d'une pull request et faites passer les données d'événement par des variables d'environnement. CodeQL peut vérifier dans les workflows les schémas d'injection et de pwn request ; il est disponible pour tous sur GitHub Actions depuis avril 2025.
Activez le secret scanning et la push protection. Ils détectent les secrets commités dans le dépôt, une fuite différente de celles ci-dessus, mais tout aussi courante.
Restreignez les connexions du job. La plupart des jobs ont besoin d'une courte liste de destinations : des registres de paquets, des registres de conteneurs et quelques API. Si le job ne peut atteindre que celles-ci, un secret lu par du code malveillant n'a nulle part où aller. Découvrez comment une politique de trafic sortant empêche l'exfiltration depuis GitHub Actions.
Renouvelez après toute exposition. Si un workflow a exécuté une action ou un paquet compromis, partez du principe que chaque secret qu'il pouvait lire a été volé. Révoquez-les et réémettez-les tous, pas seulement ceux que vous pensez avoir été utilisés.
La place de CRACI
CRACI exécute vos jobs GitHub Actions sur ses propres runners : vous remplacez runs-on par craci, et les workflows, les secrets et l'historique des exécutions restent dans GitHub. Deux de ses fonctions visent les fuites décrites ci-dessus.
- Une politique de trafic sortant qui bloque tout en cas d'échec. Vous définissez quelles sources de paquets, quels registres et quels hôtes un job peut atteindre. La politique est validée avant le démarrage du job et appliquée sur le runner : une connexion vers toute autre destination est bloquée, et vous recevez une alerte en cas de violation de la politique.
- Une trace de ce qu'a fait le job. CRACI enregistre chaque paquet que chaque job a réellement récupéré, y compris les paquets restaurés depuis des caches, ainsi qu'une trace des connexions réseau du job. Quand le prochain paquet compromis est annoncé, vous pouvez voir quels builds l'ont récupéré et à quoi ils se sont connectés, au lieu de le deviner à partir des lockfiles.
Chaque job s'exécute dans sa propre machine virtuelle : rien de ce qu'un job laisse derrière lui n'est là pour le suivant. CRACI ne recherche pas les secrets dans votre code ; gardez le secret scanning de GitHub pour cela.
Réservez une démo pour voir la trace de l'un de vos propres builds.
