{"id":517,"date":"2026-03-06T18:09:45","date_gmt":"2026-03-06T17:09:45","guid":{"rendered":"https:\/\/secure-pipelines.com\/?p=517"},"modified":"2026-03-24T12:56:45","modified_gmt":"2026-03-24T11:56:45","slug":"lab-hardening-github-actions-workflows-permissions-pinning-secrets","status":"publish","type":"post","link":"https:\/\/secure-pipelines.com\/fr\/ci-cd-security\/lab-hardening-github-actions-workflows-permissions-pinning-secrets\/","title":{"rendered":"Lab : Durcissement des Workflows GitHub Actions \u2014 Permissions, Pinning et Secrets"},"content":{"rendered":"<h2>Pr\u00e9sentation<\/h2>\n<p>GitHub Actions est devenu la plateforme CI\/CD la plus largement adopt\u00e9e, aussi bien pour les logiciels open source que commerciaux. Cette popularit\u00e9 en fait la surface d\u2019attaque num\u00e9ro un dans le paysage CI\/CD. Des workflows mal configur\u00e9s divulguent r\u00e9guli\u00e8rement des secrets, accordent des permissions excessives et int\u00e8grent du code tiers pouvant \u00eatre modifi\u00e9 de mani\u00e8re silencieuse.<\/p>\n<p>Dans ce lab pratique, vous allez durcir un workflow GitHub Actions volontairement non s\u00e9curis\u00e9 en utilisant les trois techniques les plus efficaces disponibles aujourd\u2019hui :<\/p>\n<ol>\n<li><strong>Permissions minimales<\/strong> \u2014 restreindre le <code>GITHUB_TOKEN<\/code> aux seuls scopes dont chaque job a r\u00e9ellement besoin.<\/li>\n<li><strong>Pinning SHA<\/strong> \u2014 r\u00e9f\u00e9rencer chaque action tierce par son SHA de commit immuable plut\u00f4t que par un tag mutable.<\/li>\n<li><strong>Protection des secrets<\/strong> \u2014 limiter les secrets \u00e0 des environments avec des portes d\u2019approbation et emp\u00eacher les fuites via les pull requests provenant de forks.<\/li>\n<\/ol>\n<p>\u00c0 la fin de ce lab, vous disposerez d\u2019un template de workflow de qualit\u00e9 production que vous pourrez int\u00e9grer dans n\u2019importe quel d\u00e9p\u00f4t.<\/p>\n<h2>Pr\u00e9requis<\/h2>\n<ul>\n<li>Un compte GitHub avec la permission de cr\u00e9er des d\u00e9p\u00f4ts.<\/li>\n<li>Une familiarit\u00e9 de base avec la syntaxe YAML de GitHub Actions (triggers, jobs, steps).<\/li>\n<li>Le CLI <code>gh<\/code> install\u00e9 (optionnel mais utile pour interroger les SHA des actions).<\/li>\n<\/ul>\n<h2>Mise en Place de l\u2019Environnement<\/h2>\n<h3>Cr\u00e9er un D\u00e9p\u00f4t de Test<\/h3>\n<p>Cr\u00e9ez un nouveau d\u00e9p\u00f4t public sur GitHub appel\u00e9 <code>gha-hardening-lab<\/code>. Vous pouvez le faire via l\u2019interface ou avec le CLI :<\/p>\n<pre><code>gh repo create gha-hardening-lab --public --clone\ncd gha-hardening-lab<\/code><\/pre>\n<p>Initialisez un projet Node.js minimal pour que le workflow ait quelque chose \u00e0 compiler :<\/p>\n<pre><code>npm init -y\ncat &lt;&lt;'EOF' &gt; index.js\nconsole.log(\"Hello from the hardening lab\");\nEOF\ngit add -A &amp;&amp; git commit -m \"Initial commit\" &amp;&amp; git push<\/code><\/pre>\n<h3>Le Workflow Initial (Non S\u00e9curis\u00e9)<\/h3>\n<p>Cr\u00e9ez le fichier <code>.github\/workflows\/build.yml<\/code> avec le contenu suivant. Ce workflow est volontairement non s\u00e9curis\u00e9 \u2014 il n\u2019a pas de bloc permissions, utilise des tags mutables et expose les secrets de mani\u00e8re trop large :<\/p>\n<pre><code># .github\/workflows\/build.yml  \u2014 Point de d\u00e9part NON S\u00c9CURIS\u00c9\nname: Build\n\non:\n  push:\n  pull_request_target:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v4\n      - uses: actions\/setup-node@v4\n        with:\n          node-version: 20\n      - run: npm install\n      - run: npm test\n        env:\n          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}\n      - uses: actions\/upload-artifact@v4\n        with:\n          name: build-output\n          path: .\n<\/code><\/pre>\n<p>Commitez et poussez ce fichier. Il s\u2019ex\u00e9cutera avec succ\u00e8s, mais il pr\u00e9sente au moins cinq probl\u00e8mes de s\u00e9curit\u00e9 que vous corrigerez dans les exercices ci-dessous.<\/p>\n<h2>Exercice 1 : Permissions Minimales<\/h2>\n<h3>Le Probl\u00e8me des Permissions par D\u00e9faut<\/h3>\n<p>Lorsqu\u2019un workflow ne d\u00e9clare pas de bloc <code>permissions<\/code>, le <code>GITHUB_TOKEN<\/code> re\u00e7oit les permissions par d\u00e9faut du d\u00e9p\u00f4t. Pour la plupart des d\u00e9p\u00f4ts, cela signifie un <strong>acc\u00e8s en lecture et \u00e9criture \u00e0 chaque scope<\/strong> \u2014 contents, packages, issues, pull requests, deployments, et plus encore. Si un attaquant compromet n\u2019importe quelle \u00e9tape de ce workflow, il h\u00e9rite de toutes ces permissions.<\/p>\n<p>Le principe du moindre privil\u00e8ge exige que vous n\u2019accordiez que les permissions dont chaque job a r\u00e9ellement besoin, et rien de plus.<\/p>\n<h3>\u00c9tape 1 \u2014 D\u00e9finir un D\u00e9faut Restrictif au Niveau Sup\u00e9rieur<\/h3>\n<p>Ajoutez une cl\u00e9 <code>permissions<\/code> de niveau sup\u00e9rieur imm\u00e9diatement apr\u00e8s le bloc <code>on:<\/code>. Cela d\u00e9finit la valeur par d\u00e9faut pour chaque job du workflow :<\/p>\n<pre><code>permissions:\n  contents: read\n<\/code><\/pre>\n<p>Si vous souhaitez commencer avec la valeur par d\u00e9faut la plus restrictive possible puis accorder des permissions par job, vous pouvez utiliser un map vide :<\/p>\n<pre><code>permissions: {}\n<\/code><\/pre>\n<h3>\u00c9tape 2 \u2014 Ajouter des Permissions par Job<\/h3>\n<p>Chaque job peut surcharger la valeur par d\u00e9faut du workflow. N\u2019accordez que ce dont le job a besoin :<\/p>\n<pre><code>jobs:\n  build:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read       # checkout du code\n      actions: read        # lecture des m\u00e9tadonn\u00e9es du workflow\n    steps:\n      - uses: actions\/checkout@v4\n      # ...\n<\/code><\/pre>\n<p>Si un second job doit t\u00e9l\u00e9verser un asset de release, vous lui accorderez <code>contents: write<\/code> sur ce job uniquement \u2014 jamais au niveau du workflow.<\/p>\n<h3>Avant et Apr\u00e8s<\/h3>\n<p><strong>Avant (non s\u00e9curis\u00e9) :<\/strong><\/p>\n<pre><code>name: Build\non:\n  push:\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v4\n      - run: npm install\n<\/code><\/pre>\n<p><strong>Apr\u00e8s (durci) :<\/strong><\/p>\n<pre><code>name: Build\non:\n  push:\n    branches: [main]\n\npermissions:\n  contents: read\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      actions: read\n    steps:\n      - uses: actions\/checkout@v4\n      - run: npm install\n<\/code><\/pre>\n<h3>V\u00e9rifier les Permissions Effectives<\/h3>\n<p>Apr\u00e8s l\u2019ex\u00e9cution du workflow, ouvrez le job dans l\u2019onglet Actions. Cliquez sur l\u2019ic\u00f4ne d\u2019engrenage en haut \u00e0 droite du journal du job et s\u00e9lectionnez <strong>\u00ab Set up job \u00bb<\/strong>. D\u00e9veloppez cette section pour voir les permissions exactes du <code>GITHUB_TOKEN<\/code> qui ont \u00e9t\u00e9 accord\u00e9es. Confirmez que seules <code>contents: read<\/code> et <code>actions: read<\/code> apparaissent.<\/p>\n<p>Vous pouvez \u00e9galement interroger les permissions de mani\u00e8re programmatique dans une \u00e9tape :<\/p>\n<pre><code>- name: Print token permissions\n  run: |\n    curl -s -H \"Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}\" \\\n      https:\/\/api.github.com\/repos\/${{ github.repository }} \\\n      | jq '.permissions'\n<\/code><\/pre>\n<h2>Exercice 2 : Pinning des Actions par SHA<\/h2>\n<h3>Pourquoi les Tags Sont Dangereux<\/h3>\n<p>Lorsque vous \u00e9crivez <code>uses: actions\/checkout@v4<\/code>, vous r\u00e9f\u00e9rencez un tag Git. Les tags sont mutables \u2014 le mainteneur de l\u2019action (ou un attaquant qui compromet son compte) peut supprimer et recr\u00e9er le tag pointant vers un code enti\u00e8rement diff\u00e9rent. Votre workflow ex\u00e9cutera alors silencieusement le nouveau code lors de sa prochaine ex\u00e9cution. Le pinning SHA \u00e9limine ce risque car un SHA de commit est immuable.<\/p>\n<h3>\u00c9tape 1 \u2014 Trouver le SHA d\u2019une Action<\/h3>\n<p>Utilisez le CLI <code>gh<\/code> pour r\u00e9soudre un tag en son SHA de commit :<\/p>\n<pre><code># R\u00e9soudre actions\/checkout@v4 en un SHA de commit\ngh api repos\/actions\/checkout\/git\/ref\/tags\/v4 --jq '.object.sha'<\/code><\/pre>\n<p>Si le tag est annot\u00e9 (la plupart le sont), la commande ci-dessus retourne le SHA de l\u2019objet tag. Vous devez le d\u00e9r\u00e9f\u00e9rencer vers le commit :<\/p>\n<pre><code>TAG_SHA=$(gh api repos\/actions\/checkout\/git\/ref\/tags\/v4 --jq '.object.sha')\ngh api repos\/actions\/checkout\/git\/tags\/$TAG_SHA --jq '.object.sha'<\/code><\/pre>\n<p>Alternativement, visitez le d\u00e9p\u00f4t de l\u2019action sur GitHub, cliquez sur le tag et copiez le SHA complet du commit depuis l\u2019URL ou l\u2019en-t\u00eate du commit.<\/p>\n<h3>\u00c9tape 2 \u2014 Pinner les Actions Courantes<\/h3>\n<p>Remplacez chaque tag mutable par le SHA complet de 40 caract\u00e8res. Ajoutez toujours un commentaire en fin de ligne avec la version pour la lisibilit\u00e9 :<\/p>\n<pre><code>steps:\n  - uses: actions\/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n  - uses: actions\/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0\n    with:\n      node-version: 20\n  - uses: actions\/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3\n    with:\n      path: ~\/.npm\n      key: ${{ runner.os }}-node-${{ hashFiles('**\/package-lock.json') }}\n  - uses: actions\/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2\n    with:\n      name: build-output\n      path: dist\/\n<\/code><\/pre>\n<h3>\u00c9tape 3 \u2014 Automatiser les Mises \u00e0 Jour SHA avec Dependabot<\/h3>\n<p>Le pinning par SHA signifie que vous ne recevez plus les mises \u00e0 jour automatiques bas\u00e9es sur les tags. Dependabot r\u00e9sout ce probl\u00e8me en ouvrant des pull requests chaque fois qu\u2019une action pinn\u00e9e publie une nouvelle version.<\/p>\n<p>Cr\u00e9ez le fichier <code>.github\/dependabot.yml<\/code> :<\/p>\n<pre><code>version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"\/\"\n    schedule:\n      interval: \"weekly\"\n    commit-message:\n      prefix: \"ci\"\n<\/code><\/pre>\n<p>Apr\u00e8s avoir pouss\u00e9 ce fichier, Dependabot analysera vos workflows chaque semaine et ouvrira des PR pour mettre \u00e0 jour les SHA pinn\u00e9s. Chaque PR affiche le diff du code de l\u2019action, vous donnant l\u2019occasion de relire avant de merger.<\/p>\n<p>Si vous pr\u00e9f\u00e9rez Renovate \u00e0 Dependabot, ajoutez un <code>renovate.json<\/code> \u00e0 la racine du d\u00e9p\u00f4t :<\/p>\n<pre><code>{\n  \"$schema\": \"https:\/\/docs.renovatebot.com\/renovate-schema.json\",\n  \"extends\": [\"config:recommended\"],\n  \"github-actions\": {\n    \"enabled\": true\n  }\n}\n<\/code><\/pre>\n<h2>Exercice 3 : Protection des Secrets<\/h2>\n<h3>Secrets de D\u00e9p\u00f4t vs. Secrets d\u2019Environnement<\/h3>\n<p>GitHub propose deux niveaux de stockage des secrets :<\/p>\n<ul>\n<li><strong>Secrets de d\u00e9p\u00f4t<\/strong> \u2014 disponibles pour chaque workflow et chaque job du d\u00e9p\u00f4t. Pratiques mais trop larges.<\/li>\n<li><strong>Secrets d\u2019environnement<\/strong> \u2014 disponibles uniquement pour les jobs qui d\u00e9clarent explicitement <code>environment: &lt;nom&gt;<\/code>. C\u2019est l\u2019approche recommand\u00e9e pour les identifiants sensibles.<\/li>\n<\/ul>\n<h3>\u00c9tape 1 \u2014 Cr\u00e9er un Environnement avec des R\u00e8gles de Protection<\/h3>\n<p>Dans votre d\u00e9p\u00f4t, naviguez vers <strong>Settings \u2192 Environments<\/strong> et cr\u00e9ez un environnement appel\u00e9 <code>production<\/code>. Activez les r\u00e8gles de protection suivantes :<\/p>\n<ol>\n<li><strong>Reviewers requis<\/strong> \u2014 ajoutez au moins un membre de l\u2019\u00e9quipe qui doit approuver les d\u00e9ploiements.<\/li>\n<li><strong>D\u00e9lai d\u2019attente<\/strong> \u2014 ajoutez \u00e9ventuellement un d\u00e9lai (par ex. 5 minutes) pour donner du temps aux reviewers.<\/li>\n<li><strong>Branches de d\u00e9ploiement<\/strong> \u2014 restreignez \u00e0 <code>main<\/code> uniquement.<\/li>\n<\/ol>\n<p>Ajoutez maintenant votre <code>DEPLOY_TOKEN<\/code> en tant que secret \u00e0 l\u2019int\u00e9rieur de cet environnement, et non au niveau du d\u00e9p\u00f4t.<\/p>\n<h3>\u00c9tape 2 \u2014 R\u00e9f\u00e9rencer l\u2019Environnement dans Votre Workflow<\/h3>\n<pre><code>jobs:\n  deploy:\n    runs-on: ubuntu-latest\n    environment: production\n    permissions:\n      contents: read\n    steps:\n      - uses: actions\/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n      - name: Deploy\n        run: .\/deploy.sh\n        env:\n          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}\n<\/code><\/pre>\n<p>La d\u00e9claration <code>environment: production<\/code> signifie que ce job sera mis en pause et attendra l\u2019approbation d\u2019un reviewer avant l\u2019ex\u00e9cution de toute \u00e9tape. Le secret <code>DEPLOY_TOKEN<\/code> n\u2019est disponible que dans cet environnement \u2014 il ne peut pas \u00eatre acc\u00e9d\u00e9 par d\u2019autres jobs ou workflows qui ne d\u00e9clarent pas cet environnement.<\/p>\n<h3>\u00c9tape 3 \u2014 Comprendre le Comportement des Forks<\/h3>\n<p>Les secrets ne sont <strong>pas<\/strong> disponibles pour les workflows d\u00e9clench\u00e9s par des \u00e9v\u00e9nements <code>pull_request<\/code> provenant de forks. Il s\u2019agit d\u2019une fronti\u00e8re de s\u00e9curit\u00e9 critique. Si vous cr\u00e9ez un workflow qui d\u00e9pend de secrets lors des v\u00e9rifications de PR, il \u00e9chouera pour les contributeurs externes :<\/p>\n<pre><code># Cette \u00e9tape \u00e9chouera pour les PR provenant de forks car DEPLOY_TOKEN est vide\n- name: Authenticated API call\n  run: |\n    curl -H \"Authorization: Bearer $DEPLOY_TOKEN\" https:\/\/api.example.com\/health\n  env:\n    DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}\n<\/code><\/pre>\n<p>C\u2019est <strong>voulu<\/strong> \u2014 cela emp\u00eache les forks malveillants d\u2019exfiltrer vos secrets.<\/p>\n<h3>\u00c9tape 4 \u2014 Le Danger de pull_request_target<\/h3>\n<p>Le trigger <code>pull_request_target<\/code> s\u2019ex\u00e9cute dans le contexte du d\u00e9p\u00f4t <strong>de base<\/strong>, ce qui signifie qu\u2019il <em>a<\/em> acc\u00e8s aux secrets. C\u2019est extr\u00eamement dangereux si vous checkoutez \u00e9galement le code HEAD de la PR :<\/p>\n<pre><code># DANGEREUX \u2014 NE FAITES PAS CECI\non:\n  pull_request_target:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n        with:\n          ref: ${{ github.event.pull_request.head.sha }}  # Checkout de code NON FIABLE\n      - run: npm install  # Ex\u00e9cute du code contr\u00f4l\u00e9 par l'attaquant avec acc\u00e8s aux secrets\n        env:\n          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}\n<\/code><\/pre>\n<p>Un attaquant peut modifier <code>package.json<\/code> pour inclure un script <code>postinstall<\/code> qui exfiltre le <code>DEPLOY_TOKEN<\/code>. Ne combinez jamais <code>pull_request_target<\/code> avec un checkout du HEAD de la PR, sauf si vous avez explicitement valid\u00e9 et sandbox\u00e9 le code.<\/p>\n<p><strong>Alternative s\u00fbre :<\/strong> Utilisez le trigger standard <code>pull_request<\/code> pour les workflows de build et de test. R\u00e9servez <code>pull_request_target<\/code> uniquement pour les workflows d\u2019\u00e9tiquetage ou de commentaire qui n\u2019ex\u00e9cutent jamais le code de la PR.<\/p>\n<h3>R\u00e9sum\u00e9 des Bonnes Pratiques<\/h3>\n<ul>\n<li>Stockez les secrets sensibles dans des environments, pas au niveau du d\u00e9p\u00f4t.<\/li>\n<li>Ajoutez des reviewers requis et des restrictions de branche \u00e0 chaque environnement contenant des identifiants de production.<\/li>\n<li>Utilisez le trigger <code>pull_request<\/code> pour la CI. \u00c9vitez <code>pull_request_target<\/code> sauf si vous comprenez pleinement les implications de confiance.<\/li>\n<li>Concevez les workflows de sorte que les jobs n\u00e9cessitant des secrets soient s\u00e9par\u00e9s des jobs ex\u00e9cutant du code non fiable.<\/li>\n<\/ul>\n<h2>Exercice 4 : Durcissement Suppl\u00e9mentaire<\/h2>\n<h3>Emp\u00eacher les Ex\u00e9cutions Dupliqu\u00e9es avec la Concurrence<\/h3>\n<p>Sans politique de concurrence, pousser plusieurs commits en succession rapide g\u00e9n\u00e8re plusieurs ex\u00e9cutions de workflow qui gaspillent des ressources et peuvent provoquer des conditions de concurrence lors du d\u00e9ploiement. Ajoutez un bloc <code>concurrency<\/code> au niveau du workflow :<\/p>\n<pre><code>concurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n<\/code><\/pre>\n<p>Cela annule toute ex\u00e9cution en cours pour le m\u00eame workflow et la m\u00eame branche lorsqu\u2019un nouveau commit est pouss\u00e9.<\/p>\n<h3>D\u00e9finir des Limites de Timeout<\/h3>\n<p>Un job bloqu\u00e9 peut consommer des minutes de runner ind\u00e9finiment. D\u00e9finissez toujours un timeout explicite :<\/p>\n<pre><code>jobs:\n  build:\n    runs-on: ubuntu-latest\n    timeout-minutes: 15\n<\/code><\/pre>\n<p>Choisissez une valeur qui donne \u00e0 votre build suffisamment de marge mais emp\u00eache les processus incontr\u00f4l\u00e9s. Pour la plupart des builds Node.js ou Go, 10 \u00e0 20 minutes sont g\u00e9n\u00e9reux.<\/p>\n<h3>Restreindre les Triggers de Workflow<\/h3>\n<p>\u00c9vitez les triggers g\u00e9n\u00e9riques qui se d\u00e9clenchent sur chaque branche :<\/p>\n<pre><code># Trop large \u2014 s'ex\u00e9cute \u00e0 chaque push sur chaque branche\non:\n  push:\n<\/code><\/pre>\n<p>Au lieu de cela, limitez les triggers aux branches qui comptent :<\/p>\n<pre><code>on:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n<\/code><\/pre>\n<p>Cela r\u00e9duit les ex\u00e9cutions inutiles et limite la surface d\u2019attaque pour les attaques par injection bas\u00e9es sur les branches.<\/p>\n<h3>Ex\u00e9cution Conditionnelle pour les \u00c9tapes Sensibles<\/h3>\n<p>Utilisez les conditions <code>if:<\/code> pour emp\u00eacher les \u00e9tapes sensibles de s\u2019ex\u00e9cuter dans des contextes o\u00f9 elles ne le devraient pas :<\/p>\n<pre><code>- name: Deploy to production\n  if: github.ref == 'refs\/heads\/main' &amp;&amp; github.event_name == 'push'\n  run: .\/deploy.sh\n  env:\n    DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}\n<\/code><\/pre>\n<p>Cela garantit que l\u2019\u00e9tape de d\u00e9ploiement ne s\u2019ex\u00e9cute que lors des pushs vers <code>main<\/code>, jamais sur les pull requests ou autres branches, m\u00eame si le job lui-m\u00eame est d\u00e9clench\u00e9.<\/p>\n<h2>Le Workflow Durci Final<\/h2>\n<p>Voici le workflow durci complet aux c\u00f4t\u00e9s de l\u2019original. Chaque am\u00e9lioration de s\u00e9curit\u00e9 est annot\u00e9e avec un commentaire.<\/p>\n<h3>Original (Non S\u00e9curis\u00e9)<\/h3>\n<pre><code>name: Build\n\non:\n  push:\n  pull_request_target:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v4\n      - uses: actions\/setup-node@v4\n        with:\n          node-version: 20\n      - run: npm install\n      - run: npm test\n        env:\n          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}\n      - uses: actions\/upload-artifact@v4\n        with:\n          name: build-output\n          path: .\n<\/code><\/pre>\n<h3>Durci<\/h3>\n<pre><code>name: Build\n\n# DURCI : Triggers limit\u00e9s \u2014 uniquement la branche main, trigger PR s\u00fbr\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\n# DURCI : Permissions par d\u00e9faut restrictives pour tous les jobs\npermissions:\n  contents: read\n\n# DURCI : Annulation des ex\u00e9cutions dupliqu\u00e9es\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    # DURCI : Timeout explicite\n    timeout-minutes: 15\n    # DURCI : Permissions par job (moindre privil\u00e8ge)\n    permissions:\n      contents: read\n      actions: read\n    steps:\n      # DURCI : Toutes les actions pinn\u00e9es par SHA\n      - uses: actions\/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n      - uses: actions\/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0\n        with:\n          node-version: 20\n      - run: npm install\n      - run: npm test\n        # DURCI : Aucun secret expos\u00e9 dans le job de build\/test\n      - uses: actions\/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2\n        with:\n          name: build-output\n          path: dist\/\n\n  deploy:\n    needs: build\n    runs-on: ubuntu-latest\n    # DURCI : S'ex\u00e9cute uniquement lors d'un push vers main\n    if: github.ref == 'refs\/heads\/main' &amp;&amp; github.event_name == 'push'\n    # DURCI : Secrets prot\u00e9g\u00e9s derri\u00e8re un environnement avec reviewers requis\n    environment: production\n    timeout-minutes: 10\n    permissions:\n      contents: read\n    steps:\n      - uses: actions\/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n      - name: Deploy\n        run: .\/deploy.sh\n        env:\n          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}\n<\/code><\/pre>\n<h2>Tests de Rupture (\u00c9chec Intentionnel)<\/h2>\n<p>Pour consolider votre compr\u00e9hension, cassez d\u00e9lib\u00e9r\u00e9ment le workflow durci et observez les cons\u00e9quences.<\/p>\n<h3>Test 1 \u2014 Supprimer le Bloc Permissions<\/h3>\n<p>Supprimez la cl\u00e9 <code>permissions:<\/code> de niveau sup\u00e9rieur et les permissions par job. Poussez et ex\u00e9cutez le workflow. Il r\u00e9ussira toujours, mais si vous inspectez l\u2019\u00e9tape de configuration du job, vous verrez que le token a maintenant un acc\u00e8s <strong>en lecture et \u00e9criture<\/strong> \u00e0 chaque scope. Une \u00e9tape compromise pourrait pousser du code, supprimer des branches ou modifier des releases.<\/p>\n<h3>Test 2 \u2014 Utiliser une Action Non Pinn\u00e9e<\/h3>\n<p>Remettez une action avec une r\u00e9f\u00e9rence par tag :<\/p>\n<pre><code>- uses: actions\/checkout@v4\n<\/code><\/pre>\n<p>Le workflow s\u2019ex\u00e9cute toujours. Mais si le tag <code>v4<\/code> est un jour d\u00e9plac\u00e9 vers un commit malveillant, votre workflow ex\u00e9cutera ce code sans avertissement. Il n\u2019y a pas de trace d\u2019audit \u2014 le tag r\u00e9sout simplement vers un SHA diff\u00e9rent. Re-pinnez-le au SHA apr\u00e8s ce test.<\/p>\n<h3>Test 3 \u2014 Acc\u00e9der aux Secrets de Production depuis une PR<\/h3>\n<p>Cr\u00e9ez une branche de fonctionnalit\u00e9 et ouvrez une pull request. Le job <code>deploy<\/code> ne s\u2019ex\u00e9cutera pas \u00e0 cause de la condition <code>if:<\/code>. M\u00eame si vous supprimez la condition, le secret d\u2019environnement <code>DEPLOY_TOKEN<\/code> est prot\u00e9g\u00e9 par l\u2019environnement <code>production<\/code>, qui restreint le d\u00e9ploiement \u00e0 la branche <code>main<\/code> et requiert l\u2019approbation d\u2019un reviewer. La valeur du secret sera vide dans le contexte de la PR.<\/p>\n<p>C\u2019est exactement le comportement souhait\u00e9 \u2014 les secrets ne sont jamais disponibles dans des contextes non fiables.<\/p>\n<h2>Nettoyage<\/h2>\n<p>Lorsque vous avez termin\u00e9 le lab, supprimez le d\u00e9p\u00f4t de test pour \u00e9viter d\u2019encombrer votre compte :<\/p>\n<pre><code>gh repo delete gha-hardening-lab --yes<\/code><\/pre>\n<p>Si vous avez utilis\u00e9 un fork d\u2019un projet existant, vous pouvez le r\u00e9initialiser \u00e0 la place :<\/p>\n<pre><code>git checkout main\ngit reset --hard origin\/main\ngit push --force\n<\/code><\/pre>\n<h2>Points Cl\u00e9s \u00e0 Retenir<\/h2>\n<ul>\n<li><strong>D\u00e9clarez toujours un bloc <code>permissions<\/code>.<\/strong> D\u00e9finissez une valeur par d\u00e9faut restrictive au niveau du workflow et n\u2019accordez des scopes suppl\u00e9mentaires par job que si n\u00e9cessaire.<\/li>\n<li><strong>Pinnez chaque action tierce par son SHA complet.<\/strong> Les tags sont mutables et peuvent \u00eatre silencieusement redirig\u00e9s vers du code malveillant.<\/li>\n<li><strong>Utilisez Dependabot ou Renovate<\/strong> pour garder les SHA pinn\u00e9s \u00e0 jour automatiquement.<\/li>\n<li><strong>Stockez les secrets sensibles dans des environments<\/strong> avec des reviewers requis et des restrictions de branche \u2014 jamais au niveau du d\u00e9p\u00f4t.<\/li>\n<li><strong>Utilisez <code>pull_request<\/code>, pas <code>pull_request_target<\/code><\/strong>, pour les workflows qui compilent ou testent le code des PR. Le trigger <code>pull_request_target<\/code> accorde l\u2019acc\u00e8s aux secrets \u00e0 du code potentiellement non fiable.<\/li>\n<li><strong>Ajoutez <code>concurrency<\/code>, <code>timeout-minutes<\/code> et des triggers limit\u00e9s aux branches<\/strong> pour r\u00e9duire le gaspillage de ressources et r\u00e9duire la surface d\u2019attaque.<\/li>\n<\/ul>\n<h2>Prochaines \u00c9tapes<\/h2>\n<p>Poursuivez votre apprentissage de la s\u00e9curit\u00e9 CI\/CD avec ces guides connexes :<\/p>\n<ul>\n<li><a href=\"https:\/\/secure-pipelines.com\/fr\/ci-cd-security\/ci-cd-execution-models-trust-assumptions-security-guide-2\/\">Mod\u00e8les d\u2019Ex\u00e9cution CI\/CD et Hypoth\u00e8ses de Confiance<\/a> \u2014 Comprenez comment les diff\u00e9rentes plateformes CI\/CD mod\u00e9lisent la confiance et o\u00f9 les fronti\u00e8res se brisent.<\/li>\n<li><a href=\"https:\/\/secure-pipelines.com\/fr\/ci-cd-security\/separation-of-duties-least-privilege-ci-cd-pipelines\/\">S\u00e9paration des Responsabilit\u00e9s et Moindre Privil\u00e8ge dans les Pipelines CI\/CD<\/a> \u2014 Concevez des pipelines o\u00f9 aucun acteur ou identifiant unique n\u2019a plus d\u2019acc\u00e8s que n\u00e9cessaire.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Pr\u00e9sentation GitHub Actions est devenu la plateforme CI\/CD la plus largement adopt\u00e9e, aussi bien pour les logiciels open source que commerciaux. Cette popularit\u00e9 en fait la surface d\u2019attaque num\u00e9ro un dans le paysage CI\/CD. Des workflows mal configur\u00e9s divulguent r\u00e9guli\u00e8rement des secrets, accordent des permissions excessives et int\u00e8grent du code tiers pouvant \u00eatre modifi\u00e9 de &#8230; <a title=\"Lab : Durcissement des Workflows GitHub Actions \u2014 Permissions, Pinning et Secrets\" class=\"read-more\" href=\"https:\/\/secure-pipelines.com\/fr\/ci-cd-security\/lab-hardening-github-actions-workflows-permissions-pinning-secrets\/\" aria-label=\"En savoir plus sur Lab : Durcissement des Workflows GitHub Actions \u2014 Permissions, Pinning et Secrets\">Lire la suite<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[49,52],"tags":[],"post_folder":[],"class_list":["post-517","post","type-post","status-publish","format-standard","hentry","category-ci-cd-security","category-github-actions"],"_links":{"self":[{"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/posts\/517","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/comments?post=517"}],"version-history":[{"count":2,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/posts\/517\/revisions"}],"predecessor-version":[{"id":574,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/posts\/517\/revisions\/574"}],"wp:attachment":[{"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/media?parent=517"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/categories?post=517"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/tags?post=517"},{"taxonomy":"post_folder","embeddable":true,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/post_folder?post=517"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}