
Article - Publié le
Impossible de ne jamais avoir entendu parler des Github Actions dans le monde du DevOps. Cette plateforme de CI développée et intégrée à Github permet aujourd'hui d'accomplir de très nombreuses actions automatisées lors d'événements sur un dépôt Github. Tests, déploiements, notifications, commits, tout est possible avec les Github Actions. C'est merveilleux.
Oui, mais. Comme toujours avec les panacées technologiques novatrices, les trouble-fêtes ne sont jamais loin. Tandis que ces nouvelles solutions sont adoptées massivement, tandis que l'excitation est à son comble, tandis que les foules accourent pour s'approprier ces nouveaux outils, les faiblesses de ces systèmes se révèlent, et notamment leurs faiblesses de sécurité.
Nous avons voulu, dans cet article, présenter différentes erreurs à éviter dans la création d'une CI via Github Actions. Nous avons pu nous-mêmes faire le constat de ces faiblesses dans nos systèmes, et nous souhaitons partager les pratiques de sécurité qui ont émergées de leur remédiation, afin de mettre en lumière certains écueils dont il vaut mieux se prémunir.
tj-actions/changed-files, mars 2025. reviewdog/action-setup, mars 2025. trivy-action, février 2026. Ces trois actions Github ont été malgré elles des exemples de ce que l'on appelle une Supply Chain Attack (que l'on pourrait traduire par "attaque par chaîne d'approvisionnement"). Le principe est simple : plutôt que de s'attaquer au système lui-même, l'attaquant cible ses dépendances. Ce type d'attaque se retrouve majoritairement dans les environnements npm et pypi, les IDEs comme VSCode, et les CIs comme Github Actions, car ce sont des outils très populaires.
Les effets d'une Supply Chain Attack sont bien souvent dévastateurs, car celle-ci touche l'ensemble des projets utilisant la dépendance corrompue. Ainsi, la compromission de l'action trivy en février 2026 a touché plusieurs dizaines de milliers de projets, avec des effets en cascade ayant des répercussions pour certaines encore inconnues à la date d'écriture de cet article.
Le premier, le plus simple, est de limiter au maximum ses dépendances. Dans une forteresse, moins il y a de portes, moins il y a de failles potentielles. Ces recommandations s'appliquent bien sûr à tous les autres systèmes et projets embarquant des dépendances.
La première étape de notre longue quête vers une meilleure sécurité des Github Actions consiste donc à aller à contre-courant, dans un écosystème qui encourage la multiplication des dépendances, en réduisant au strict nécessaire les actions tierces utilisées, et à se limiter à des sources de confiance. On conviendra ainsi que, pour lister les fichiers d'un répertoire, pour vérifier le format du titre d'une Pull Request, ou même pour créer une nouvelle branche, il n'est pas nécessaire de recourir à une action tierce, maintenue par on-ne-sait-qui.
Bien sûr, ce n'est pas suffisant, et nous allons voir par la suite comment renforcer plus en détails ses workflows Github Actions. Néanmoins, ce principe de minimisation demeure une posture à conserver lors de la construction d'une CI avec Github Actions, ou avec tout autre système.
Lorsqu'on regarde les documentations de beaucoup d'actions sur Github, on tombe sur quelque chose de la forme :
- uses: docker/build-push-action@v7On serait tenté de se dire que c'est satisfaisant : si jamais une nouvelle version mineure ou patch de l'action est publiée, les prochains runs la prendront automatiquement, alors qu'un changement plus conséquent lors d'une version majeure ne sera pas pris en compte.
Malheureusement, en y regardant de plus près, c'est prendre un risque. Si jamais l'action en question venait à être compromise, l'attaquant pourrait tout simplement publier une version et faire pointer les nouveaux tags vers le commit malveillant.
v2.x.z non ? Même dans ce cas, sauf si les tags immuables sont activés, un attaquant pourrait toujours renommer tous les tags existant du dépôt. Ce n'est pas de la science-fiction, mais bien ce qui s'est produit lors du hack de trivy en mars 2026.
La solution à ce problème est simple, il suffit de fixer l'action utilisée à un SHA de commit, par exemple :
- uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856aAvec ça, plus de surprise, l'action téléchargée sera toujours la même.
À première vue, cette approche comporte un inconvénient de taille : elle casse les automatisations de mises à jour de dépendances comme dependabot.
La bonne nouvelle, c'est que ce n'est pas le cas, en rajoutant une petite indication, on peut permettre à dependabot de continuer à nous proposer les mises à jour automatiquement. Pour ça il suffit d'indiquer le numéro de version :
- uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0Avec ce petit détail, on a le meilleur des deux mondes, la reproductibilité et la facilité de maintenance. C'est extraordinaire.
persist-credentials par défautactions/checkout, préciser explicitement la valeur persist-credentials, et lui assigner false sauf en cas de besoin spécifique.Vous commencez peut-être à pressentir une certaine propension à l'insécurité chez Github. Sachez que cette intuition est fondée. Lors du clonage d'un dépôt à l'aide de l'action actions/checkout, le comportement par défaut de l'action est de conserver les identifiants utilisés (par défaut, secrets.GITHUB_TOKEN) dans le fichier .git/config pour les versions antérieures à v6, et dans le dossier $RUNNER_TEMP à partir des versions v6 (le token pourra alors être retrouvé en lisant les fichiers /home/runner/work/_temp/git-credentials-*.config).
Ce comportement permet aux steps suivantes du job en cours d'accéder à ces informations. Fondamentalement, ce n'est pas un problème, pour peu que l'on soit au courant de ce comportement. Il peut y avoir des cas où ces identifiants sont nécessaires pour les steps suivantes, notamment si elles ont besoin d'effectuer des actions sur Github. Néanmoins, cela veut aussi dire que le GITHUB_TOKEN est accessible à toute action du job en cours. En particulier, une action malveillante pourra alors exfiltrer ce token et hériter des droits de ce token (bien souvent très larges !).
Ce comportement est conditionné par le paramètre persist-credentials, dont la valeur par défaut est true.
Voici un exemple de workflow pour lequel le paramètre persist-credentials n'est pas spécifié lors de l'utilisation de l'action actions/checkout :
name: "[BAD] Persist credentials"
on:
workflow_dispatch:
permissions:
contents: write
jobs:
install-dependency:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: lgerard-pass/setup-dependency/git-commit@main
with:
msg: "coucou"Ici, l'action lgerard-pass/setup-dependency/git-commit@main héritera du GITHUB_TOKEN et de ses droits (en l'occurence, contents: write). Supposons que cette action est compromise, et devient :
name: Setup dependency
description: An action to setup an awesome dependency
inputs:
msg:
required: true
runs:
using: composite
steps:
- name: Install & run super-print
shell: bash
run: |
echo "pwned" > file.txt
git config --global user.email "attacker@evil.com"
git config --global user.name "attacker"
git checkout -B pwned
git add -A
git commit -m "pwned"
git push origin pwnedCe qui nous donne :

Lors du lancement du workflow, l'action tierce pourra alors écrire dans le dépôt, et plus généralement effectuer l'ensemble des actions permises par les droits du GITHUB_TOKEN. Rappelons que ces tokens peuvent être valides jusqu'à 6 heures.
Afin d'éviter cette malheureuse situation, il faut passer explicitement la valeur de persist-credentials à false. Notre workflow initial devient donc :
name: "[GOOD] Persist credentials"
on:
workflow_dispatch:
permissions:
contents: write
jobs:
install-dependency:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: lgerard-pass/setup-dependency/git-commit@main
with:
msg: "coucou"Ici, le GITHUB_TOKEN n'est plus écrit nulle part, et les steps suivantes n'en hériteront donc pas.
Il faut savoir que ce comportement est connu de Github, qui considère qu'il s'agit bien du comportement souhaité de leur action. Chacun et chacune en tirera les conclusions qui s'imposent.
secrets: inheritsecrets: inherit partage TOUS les secrets du workflow appelant vers l'appelé, même si le workflow appelé ne les déclare pas lui-même.Lorsqu'on utilise des workflows réutilisables, par défaut, aucun secret ne leur est propagé.
Prenons cet exemple :
# workflow appelé
name: "sub-workflow secrets inherit"
on:
workflow_call:
secrets:
STAGING_DEPLOY_KEY:
required: true
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- run: echo "${{ secrets.STAGING_DEPLOY_KEY }}"# workflow appelant
name: "Secrets inherit"
on:
workflow_dispatch:
jobs:
example:
uses: ./.github/workflows/secrets_inherit_sub.ymlIci même si dans la configuration de mon dépôt, de mon organisation, ou de mon environnement le secret STAGING_DEPLOY_KEY est présent, il ne sera pas disponible dans le workflow appelé. Pour le faire fonctionner il faudrait, de manière explicite, passer le secret du workflow appelant à celui appelé.
# workflow appelant
name: "Secrets inherit"
on:
workflow_dispatch:
jobs:
example:
uses: ./.github/workflows/secrets_inherit_sub.yml
secrets:
STAGING_DEPLOY_KEY: ${{ secrets.STAGING_DEPLOY_KEY }}Pour un secret, c'est suffisant. Si on commence à en avoir beaucoup, on peut être tenté de prendre un raccourci, en utilisant secrets: inherit.
name: "[BAD] Secrets inherit "
on:
workflow_dispatch:
jobs:
example:
uses: ./.github/workflows/secrets_inherit_sub.yml
secrets: inherit1. Elle obfusque quels secrets sont passés au workflow appelé, ce qui peut rendre l'audit plus difficile.
2. Surtout, tous les secrets accessibles au workflow appelant sont propagés au workflow appelé, y compris ceux qui ne sont pas déclarés.
Ainsi ce workflow fonctionnerait parfaitement bien :
name: "sub-workflow secrets inherit"
on:
workflow_call:
secrets:
STAGING_DEPLOY_KEY:
required: true
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- run: echo "${{ secrets.STAGING_DEPLOY_KEY }}"
- run: test "deploy-key-production" = "${{ secrets.PRODUCTION_DEPLOY_KEY }}"Ce comportement à lui tout seul ne permet pas de faire de l'escalade de privilège, car il faut déjà avoir les droits d'édition sur le workflow pour pouvoir l'exploiter. Néanmoins, avec cette implémentation, il devient difficile de déterminer de quels secrets le workflow appelé a réellement besoin, et cela favorise l'utilisation de secrets non nécessaires dans certains workflows.
run en variables d'environnement pour éviter une injection de commande.Ne JAMAIS se fier aux entrées utilisateurs.
Si on devait résumer la cybersécurité, cette phrase siègerait très probablement au panthéon des mesures essentielles. Et, bien entendu, elle trouve aussi tout son sens dans le contexte des Github Actions.
Dans un workflow Github Actions, il est possible de déclarer et d'utiliser des variables, notées comme ceci : ${{ nom_de_la_variable }}. Cela peut être très utile pour récupérer des informations d'autres steps du workflow, des informations sur l'environnement dans lequel le workflow est lancé, etc...
Cependant, ces variables comportent un risque majeur : dans un champ run, elles ne sont pas protégées (escaped en anglais). Cela veut dire qu'une variable Github Actions intégrée aux commandes dans un champ run ne sera pas interprétée comme une chaîne de caractères, mais comme une partie de la commande elle-même ! Cela permet de réaliser une injection de commande.
Prenons par exemple le workflow suivant :
name: "[on_workflow] Input injection"
on:
workflow_dispatch:
jobs:
install-dependency:
runs-on: ubuntu-latest
environment:
name: production
deployment: false
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- run: |
echo "running from branch ${{ github.ref_name }}"Ce workflow comporte une faille majeure : si un attaquant crée une branche avec le nom ";ls${IFS}-al#, la commande dans le champ run devient :
echo "running from branch ";ls${IFS}-al#"Il devient alors possible pour l'attaquant d'exécuter des commandes arbitraires :

L'attaquant hérite alors des droits donnés au workflow, et peut exécuter toutes les actions que celui-ci est capable de faire. Pour peu que le runner soit authentifié à d'autres systèmes, comme par exemple un cloud provider, les effets peuvent alors être catastrophiques.
Afin de prévenir ce risque, il est essentiel de passer les variables Github Actions en variable d'environnement dans les champs run. Ainsi, elles sont correctement protégées et ne présentent plus le même risque. Notre workflow devient alors, dans sa version corrigée :
name: "[on_workflow] Input injection"
on:
workflow_dispatch:
jobs:
install-dependency:
runs-on: ubuntu-latest
environment:
name: production
deployment: false
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- run: |
echo "running from branch $REF_NAME"
env:
REF_NAME: ${{ github.ref_name }}Github recommande de procéder ainsi pour toutes les variables pouvant être contrôlées par l'utilisateur qui lance le workflow. Nous irons même plus loin : il est nécessaire de passer toutes les variables Github Actions en variable d'environnement dans un champ run. En effet, même dans le cas de variable n'étant pas directement contrôlables par l'utilisateur à l'origine du déclenchement du workflow, rien n'indique que ces variables sont totalement dignes de confiance. Elles proviennent bien souvent de steps antérieurs du workflow, pouvant elles aussi avoir été corrompues d'une manière ou d'une autre.
Il suffit de jeter un oeil sur les quelques faiblesses de sécurité évoquées dans cet article pour se convaincre qu'on ne manque pas de façon de se faire compromettre dans l'environnement Github Actions !
fork pour une actionfork fonctionnera au lieu de renvoyer une erreur.Comme nous venons de le voir, il est pertinent de forcer les actions que nous utilisons à un commit spécifique.
Voici donc un cas typique :
- uses: lgerard-pass/setup-dependency/general@2f89a0522d96d6a86b7f2bd524c1a9a0257db1f4À première vue, on utilise les bonnes pratiques et donc nous ne devrions pas rencontrer de problèmes.
D'ailleurs allons voir sur quoi pointe ce commit :
https://github.com/lgerard-pass/setup-dependency/tree/2f89a0522d96d6a86b7f2bd524c1a9a0257db1f4

fork ?Et oui, mon collègue s'est amusé à forker le dépôt et à faire pointer le commit de cette action vers son fork. Instinctivement on aurait pu se dire que seuls les commits appartenant vraiment au dépôt qui contient l'action fonctionneraient. Mais non, un commit appartenant à un fork fonctionnera parfaitement, de manière totalement silencieuse ! Sacré Github.
C'est donc un élément à garder en tête, il faut vérifier que le commit auquel on fait référence de vient pas d'un dépôt malveillant.
forkL'intérêt d'un outil de CI, c'est en particulier de pouvoir lancer tout un ensemble de validations qui vont permettre de s'assurer de la qualité d'une contribution. Il est donc absolument nécessaire de pouvoir lancer le code modifié avant d'intégrer la contribution. Dans la plupart des cas ce n'est pas un problème :
fork, et dans ce cas-là, Github va mettre en place un certain nombre de restrictions, notamment limiter les permissions sur le dépôt correspondant à de la lecture seule.Cependant, certains workflows ont besoin de faire des modifications sur le dépôt original. Par exemple pour automatiquement ajouter des labels à la pull request.
Dans ce cas, il est possible d'utiliser un autre événement que pull_request. Le pull_request_target.
L'élément important à garder en tête, c'est que lorsqu'on utilise l'événement pull_request depuis un fork, certaines restrictions sont mises en place automatiquement. Par exemple, les secrets du dépôt original ne sont pas accessibles et le token fourni par Github n'aura que le droit de lire le contenu du dépôt. Mais lorsqu'on passe sur l'événement pull_request_target, ces restrictions sautent, le workflow lancé depuis un fork aura les mêmes permissions et accès que si il avait été exécuté directement depuis le dépôt original.
Il est donc crucial que le workflow n'exécute jamais le code contenu dans la PR. Par exemple, si le workflow lance des tests sur ce code non validé, en pratique cela donnerait accès à tous les secrets à n'importe quel attaquent qui ouvre une PR.
C'est un cas qui arrive souvent sur des projets open-source, à tel point que Github a un petit nom pour ce type d'attaques : les "pwn request".
Il y a également une page dédiée qui revient en détail sur ce problème et comment l'éviter : https://docs.github.com/en/actions/reference/security/securely-using-pull_request_target.
On vous encourage à la lire pour comprendre le sujet plus en détail.
workflow commence souvent de la même façon. Setup-ci, setup-ça, setup-setup-setup. Et après on démarre.Une des faiblesses de cette approche est que, bien que l'on fixe correctement la version des actions de setup, on ne fixe pas les dépendances téléchargées par ces mêmes actions. Ainsi, une action de setup pourra tout à fait aller récupérer la dernière version d'une librairie, et donc potentiellement une version corrompue.
Prenons par exemple cette action :
name: "[BAD] Pin software"
on:
workflow_dispatch:
jobs:
install-dependency:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: lgerard-pass/setup-dependency/general@1b8e8c87b4277fead0058bc060b2e3231a8a5a67 # v1.0
with:
msg: is this action safe ?Pour laquelle l'action lgerard-pass/setup-dependency/general est :
name: Install SUPER-PRINT
description: An action to install SUPER-PRINT
inputs:
msg:
required: true
runs:
using: composite
steps:
- name: Install & run super-print
shell: bash
run: |
curl -O -L "https://github.com/lgerard-pass/setup-dependency/releases/latest/download/super-print.py"
python3 ./super-print.py "$MSG"
env:
MSG: ${{ inputs.msg }}Dans cette action, la dernière version du programme super-print est téléchargée. Dès lors, quand bien même l'action appelée est elle-même fixée, ce qu'elle télécharge ne l'est pas. Une compromission du programme super-print aurait alors des impacts sur notre CI.
La solution est similaire à ce qu'on peut faire pour fixer les actions, il faut fixer les dépendances. Pour ça, nous avons plusieurs solutions :
On peut utiliser des images Docker dans lesquelles les versions sont fixées, et non téléchargées à chaque fois. En supposant que super-print ait été publié sur un registre Docker, le workflow suivant permet alors de résoudre notre problème :
name: "[GOOD] Pin software"
on:
workflow_dispatch:
jobs:
install-dependency:
runs-on: ubuntu-latest
container:
image: ghcr.io/lgerard-pass/setup-dependency@sha256:ee0952518ea6ef9f7b7844531d5596a7040346c1bfa692a298cc37fc9788ff8a
steps:
- run: python /super-print.py "I am safe !"Ici, dans l'éventualité où le dépôt Github serait compromis et une version malicieuse de super-print mise en ligne, nous serions à l'abri, caché dans notre image Docker où tout est fixe.
D'autres solutions existent, comme le package manager Nix par exemple. C'est un outil qui a pour règle de rendre les builds reproductible à l'octet près. Il se base sur un lockfile pour fixer les dépendances. Par construction, si une dépendance venait à changer, le hash qui est associé changerait lui aussi et Nix refuserait de poursuivre le build. Un tel workflow ressemblerait à quelque chose comme :
jobs:
codegen:
name: "Ensure code is generated"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: cachix/install-nix-action@616559265b40713947b9c190a8ff4b507b5df49b # v31.10.4
- uses: DeterminateSystems/magic-nix-cache-action@565684385bcd71bad329742eefe8d12f2e765b39 # v13
- shell: "nix develop -c bash -e {0}"
Bien que Nix soit pensé pour la reproductibilité, d'autres gestionnaires de paquets ont aussi des fonctionnalités similaires qui permettent de se protéger. En général, si un lockfile contenant un checksum pour chaque dépendance est utilisé pour les télécharger, la possibilité d'une compromission avec ce genre d'attaque réduit drastiquement.
workflows utiliser un service account (GCP)workflow.Certains de nos workflows vont avoir besoin de se connecter à notre compte cloud. Pour y pousser des images docker, ou tout simplement faire les déploiement pour nous, par exemple.
Pour ça, il va falloir, d'une manière ou d'une autre, authentifier notre CI/CD à notre compte cloud. Il y a plusieurs manières de faire et elles dépendent du cloud provider considéré.
Dans le cas de Google Cloud Platform (GCP), il y a notamment deux manières de réaliser cela via un service account.
On veut éviter le premier cas car il nous force à générer un secret qui dure dans le temps. Les secrets c'est comme les brosses à dents, il faut les faire tourner régulièrement. Tu fais bien tourner tes secrets, hein Anakin ?
Heureusement, une documentation assez exhaustive existe chez GCP (disponibles dans les ressources en fin d'article).
Nous ne rentrerons pas dans le détail ici, mais ce qui est important à retenir c'est qu'avec cette méthode la sécurité ne repose plus sur la connaissance d'un secret mais sur une permission définie côté GCP.
La commande cruciale est celle-ci :
gcloud iam service-accounts add-iam-policy-binding "my-service-account@${PROJECT_ID}.iam.gserviceaccount.com" \
--project="${PROJECT_ID}" \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/projects/1234567890/locations/global/workloadIdentityPools/my-pool/attribute.repository/my-org/my-repo"Il va sans dire qu'en pratique on préférera faire ça via Terraform, Pulumi, ou autre outil Infrastructure as Code, plutôt qu'en lançant ce genre de commande manuellement.
La partie importante est le attribute.repository/my-org/my-repo. Ce que ça signifie, c'est que les token signés par Github, et qui contiennent l'attribut attribute.repository = my-org/my-repo auront le droit de s'authentifier en tant que :
my-service-account@${PROJECT_ID}.iam.gserviceaccount.comPour le dire autrement, toute CI lancée sur Github qui se joue dans le contexte du dépôt my-org/my-repo pourra utiliser ce service account sans connaître le moindre secret. Ça inclut évidemment les PR du dépôt (et des dépôts forkés dans le cas d'un event de type pull_request_target). Pour ça il suffit de rajouter dans son workflow la permission id-token: write et d'écrire quelque chose comme :
- uses: 'google-github-actions/auth@v3'
with:
workload_identity_provider: ${{ vars.WORKLOAD_IDENTITY }}
service_account: ${{ vars.SERVICE_ACCOUNT }}Sauf que si ce service account a les permissions pour déployer sur la production, ça signifie qu'en pratique, quiconque avec la possibilité d'écrire dans le dépôt, pourra déployer ce qu'il veut. Ce n'est probablement pas ce qu'on souhaite.
Pour éviter ça, on peut modifier la configuration de notre Workload Identity Pool pour ajouter des attributs personnalisés :
resource "google_iam_workload_identity_pool_provider" "github" {
project = var.project_id
workload_identity_pool_id = google_iam_workload_identity_pool.github.workload_identity_pool_id
workload_identity_pool_provider_id = "github"
display_name = "Github"
description = "GitHub Actions identity pool provider for authenticating to GCP while running on Github Actions"
disabled = false
attribute_condition = <<EOT
assertion.repository_owner_id == "${var.github_owner_id}"
EOT
attribute_mapping = {
"google.subject" = "assertion.sub"
"attribute.actor" = "assertion.actor"
"attribute.aud" = "assertion.aud"
"attribute.repository" = "assertion.repository"
"attribute.environment" = "assertion.environment"
"attribute.reporef" = "assertion.repository + '::' + assertion.ref"
"attribute.repoenvironment" = "assertion.repository + '::' + assertion.environment"
}
oidc {
issuer_uri = "https://token.actions.githubusercontent.com"
}
}Notez les lignes :
"attribute.reporef" = "assertion.repository + '::' + assertion.ref"
"attribute.repoenvironment" = "assertion.repository + '::' + assertion.environment"Celle-ci vont créér de nouveaux attributs que l'on pourra exploiter pour mettre les permissions correctement. Dans la commande gcloud plus haut on pourra donc remplacer :
--member="principalSet://iam.googleapis.com/projects/1234567890/locations/global/workloadIdentityPools/my-pool/attribute.repository/my-org/my-repo"par
--member="principalSet://iam.googleapis.com/projects/1234567890/locations/global/workloadIdentityPools/my-pool/attribute.reporef/my-org/my-repo::my-branch" --member="principalSet://iam.googleapis.com/projects/1234567890/locations/global/workloadIdentityPools/my-pool/attribute.repoenvironment/my-org/my-repo::my-environment"En faisant ça, on pourra limiter les workflows qui ont accès à ce service account à une branche dans le premier cas, ou un environnement (au sens Github) dans le second.
Bien sur cette protection n'est efficace QUE si ces branches ou ces environnements sont eux-mêmes protégés, ce qui n'est pas le cas par défaut.
Pour protéger un environnement, il suffit d'avoir les droits owner sur le dépôt et d'aller dans les paramètres dans la catégories environnements.
Ensuite sur la page d'un environnement on aura quelque chose comme :

Dans ce cas, on autorise l'utilisation du service account uniquement pour les workflows de la branche main, elle-même protégés contre des pushs sauvages et nécessitant une review avant d'y intégrer des changements.
Qui n'a jamais rêvé de pouvoir, l'espace d'un instant, devenir un autre ? Avec Github, c'est possible ! Dans un commit, les champs user.email et user.name sont définis par l'utilisateur, localement. Tout le monde peut donc localement créer des commits avec n'importe quel identité, et jusqu'ici ce n'est pas un problème, tant que cela reste local.
Le problème, c'est que Github laisse faire, et associe le commit à l'identité Github déclarée dans celui-ci sans réaliser la moindre vérification. Ainsi, si un attaquant connaît votre adresse mail, il pourra réaliser des commits en votre nom en plaçant cette adresse dans la variable user.email. Pour récupérer l'adresse mail de la victime, il suffit à l'attaquant de regarder un commit effectué par celle-ci, dans lequel l'adresse mail est inscrite.
Eh non bien sûr, car Github a pensé à tout ! Github met au service des utilisateurs un système d'adresses mail anonymes, qui permettent de ne pas inscrire sa réelle adresse mail dans ses commits, afin de la protéger. Il s'agit là d'une mesure ingénieuse permettant de protéger les données des personnes, mais celle-ci a une faiblesse. En effet, le format de l'adresse mail est <user_id>+<user_name>@users.noreply.github.com. Lorsque Github reçoit un commit, il va alors extraire l'id utilisateur de cette adresse mail, qui correspond à l'id du compte Github, et va associer le commit à ce compte.
La simple connaissance de l'id d'un compte suffit alors pour pousser des commits en lui en rattachant la paternité ! Ce commit sera considéré comme une contribution de sa part sur Github, apparaîtra sur son profil, et l'opération sera presque transparente. Démonstration.
Prenons l'un des utilisateurs Github ayant le plus de stars, sindresorhus. Il est possible de récupérer son id utilisateur via l'API Github à l'URL : https://api.github.com/users/sindresorhus
$ curl -s https://api.github.com/users/sindresorhus | jq .id
170270On peut alors modifier sa configuration git :
$ git config user.email "170270+sindresorhus@users.noreply.github.com"
$ git config user.name "Sindre Sorhus"Tout commit poussé sera alors automatiquement attribué à notre victime :

Oui, Github est un système très permissif. Néanmoins, il met à disposition la possibilité de signer ses commits, ce qui fera apparaître la mention Verified à côté des commits dont l'identité du contributeur a pu être vérifiée.

L'absence de cette pastille doit donc éveiller les soupçons, car cela signifie que le commit n'a pas été ou n'a pas pu être signé. Pour mettre cela encore plus en avant, Github met a disposition le Mode Vigilant, qui permet de marqué explicitement comme Unverified les commits n'ayant pas été ou n'ayant pas pu être signés pour votre compte. Nous recommandons bien évidemment de l'activer, dans Settings -> SSH and GPG keys -> Vigilant Mode. De même, nous vous recommandons d'activer la signature des commits, et de faire activer celle-ci aux contributeurs de vos nombreux projets.
Nous l'avons vu, ce ne sont pas les embûches qui manquent. Github Actions fournit des outils qui, pour certains, peuvent avoir des comportements permissifs, contre-intuitifs, voire encourageant des pratiques contraires aux principes de sécurité. Sa facilité d'accès rend cela encore plus dangereux, car bon nombres d'utilisateur.ices des Github Actions n'auront pas nécessairement à l'esprit les impacts de leurs actions en termes de sécurité.
C'est pourquoi il est essentiel que des pratiques émergent concernant les Github Actions, tout comme elles ont émergé pour les autres technologies qui ont peuplé ces dernières décennies. Aujourd'hui, les attaquants ont affûtés leurs lames et préparé leurs arsenaux contre les CIs et contre Github Actions.
Les coups pleuvent, jour après jour, et certaines forteresses que l'on pensait inexpugnables sont tombées. Protecteurs des systèmes, serons-nous à la hauteur du défi qui nous est lancé ?
Compromission de reviewdog/action-setup
Compromission de tj-actions/changed-files
Documentation Github sur les tags immuables
Héritage des secrets dans Github Actions
Recommandations de Github concernant l'injection de commandes
Cheat sheet sur les injections de commandes
Utilisation de pull_request_target en toute sécurité

Article - Publié le

Article - Publié le