Lundi 9 novembre, entre 17 h et 19 h à Paris, une partie des workflows GitHub Actions de vos dépôts publics ne démarrera pas. GitHub a confirmé le 2 novembre qu’il bloquera par défaut le déclencheur pull_request_target sur les dépôts publics à partir du 2 décembre, et qu’il fera deux répétitions générales avant : le 9 et le 16 novembre, de 16 h à 18 h UTC. Si vous maintenez un bundle open source, un connecteur public ou le dépôt d’une bibliothèque interne publiée sur Packagist, vous êtes concerné. Ma position : la tentation sera de rouvrir la porte avec une règle d’autorisation, et c’est presque toujours la mauvaise réponse. Ce mois est l’occasion de sortir de ce déclencheur tout ce qui exécute du code, et de ne garder une exception que pour ce qui se contente de lire.
Ce qui change, pour qui et quand
Le changelog du 2 novembre est court et précis. À partir du 2 décembre 2026, Actions bloque les événements pull_request_target dans les dépôts publics, sauf si une règle d’événement (une « Actions event policy ») les autorise explicitement. Sont concernés les dépôts publics sur GitHub Free, Team et Enterprise Cloud. Les dépôts privés et internes ne le sont pas, GitHub Enterprise Server non plus.
La règle n’est pas nouvelle : GitHub l’a annoncée le 17 septembre avec la disponibilité générale des protections d’exécution des workflows, et l’application automatique était d’abord prévue le 2 novembre. Une note ajoutée le 21 octobre l’a repoussée au 2 décembre. D’ici là, la règle tourne en mode évaluation : les workflows s’exécutent avec un avertissement, et la page « Policy insights », sous les règles Actions du dépôt ou de l’organisation, montre les exécutions qui auraient été bloquées.
Les deux coupures de novembre sont le vrai calendrier. Pendant ces fenêtres, les exécutions bloquées échouent avec une annotation d’erreur et le workflow ne tourne pas. En novembre, Paris est à UTC+1 : comptez de 17 h à 19 h, lundi 9 et lundi 16. Si votre étiquetage automatique ou vos tests de contributions externes passent par ce déclencheur, c’est à ce moment-là que vous le découvrirez, si vous n’avez pas regardé avant.
Pourquoi ce déclencheur est dangereux
pull_request_target a été conçu pour un besoin légitime : réagir à une pull request venue d’un fork avec des droits que pull_request n’a pas, par exemple poser un label ou commenter. Pour cela, le job s’exécute dans le contexte de la branche par défaut du dépôt cible, avec son GITHUB_TOKEN et l’accès aux secrets du dépôt et de l’organisation. Tant qu’aucun code du fork ne s’exécute, c’est sûr. La documentation de GitHub le dit ainsi : le danger commence quand on récupère le code de la pull request et qu’on le lance.
Ce motif porte un nom, la « pwn request », et il a causé plusieurs compromissions de chaîne d’approvisionnement. Il prend des formes moins visibles qu’on ne le croit. La documentation cite un actions/checkout qui pointe sur la tête de la pull request, un repository: qui vise le fork, un git fetch ou un gh pr checkout dans une étape run. Et elle insiste sur un point que les équipes PHP sous-estiment : l’exécution ne passe pas seulement par la commande de test. Elle passe par les fichiers de configuration et les dépendances.
Sur un projet PHP, c’est très concret. Un composer install lit le composer.json du fork, donc ses blocs scripts comme post-install-cmd, et ses repositories déclarés à la main. Un vendor/bin/phpunit charge le phpunit.xml.dist du fork et son fichier bootstrap. Il suffit qu’un contributeur malveillant modifie l’un de ces fichiers pour que son code tourne avec vos secrets de base de données de test, votre jeton Packagist ou votre clé de déploiement à portée.
GitHub durcit la plateforme par couches depuis un an. Depuis le 8 décembre 2025, le fichier de workflow et le commit récupéré pour pull_request_target viennent toujours de la branche par défaut. La version 7 d’actions/checkout refuse désormais par défaut de récupérer le code d’une pull request de fork dans un workflow déclenché par pull_request_target ou workflow_run, sauf si l’on ajoute allow-unsafe-pr-checkout: true, un nom choisi pour sauter aux yeux en revue de code. Le blocage du 2 décembre est la couche suivante.
Inventorier vos workflows avant le 9 novembre
Avant de toucher aux réglages, faites l’inventaire dans le code. Ce petit script parcourt les workflows d’un dépôt, repère ceux qui utilisent le déclencheur et signale les trois signaux qui comptent : récupération du code du fork, usage de secrets, absence de bloc permissions.
#!/bin/sh
# Inventaire des workflows déclenchés par pull_request_target
for f in $(grep -lE '^\s*(on:.*)?pull_request_target' .github/workflows/*.y*ml 2>/dev/null); do
echo "== $f"
grep -nE 'head\.(sha|ref)|allow-unsafe-pr-checkout|gh pr checkout|git fetch' "$f" \
&& echo " -> code du fork récupéré : à réécrire" \
|| echo " -> pas de récupération du code du fork détectée"
grep -nE 'secrets\.' "$f" >/dev/null && echo " -> secrets utilisés"
grep -nE '^\s*permissions:' "$f" >/dev/null || echo " -> aucun bloc permissions"
done
Je l’ai exécuté sur un dépôt de démonstration à trois workflows : une CI classique sur pull_request, un étiqueteur sur pull_request_target, et un workflow « tests des forks » qui récupère la tête de la pull request avant composer install et PHPUnit avec un DATABASE_URL tiré des secrets. Voici la sortie :
== .github/workflows/labeler.yml
-> pas de récupération du code du fork détectée
== .github/workflows/tests-fork.yml
9: ref: ${{ github.event.pull_request.head.sha }}
10: allow-unsafe-pr-checkout: true
-> code du fork récupéré : à réécrire
-> secrets utilisés
-> aucun bloc permissions
La CI sur pull_request n’apparaît pas, c’est voulu. Ce tri donne trois familles, et chacune a sa décision.
| Ce que fait le workflow | Décision |
|---|---|
| Lit la pull request sans exécuter son code (labels, taille, titre) | Garder, avec une règle d’autorisation ciblée sur ce fichier |
| Construit ou teste le code du fork, sans secret | Passer sur pull_request |
| Construit ou teste le code du fork, avec secrets ou droits d’écriture | Découper en deux workflows |
| Personne ne sait plus à quoi il sert | Le supprimer, le blocage le confirmera |
Réécrire plutôt que rouvrir
Le cas le plus fréquent dans un dépôt PHP qui a quelques années est le deuxième de la liste : un workflow passé sur pull_request_target un jour où les tests des contributeurs externes échouaient faute de secret, alors que les tests n’en avaient pas vraiment besoin. Revenez à pull_request, avec un jeton en lecture seule, et fournissez les dépendances de test par des services du job (une base MySQL éphémère n’a pas besoin d’un mot de passe secret).
Quand un secret ou un droit d’écriture est vraiment nécessaire, par exemple pour commenter la pull request avec le résultat, séparez le travail en deux. Le premier workflow tourne sur pull_request, exécute le code du fork sans aucun secret et dépose son résultat dans un artefact.
# .github/workflows/tests.yml
name: Tests
on: pull_request
permissions:
contents: read
jobs:
phpunit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- run: composer install --no-interaction --no-progress
- run: vendor/bin/phpunit --log-junit junit.xml
- if: always()
run: echo "${{ github.event.pull_request.number }}" > pr.txt
- if: always()
uses: actions/upload-artifact@v4
with:
name: resultats
path: |
junit.xml
pr.txt
Le second se déclenche sur workflow_run quand le premier se termine. Il a le droit d’écrire, mais il ne récupère jamais le code du fork : il lit l’artefact comme une donnée, et le valide comme telle.
# .github/workflows/tests-rapport.yml
name: Rapport de tests
on:
workflow_run:
workflows: [Tests]
types: [completed]
permissions:
actions: read
pull-requests: write
jobs:
commenter:
if: github.event.workflow_run.event == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: resultats
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ github.token }}
- env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
pr=$(tr -dc '0-9' < pr.txt)
echecs=$(grep -o 'failures="[0-9]*"' junit.xml | head -n 1 | tr -dc '0-9')
gh pr comment "$pr" --body "PHPUnit : ${echecs:-0} échec(s)."
Deux détails comptent. Le numéro de pull request est filtré pour ne garder que des chiffres, parce qu’il vient d’un artefact produit par du code non fiable. Et rien du contenu de l’artefact n’est interpolé avec ${{ }} dans le script : tout passe par des variables shell, ce qui ferme la porte à l’injection de commande. La documentation de GitHub demande explicitement de traiter les artefacts d’un autre workflow comme des données non fiables.
Reste l’étiqueteur. Lui peut légitimement garder pull_request_target. Créez une règle d’événement dans Settings, Actions, Policies, autorisez l’événement et ciblez ce seul fichier de workflow. Ne l’autorisez jamais pour tout le dépôt : le jour où quelqu’un ajoutera un checkout dans un autre workflow, la règle le laissera passer sans bruit.
Les limites
Le script est une heuristique. Il ne voit pas une récupération du fork cachée dans une action composite ou un workflow réutilisable appelé au niveau du job, ni les autres déclencheurs qui portent des secrets et que la documentation cite au même titre, comme issue_comment (le fameux « @bot test this ») et workflow_run. Le blocage du 2 décembre ne les concerne pas ; votre revue, si.
La règle ne protège pas non plus vos dépôts privés. Le risque y est plus faible, puisque seules les personnes qui ont accès au dépôt peuvent le forker, mais le motif est le même, et un compte de développeur compromis suffit. Enfin, les règles d’autorisation existent par fichier de workflow, pas par étape : une exception bien ciblée reste une exception à surveiller à chaque modification de ce fichier.
Cet article s’appuie sur les changelogs et la documentation de GitHub ; je n’ai pas observé de coupure sur un dépôt public réel, la première étant prévue le 9 novembre.
Avant le 9 novembre : par où commencer
- Lancez l’inventaire sur chacun de vos dépôts publics, y compris les vieux bundles que plus personne n’ouvre, et ouvrez la page « Policy insights » pour croiser avec ce que GitHub a déjà détecté.
- Supprimez tout
allow-unsafe-pr-checkout: trueque vous ne savez pas justifier en une phrase. - Repassez sur
pull_requestchaque workflow de tests qui n’a besoin d’aucun secret, et découpez les autres en deux workflows. - N’écrivez une règle d’autorisation que pour les workflows qui lisent sans exécuter, ciblée par fichier, et notez-la dans le README du dépôt.
- Bloquez une heure le lundi 9 novembre à 17 h pour regarder ce qui échoue : c’est le test le moins cher que GitHub vous offrira avant décembre.
La même question vaudra bientôt pour les bots de revue par IA, qui lisent un texte écrit par des inconnus avec un jeton en écriture : c’est le prochain déclencheur à auditer.
Sources
- pull_request_target blocked by default December 2, GitHub Changelog, 2 novembre 2026
- Workflow execution protections in GitHub Actions generally available, GitHub Changelog, 17 septembre 2026, note du 21 octobre 2026
- Securely using pull_request_target, GitHub Docs, consultée le 3 novembre 2026
- Controlling workflow execution, GitHub Docs, consultée le 3 novembre 2026
- actions/checkout, README, GitHub, consulté le 3 novembre 2026
- Actions pull_request_target and environment branch protections changes, GitHub Changelog, 7 novembre 2025
Sur ce sujet, l’offre de l’atelier : Audit gratuit