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]Détecter les workflows vulnérables
Section intitulée « Détecter les workflows vulnérables »Plutôt que d'auditer à l'œil, deux scanners repèrent automatiquement les
pull_request_target dangereux.
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 ou la règle
CKV_GHA_1de Checkov.
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.