Aller au contenu
CI/CD & Automatisation medium

Sécuriser pull_request_target

11 min de lecture

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.

  • Distinguer pull_request et pull_request_target et 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énementAccès aux secretsCode exécutéContexte
pull_request❌ Non (forks)Code de la PRFork
pull_request_target✅ OuiCode de la branche cibleBase repo

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 test

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 test

Le danger survient quand vous combinez pull_request_target avec un checkout du code de la PR :

# ❌ DANGEREUX : accès aux secrets + code du fork
on:
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 :

  1. L'attaquant fork votre repo
  2. Il modifie package.json pour ajouter un script malveillant dans test
  3. Il ouvre une PR
  4. Votre workflow pull_request_target s'exécute
  5. Le script malveillant envoie $API_TOKEN vers un serveur externe

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.

É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 fork
on:
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 }}

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 fork
on:
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 !'
})

Si vous devez exécuter du code du fork et avoir accès aux secrets, séparez le travail en deux workflows distincts.

Le premier workflow se déclenche sur pull_request : il build le code du fork mais sans aucun secret à voler.

.github/workflows/pr-build.yml
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/

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.

.github/workflows/pr-deploy-preview.yml
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)

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.

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 }}

Trois patterns couvrent tous les besoins réels sans jamais exécuter le code du fork avec les secrets.

  1. 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
  1. 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
  1. Séparation build/deploy via workflow_run :
on:
workflow_run:
workflows: ["Untrusted Build"]
types: [completed]

Plutôt que d'auditer à l'œil, deux scanners repèrent automatiquement les pull_request_target dangereux.

Le check Dangerous-Workflow d'OpenSSF Scorecard signale précisément ce pattern.

Fenêtre de terminal
scorecard --local . --checks Dangerous-Workflow --show-details

Checkov couvre la même règle via l'identifiant CKV_GHA_1.

Fenêtre de terminal
checkov -d .github/workflows/ --check CKV_GHA_1

Pour un audit manuel rapide, deux grep suffisent à lister les workflows à inspecter de près.

Fenêtre de terminal
# Chercher les workflows potentiellement dangereux
grep -r "pull_request_target" .github/workflows/
grep -r "github.event.pull_request.head" .github/workflows/
  • pull_request s'exécute dans le contexte du fork sans secrets ; pull_request_target s'exécute dans le dépôt cible avec les secrets.
  • L'attaque classique combine pull_request_target et 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_target n'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_1 de Checkov.

Pour le détail des vecteurs d'exploitation, voir l'analyse de référence du GitHub Security Lab : Keeping your GitHub Actions secure.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn