L'événement pull_request_target est l'un des plus dangereux de GitHub
Actions. Il exécute du code avec accès aux secrets, même pour les PR de
forks. Mal utilisé, il ouvre la porte à l'exfiltration de vos credentials.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Distinguer
pull_requestetpull_request_targetet leur accès aux secrets - Reconnaître l'attaque classique :
pull_request_target+ checkout du fork - Identifier les usages légitimes : labelling, commentaires de PR
- Appliquer le pattern en deux workflows relié par
workflow_run - Détecter les workflows vulnérables avec Scorecard et Checkov
Différence entre pull_request et pull_request_target
Section intitulée « Différence entre pull_request et pull_request_target »Ces deux déclencheurs réagissent à une pull request, mais leur modèle de sécurité est opposé. Comprendre cette différence est la base de tout le reste.
| Événement | Accès aux secrets | Code exécuté | Contexte |
|---|---|---|---|
pull_request | ❌ Non (forks) | Code de la PR | Fork |
pull_request_target | ✅ Oui | Code de la branche cible | Base repo |
pull_request (sûr par défaut)
Section intitulée « pull_request (sûr par défaut) »Avec pull_request, le workflow s'exécute dans le contexte du fork : il
voit le code de la PR mais n'a aucun accès aux secrets. C'est le
comportement sûr par défaut.
on: pull_request: types: [opened, synchronize]
jobs: test: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false # Le code vient de la PR (fork potentiel) # Pas d'accès aux secrets pour les forks = sûr - run: npm testpull_request_target (dangereux)
Section intitulée « pull_request_target (dangereux) »Avec pull_request_target, le workflow s'exécute dans le contexte du dépôt
cible : il a accès aux secrets. Tant qu'il ne checkout pas le code du
fork, cela reste maîtrisé.
on: pull_request_target: types: [opened, synchronize]
jobs: test: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false # Le code vient de la branche cible (main) # Accès aux secrets = OK si on ne checkout pas le code du fork - run: npm testL'attaque classique
Section intitulée « L'attaque classique »Le danger survient quand vous combinez pull_request_target avec un checkout
du code de la PR :
# ❌ DANGEREUX : accès aux secrets + code du forkon: pull_request_target:
jobs: build: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.pull_request.head.sha }} # Code du fork !
# Ce script vient du fork et a accès aux secrets - run: npm test env: API_TOKEN: ${{ secrets.API_TOKEN }} # Exfiltrable !Scénario d'attaque :
- L'attaquant fork votre repo
- Il modifie
package.jsonpour ajouter un script malveillant danstest - Il ouvre une PR
- Votre workflow
pull_request_targets'exécute - Le script malveillant envoie
$API_TOKENvers un serveur externe
Cas d'usage légitimes
Section intitulée « Cas d'usage légitimes »Le déclencheur n'est pas à bannir : il a des usages légitimes, tous caractérisés par une même propriété, ils n'exécutent jamais le code du fork.
1. Labeling automatique (sans checkout du fork)
Section intitulée « 1. Labeling automatique (sans checkout du fork) »Étiqueter une PR selon les fichiers modifiés ne nécessite que l'API GitHub, jamais le code du fork.
# ✅ Sûr : pas de checkout du code du forkon: pull_request_target: types: [opened]
permissions: {}
jobs: label: runs-on: ubuntu-24.04 permissions: contents: read pull-requests: write steps: - uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }}2. Commenter les PRs
Section intitulée « 2. Commenter les PRs »Poster un message de bienvenue passe par actions/github-script, qui appelle
l'API GitHub sans rien cloner.
# ✅ Sûr : pas d'exécution de code du forkon: pull_request_target: types: [opened]
permissions: {}
jobs: welcome: runs-on: ubuntu-24.04 permissions: pull-requests: write steps: - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body: 'Merci pour votre contribution !' })Pattern sécurisé : workflow en deux parties
Section intitulée « Pattern sécurisé : workflow en deux parties »Si vous devez exécuter du code du fork et avoir accès aux secrets, séparez le travail en deux workflows distincts.
Workflow 1 : Build sans secrets
Section intitulée « Workflow 1 : Build sans secrets »Le premier workflow se déclenche sur pull_request : il build le code du
fork mais sans aucun secret à voler.
name: PR Build
on: pull_request: # Pas de secrets, code du fork
permissions: {}
jobs: build: runs-on: ubuntu-24.04 permissions: contents: read steps: - name: Récupérer le code de la PR uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - name: Builder le projet run: npm ci && npm run build - name: Publier l'artefact de build uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: build-output path: dist/Workflow 2 : Déploiement avec secrets
Section intitulée « Workflow 2 : Déploiement avec secrets »Le second se déclenche sur workflow_run, une fois le build terminé : il a les
secrets mais ne manipule que l'artefact, jamais le code source du fork.
name: Deploy Preview
on: workflow_run: workflows: ["PR Build"] types: [completed]
permissions: {}
jobs: deploy: if: github.event.workflow_run.conclusion == 'success' runs-on: ubuntu-24.04 permissions: actions: read # Télécharger l'artefact de l'autre run pull-requests: write # Commenter l'URL de preview steps: # Télécharger l'artefact (pas le code source) - name: Récupérer l'artefact de build uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: build-output path: dist/ run-id: ${{ github.event.workflow_run.id }} github-token: ${{ github.token }} # Déployer avec les secrets - name: Déployer la preview run: ./deploy-preview.sh env: DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}Pourquoi c'est sûr :
- Le premier workflow exécute le code du fork, mais sans accès aux secrets
- Le second workflow a accès aux secrets, mais ne télécharge que l'artefact (pas le code source du fork)
- L'artefact ne peut pas contenir de code exécutable (juste les fichiers buildés)
Règles de sécurité
Section intitulée « Règles de sécurité »En résumé, la sécurité de pull_request_target tient en une règle, ne
jamais exécuter le code du fork avec les secrets, et trois patterns sûrs.
❌ Ne jamais faire
Section intitulée « ❌ Ne jamais faire »Le combo mortel : checkout du code du fork et secrets exposés dans le même job.
on: pull_request_target
jobs: build: runs-on: ubuntu-24.04 steps: # Checkout du code du fork - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }}
# Exécution de scripts du fork avec secrets - run: ./scripts/build.sh env: SECRET: ${{ secrets.SECRET }}✅ Patterns sûrs
Section intitulée « ✅ Patterns sûrs »Trois patterns couvrent tous les besoins réels sans jamais exécuter le code du fork avec les secrets.
- Pas de checkout du fork :
on: pull_request_target
jobs: label: runs-on: ubuntu-24.04 steps: # Actions GitHub uniquement, pas de code du fork - uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0- Checkout de la branche cible uniquement :
on: pull_request_target
jobs: analyze: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # Sans ref = checkout de la branche cible (main) - run: ./trusted-script.sh # Script de main, pas du fork- Séparation build/deploy via workflow_run :
on: workflow_run: workflows: ["Untrusted Build"] types: [completed]Le garde-fou de checkout v7
Section intitulée « Le garde-fou de checkout v7 »Bonne nouvelle de mi-2026 : actions/checkout@v7 ajoute enfin une protection
par défaut contre la pwn request. Quand un workflow pull_request_target (ou
certains workflow_run) tente de checkouter la ref du fork (la branche ou le SHA
de la PR), checkout refuse désormais l'opération au lieu de tirer du code non
fiable dans un runner aux permissions élevées.
Ce qu'il faut retenir :
- Le pattern dangereux (
ref: ${{ github.event.pull_request.head.sha }}souspull_request_target) échoue par défaut. C'est exactement l'anti-pattern décrit plus haut, désormais bloqué nativement. - Un nouvel input
allow-unsafe-pr-checkoutréautorise explicitement l'ancien comportement. Traitez-le comme une exception sensible : à justifier, isoler et relire, jamais activé par confort. - Le changement est rétroporté le 16 juillet 2026 sur les versions majeures
supportées ; les tags flottants (
@v4,@v3) héritent automatiquement de la protection.
Source : l'analyse de Socket sur le blocage des checkouts pull_request_target.
Détecter les workflows vulnérables
Section intitulée « Détecter les workflows vulnérables »Plutôt que d'auditer à l'œil, plusieurs scanners repèrent automatiquement les
pull_request_target dangereux.
Avec plumber
Section intitulée « Avec plumber »plumber, le scanner de pipelines CI/CD,
signale ce pattern via le contrôle ISSUE-804 (checkout de code non fiable sous
pull_request_target).
plumber analyze --github-url https://github.com/votre-org/votre-repoLe détail du contrôle et sa remédiation sont documentés ici : ISSUE-804 dans la doc plumber.
Avec Scorecard
Section intitulée « Avec Scorecard »Le check Dangerous-Workflow d'OpenSSF Scorecard signale précisément ce pattern.
scorecard --local . --checks Dangerous-Workflow --show-detailsAvec Checkov
Section intitulée « Avec Checkov »Checkov couvre la même règle via l'identifiant CKV_GHA_1.
checkov -d .github/workflows/ --check CKV_GHA_1Pattern à rechercher
Section intitulée « Pattern à rechercher »Pour un audit manuel rapide, deux grep suffisent à lister les workflows à
inspecter de près.
# Chercher les workflows potentiellement dangereuxgrep -r "pull_request_target" .github/workflows/grep -r "github.event.pull_request.head" .github/workflows/À retenir
Section intitulée « À retenir »pull_requests'exécute dans le contexte du fork sans secrets ;pull_request_targets'exécute dans le dépôt cible avec les secrets.- L'attaque classique combine
pull_request_targetet un checkout du code du fork (ref: ...head.sha), le code de l'attaquant tourne alors avec vos secrets. - Les usages légitimes de
pull_request_targetn'exécutent jamais le code du fork : labelling et commentaires passent par l'API GitHub seule. - Pour builder le code d'un fork et utiliser des secrets, séparez en deux workflows reliés par
workflow_run, le second ne consomme qu'un artefact. - Auditez avec le check Dangerous-Workflow de Scorecard, la règle
CKV_GHA_1de Checkov, ou le contrôle ISSUE-804 de plumber.
Prochaines étapes
Section intitulée « Prochaines étapes »Pour le détail des vecteurs d'exploitation, voir l'analyse de référence du GitHub Security Lab : Keeping your GitHub Actions secure.