Aller au contenu

Premier pipeline de sécurité avec GitHub Actions ou GitLab CI

Objectifs pédagogiques

À la fin de ce TP, vous devez être capables de :

  • expliquer ce qu'est un pipeline et quand il se déclenche
  • ajouter pas à pas trois contrôles de sécurité automatiques : secrets, code et dépendances
  • générer l'inventaire des composants du projet (SBOM)
  • lire les rapports produits et comprendre pourquoi le pipeline échoue
  • prouver que le pipeline bloque l'ajout d'un secret

Prérequis

  • Un compte GitHub ou GitLab.com : choisissez l'onglet correspondant dans chaque étape, les deux chemins sont équivalents
  • Git installé sur votre poste
  • Aucune installation d'outil de sécurité : tout tourne dans le pipeline

Testé

Les trois analyses ont été jouées sur le dépôt SecurePay avec les versions de ce TP. Gitleaks trouve 2 clés d'API, Semgrep signale l'eval(), et Trivy relève 16 vulnérabilités de niveau HIGH dans les dépendances : les trois jobs échouent, comme attendu. Le workflow GitHub Actions a aussi été vérifié avec actionlint.

Contexte

Vous partez du dépôt securepay-api, une API de paiement écrite sans précaution. Plutôt que de chercher les failles à la main à chaque modification, vous allez demander à la forge de le faire automatiquement, à chaque push.

flowchart LR
    P["git push"] --> S["Secrets\nGitleaks"]
    P --> C["Code\nSemgrep"]
    P --> D["Dépendances et SBOM\nTrivy"]
    S --> R{"Tout est vert ?"}
    C --> R
    D --> R
    R -->|"oui"| OK["Modification acceptée"]
    R -->|"non"| KO["Pipeline en échec :\nlire le rapport, corriger"]

Atelier 1 : Récupérer le projet

Travail demandé

  1. Sur la page du dépôt securepay-api, cliquez sur Fork pour en créer une copie sur votre compte.
  2. Clonez votre copie :

    git clone https://github.com/<votre-compte>/securepay-api.git
    cd securepay-api
    
  1. Sur GitLab.com, créez un projet par New project → Import project → Repository by URL, avec l'adresse https://github.com/Momotoculteur/securepay-api.git.
  2. Clonez votre projet :

    git clone https://gitlab.com/<votre-compte>/securepay-api.git
    cd securepay-api
    

Critère de réussite

Le projet est sur votre forge et sur votre poste.

Atelier 2 : Un premier job, la détection de secrets

Un pipeline est une suite de jobs que la forge exécute automatiquement. Chaque job démarre une machine propre, récupère votre code et lance des commandes. Si une commande échoue, le job échoue.

Travail demandé

  1. Créez le fichier du pipeline avec un seul job : Gitleaks analyse tout l'historique Git à la recherche de clés, de jetons et de mots de passe.

Fichier .github/workflows/securite.yml :

name: securite

on:
  push:
    branches: [main]
  pull_request:

permissions:
  contents: read

jobs:
  secrets:
    name: Secrets (Gitleaks)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with:
          fetch-depth: 0          # tout l'historique, pas seulement le dernier commit
      - name: Gitleaks sur tout l'historique
        run: |
          docker run --rm -v "$PWD:/repo" ghcr.io/gitleaks/gitleaks:v8.30.1 \
            git /repo --report-format json --report-path /repo/gitleaks.json
      - uses: actions/upload-artifact@v7
        if: always()              # garder le rapport même en cas d'échec
        with:
          name: rapport-gitleaks
          path: gitleaks.json

Fichier .gitlab-ci.yml :

stages: [securite]

variables:
  GIT_DEPTH: "0"                  # tout l'historique, pas seulement le dernier commit

secrets:gitleaks:
  stage: securite
  image:
    name: ghcr.io/gitleaks/gitleaks:v8.30.1
    entrypoint: [""]
  script:
    - gitleaks git --report-format json --report-path gitleaks.json .
  artifacts:
    when: always                  # garder le rapport même en cas d'échec
    paths: [gitleaks.json]
  1. Poussez le fichier, puis ouvrez l'onglet Actions (GitHub) ou Build → Pipelines (GitLab).

  2. Le job échoue : téléchargez le rapport et repérez les secrets trouvés, avec leur fichier et leur commit.

Ce que vous devez observer

  • Gitleaks trouve deux clés Stripe, dans src/routes/payments.js et dans .env.example
  • Le job est rouge : c'est le comportement voulu, le pipeline vous a protégé

Un secret trouvé est un secret compromis

Supprimer la ligne ne suffit pas : la clé reste dans l'historique Git. Dans la réalité, il faut d'abord révoquer la clé chez le fournisseur, puis la remplacer. Ici, les clés sont factices.

Critère de réussite

Le pipeline se déclenche seul à chaque push, et vous savez lire le rapport de Gitleaks.

Atelier 3 : Analyser le code

L'analyse statique (SAST) lit le code sans l'exécuter et repère les constructions dangereuses.

Travail demandé

  1. Ajoutez un deuxième job, Semgrep, avec les règles de sécurité pour JavaScript :

À ajouter sous jobs: :

  sast:
    name: Code (Semgrep)
    runs-on: ubuntu-latest
    container:
      image: semgrep/semgrep:1.183.0
    steps:
      - uses: actions/checkout@v7
      - name: Analyse statique
        run: semgrep scan --config p/javascript --metrics=off --error --json --output semgrep.json
      - uses: actions/upload-artifact@v7
        if: always()
        with:
          name: rapport-semgrep
          path: semgrep.json

À ajouter à la suite du fichier :

sast:semgrep:
  stage: securite
  image: semgrep/semgrep:1.183.0
  script:
    - semgrep scan --config p/javascript --metrics=off --error --json --output semgrep.json
  artifacts:
    when: always
    paths: [semgrep.json]
  1. Poussez, puis lisez le rapport : quelle ligne est signalée, et pourquoi est-elle dangereuse ?

Ce que vous devez observer

  • Semgrep signale src/routes/payments.js, à la ligne qui appelle eval() sur une formule envoyée par l'utilisateur : n'importe qui pourrait faire exécuter du code au serveur
  • L'option --error fait échouer le job dès qu'une faille est trouvée

Critère de réussite

Les deux jobs tournent en parallèle, et vous savez expliquer la faille signalée par Semgrep.

Atelier 4 : Les dépendances et le SBOM

Votre application utilise des bibliothèques écrites par d'autres, qui en utilisent elles-mêmes d'autres. Une faille dans l'une d'elles est une faille de votre application.

Travail demandé

  1. Ajoutez un troisième job, Trivy. Il génère d'abord le SBOM (l'inventaire des composants), puis cherche les vulnérabilités connues de niveau HIGH et CRITICAL :
  dependances:
    name: Dépendances et SBOM (Trivy)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - name: SBOM CycloneDX
        run: docker run --rm -v "$PWD:/src" aquasec/trivy:0.76.0 fs -q --format cyclonedx --output /src/sbom.cdx.json /src
      - name: Vulnérabilités HIGH et CRITICAL
        run: docker run --rm -v "$PWD:/src" aquasec/trivy:0.76.0 fs -q --scanners vuln --severity HIGH,CRITICAL --exit-code 1 /src
      - uses: actions/upload-artifact@v7
        if: always()
        with:
          name: sbom
          path: sbom.cdx.json
dependances:trivy:
  stage: securite
  image:
    name: aquasec/trivy:0.76.0
    entrypoint: [""]
  script:
    - trivy fs -q --format cyclonedx --output sbom.cdx.json .
    - trivy fs -q --scanners vuln --severity HIGH,CRITICAL --exit-code 1 .
  artifacts:
    when: always
    paths: [sbom.cdx.json]
  1. Lisez le journal du job : quelles bibliothèques sont vulnérables ? Lesquelles n'apparaissent pas dans package.json ?

  2. Ouvrez sbom.cdx.json : combien de composants contient le projet ?

Ce que vous devez observer

  • Trivy relève des vulnérabilités HIGH dans axios, jsonwebtoken et lodash, mais aussi dans follow-redirects et body-parser, qui ne sont pas dans package.json : ce sont des dépendances indirectes
  • Le SBOM recense environ 85 composants, alors que package.json n'en déclare qu'une poignée

Pourquoi HIGH et pas seulement CRITICAL ?

Sur ce projet, Trivy ne trouve aucune vulnérabilité CRITICAL : un seuil limité à CRITICAL laisserait passer 16 vulnérabilités HIGH. Le seuil se choisit en regardant ce qu'il bloque réellement.

Versions épinglées

Toutes les images et les actions portent une version précise, jamais latest. En mars 2026, l'image Trivy publiée sous les tags latest et 0.69.4 a été remplacée par une version compromise : un pipeline qui épingle une version vérifiée n'aurait pas été touché. Pour aller plus loin, épinglez les actions GitHub par l'empreinte de leur commit.

Critère de réussite

Le pipeline compte trois contrôles et conserve le SBOM du projet à chaque exécution.

Atelier 5 : Prouver que le pipeline protège

Travail demandé

  1. Créez une branche, ajoutez un faux jeton dans un fichier, et ouvrez une Pull Request (GitHub) ou une Merge Request (GitLab) :
git switch -c test-secret
echo 'GITHUB_TOKEN=ghp_FauxJetonDeTest0123456789abcdefABCD' > config.txt
git add config.txt
git commit -m "test : ajout volontaire d'un faux secret"
git push -u origin test-secret
  1. Observez le job de secrets sur la PR ou la MR, puis retrouvez le faux jeton dans le rapport.

  2. Empêchez la fusion tant que le pipeline n'est pas vert :

Settings → Branches (ou Rules → Rulesets) : protégez main et exigez la réussite des jobs du workflow securite avant toute fusion.

Settings → Merge requests : activez Pipelines must succeed. Dans Settings → Repository → Protected branches, interdisez le push direct sur main.

  1. Supprimez la branche de test.

Critère de réussite

Une modification qui introduit un secret ne peut pas être fusionnée.

Pour aller plus loin

  • Corrigez les failles une par une jusqu'à obtenir un pipeline vert : supprimer eval(), retirer les clés du code et les passer en variables masquées, mettre à jour les dépendances
  • Analysez l'image Docker du projet avec trivy image, comme dans le TP Trivy
  • Approfondissez chaque outil : TP Gitleaks, TP SonarQube, TP Renovate

Questions de synthèse

  • Pourquoi analyser tout l'historique Git, et pas seulement la dernière version ?
  • Quelle différence entre l'analyse du code (Semgrep) et celle des dépendances (Trivy) ?
  • Qu'est-ce qu'une dépendance indirecte, et pourquoi est-elle dangereuse ?
  • À quoi sert le SBOM, une fois le pipeline terminé ?
  • Pourquoi épingler la version des outils plutôt qu'utiliser latest ?