Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 721 minIntermédiaire

Sécurité dans la CI : SAST, dépendances et secrets (DevSecOps)

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • distinguer SAST, SCA, DAST et détection de secrets ;
  • mettre en place CodeQL, la revue des dépendances et Dependabot ;
  • expliquer les attaques qui visent un pipeline et leurs contre-mesures ;
  • éviter le piège de pull_request_target avec les forks.

4 exercices corrigés

DevSecOps consiste à intégrer la sécurité à chaque étape du cycle de développement, en automatisant des contrôles dans le pipeline : analyse du code source (SAST), analyse des dépendances (SCA), détection des secrets, analyse des images et tests de l'application en fonctionnement (DAST). Le pipeline doit aussi être protégé lui-même, car il détient les secrets de déploiement. Ce chapitre présente chaque contrôle avec sa mise en place sur GitHub et GitLab, puis les attaques qui visent les pipelines et leurs contre-mesures.

Décaler la sécurité à gauche

Sur une frise qui va du code à la production, « à gauche » signifie « plus tôt ». Une faille trouvée par un outil pendant la pull request coûte quelques minutes à corriger ; la même faille découverte en production coûte un incident, une correction en urgence, parfois une notification à la CNIL. Décaler à gauche (shift left), c'est placer chaque contrôle le plus tôt possible.

MomentContrôleExemple d'outil
Sur le poste, avant le commitLint de sécurité, détection de secretsExtension de l'éditeur, crochet pre-commit
Au pushBlocage des secrets connusPush protection de GitHub
Dans chaque pull requestAnalyse du code (SAST), revue des dépendances ajoutées (SCA)CodeQL, dependency-review-action, Semgrep, SonarQube
Sur main et chaque nuitAudit complet des dépendances, analyse de l'imagenpm audit, Dependabot, Trivy, Grype
En recetteTest dynamique de l'application qui tourne (DAST)OWASP ZAP
En productionSurveillance, journaux, alertesSupervision, SIEM

Analyser le code source : SAST

Le SAST (Static Application Security Testing) lit le code sans l'exécuter et cherche des motifs dangereux : une requête SQL construite par concaténation, un eval sur une saisie, un mot de passe en dur, une désérialisation risquée. Il suit le chemin des données, de la saisie de l'utilisateur jusqu'à l'endroit sensible.

Sur GitHub, l'outil intégré est CodeQL, gratuit pour les dépôts publics. Le plus simple est la configuration par défaut : Settings, Code security, CodeQL analysis, Set up, Default. Pour la régler finement, on écrit le workflow :

yaml
name: CodeQL

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: '17 3 * * 1'    # chaque lundi, pour profiter des nouvelles règles

permissions:
  contents: read
  security-events: write    # publier les alertes dans l'onglet Security

jobs:
  analyse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: github/codeql-action/init@v4
        with:
          languages: javascript-typescript
      - uses: github/codeql-action/analyze@v4

Les résultats apparaissent comme des alertes dans l'onglet Security, Code scanning, et en commentaire sur les lignes concernées de la pull request : nature de la faille, chemin de la donnée, gravité, conseil de correction. Chaque alerte se traite : on corrige, ou on la ferme en justifiant (faux positif, code de test).

D'autres outils jouent le même rôle : Semgrep (règles lisibles, nombreux langages), SonarQube (qualité et sécurité), PHPStan avec des extensions de sécurité pour PHP, les analyseurs de sécurité de .NET.

Analyser les dépendances : SCA

Une API Node.js de 2 000 lignes embarque souvent plus de 500 paquets, écrits par d'autres. Le SCA (Software Composition Analysis) compare la liste de tes dépendances, directes et indirectes, aux bases de vulnérabilités connues (CVE, avis de sécurité de GitHub) (dépendances et chaîne d'approvisionnement).

Dans le pipeline, chaque écosystème a sa commande d'audit, qui renvoie un code d'erreur quand elle trouve une faille au-dessus du seuil :

ÉcosystèmeCommande
Node.jsnpm audit --audit-level=high (échoue à partir d'une faille de gravité haute)
PHPcomposer audit
.NETdotnet restore avec NuGet Audit (actif par défaut) et <WarningsAsErrors>NU1903;NU1904</WarningsAsErrors> dans le projet pour échouer sur les failles hautes et critiques ; liste détaillée : dotnet package list --vulnerable --include-transitive
Pythonpip-audit

Sur les pull requests, l'action officielle dependency-review-action examine uniquement les dépendances ajoutées ou modifiées par la pull request et la bloque si l'une d'elles a une faille connue :

yaml
name: Revue des dépendances

on:
  pull_request:

permissions:
  contents: read

jobs:
  revue:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/dependency-review-action@v5
        with:
          fail-on-severity: high

Elle est gratuite pour les dépôts publics ; pour les dépôts privés, elle demande l'option de sécurité payante de GitHub.

En continu, Dependabot, intégré à GitHub, fait trois choses : il t'alerte quand une dépendance a une faille connue, il ouvre une pull request de correction de sécurité, et, si tu l'as configuré, il ouvre des pull requests de mise à jour régulières, que ton pipeline teste aussitôt. Active les deux premières dans Settings, Code security, et décris la troisième dans .github/dependabot.yml :

yaml
version: 2
updates:
  - package-ecosystem: npm
    directory: /
    schedule:
      interval: weekly
    groups:
      outils-de-dev:
        dependency-type: development   # une seule pull request pour les outils de développement
  - package-ecosystem: github-actions  # les versions des actions de tes workflows
    directory: /
    schedule:
      interval: monthly
  - package-ecosystem: docker          # l'image de base du Dockerfile
    directory: /
    schedule:
      interval: weekly

Sur GitLab, l'équivalent de Dependabot est l'outil libre Renovate, qui fonctionne aussi sur GitHub.

Détecter les secrets

Un mot de passe, une clé d'API ou une clé SSH commités dans un dépôt sont compromis, même si le commit suivant les supprime : ils restent dans l'historique, et des robots parcourent en permanence les dépôts publics à leur recherche.

  • Secret scanning (GitHub) analyse les dépôts publics et reconnaît des centaines de formats de jetons (GitHub, AWS, Azure, Google Cloud, Stripe, Slack…). Pour certains fournisseurs, il les signale directement à l'émetteur, qui peut les révoquer.
  • Push protection va plus loin : il refuse le push qui contient un secret reconnu, avant qu'il n'atteigne le dépôt. Elle est activée par défaut pour les dépôts publics.
  • Gitleaks et TruffleHog, outils libres, cherchent les secrets dans l'historique, en local ou dans n'importe quelle CI ; GitLab intègre sa propre détection (Secret Detection).

Si un secret a fuité, l'ordre des actions compte : d'abord révoquer le secret et en créer un nouveau, ensuite seulement nettoyer l'historique (un secret a fuité dans Git). Le chapitre 8 explique comment stocker les secrets du pipeline.

Analyser les images de conteneurs

Une image Docker contient un système de base (Debian, Alpine) et ses paquets, en plus de ton application. Un scanner d'images (Trivy, Grype, Docker Scout) liste ces paquets et les compare aux bases de vulnérabilités. On le lance dans le pipeline après la construction de l'image, avec un seuil : échouer s'il trouve une faille critique pour laquelle un correctif existe.

Trois habitudes réduisent les alertes à la source (chapitre 9) :

  • partir d'une image de base minimale (node:24-slim, une image distroless) : moins de paquets, moins de failles ;
  • reconstruire régulièrement l'image, même sans changement du code, pour récupérer les correctifs du système de base ;
  • laisser Dependabot proposer les nouvelles versions de l'image de base.

Tester l'application qui tourne : DAST

Le DAST (Dynamic Application Security Testing) teste l'application en fonctionnement, de l'extérieur, comme le ferait un attaquant : il parcourt les pages, envoie des requêtes, observe les réponses, et signale les en-têtes de sécurité manquants, les cookies sans attribut Secure, les messages d'erreur trop bavards, certaines injections. L'outil libre de référence est OWASP ZAP.

On le lance dans la CI contre l'environnement de recette, juste après le déploiement, en mode passif (baseline) à chaque déploiement et en mode actif plus rarement, jamais contre la production pendant les heures d'activité.

La sécurité du pipeline lui-même

Le pipeline a accès au code, aux secrets de déploiement et parfois à la production : c'est une cible de choix. Les attaques suivantes ont toutes eu lieu sur de vrais projets.

Une action tierce compromise. En mars 2025, un attaquant a pris le contrôle de l'action très utilisée tj-actions/changed-files et a déplacé ses étiquettes de version vers un commit malveillant, qui affichait les secrets des workflows dans leurs journaux (CVE-2025-30066). Un an plus tard, en mars 2026, des dizaines d'étiquettes de l'action aquasecurity/trivy-action, un scanner de sécurité, ont été réécrites de la même façon pour voler les secrets des pipelines qui l'utilisaient (CVE-2026-33634). Les dépôts qui référençaient ces actions par une étiquette (@v1) ont exécuté le code piégé ; ceux qui les référençaient par une empreinte de commit n'ont pas été touchés.

Contre-mesures :

  • épingler les actions tierces sur l'empreinte complète d'un commit, avec la version en commentaire pour rester lisible ; Dependabot met à jour ces empreintes ;
  • limiter les actions autorisées (Settings, Actions, General : seulement les actions de GitHub, des créateurs vérifiés et une liste choisie) ; GitHub permet aussi d'imposer l'épinglage par empreinte au niveau du dépôt ou de l'organisation ;
  • préférer une commande de quelques lignes à une action tierce qui fait la même chose.
yaml
      # Action tierce épinglée sur un commit précis (exemple d'empreinte, ne la recopie pas)
      - uses: exemple-editeur/action-deploiement@9f2c5e1a7b3d4c6e8f0a1b2c3d4e5f6a7b8c9d0e # v2.4.1

L'injection de script. Une donnée choisie par un inconnu (titre de pull request, nom de branche, message de commit) insérée avec ${{ }} dans un run devient du code. Parade : passer par une variable d'environnement (expressions et contextes).

Des droits trop larges. Un workflow qui n'a besoin que de lire le code n'a aucune raison de pouvoir écrire dans le dépôt ou publier un paquet. Déclare permissions: contents: read en tête de chaque workflow, et ajoute job par job les seuls droits nécessaires. Dans les réglages du dépôt, laisse la permission par défaut du jeton en lecture seule (Read repository contents and packages permissions).

L'empoisonnement du cache. Un job peu sûr enregistre un cache modifié, qu'un job privilégié restaure ensuite (le cache). Parade : pas de cache dans les workflows de publication et de déploiement quand il n'est pas indispensable.

Les modifications de workflow. Celui qui modifie un workflow choisit ce qui s'exécute avec les secrets. Protège .github/workflows/ avec CODEOWNERS et la revue obligatoire (protection de branche).

pull_request_target et les forks

Quand quelqu'un propose une pull request depuis un fork (sa copie du dépôt), le déclencheur pull_request exécute le workflow sans les secrets et avec un jeton en lecture seule : un inconnu ne peut rien voler en modifiant le code ou le workflow.

Le déclencheur pull_request_target, lui, s'exécute dans le contexte du dépôt cible, avec ses secrets et un jeton en écriture. Il sert à des tâches qui ne touchent pas au code proposé, comme étiqueter la pull request ou poster un message de bienvenue. Le danger apparaît quand un tel workflow récupère et exécute le code de la pull request (installation des dépendances, tests) : l'inconnu fait alors exécuter son propre code avec les secrets du dépôt. Cette erreur, surnommée pwn request, a touché de nombreux projets libres.

Depuis juin 2026, actions/checkout (version 7, et 6.1 par rétroportage) refuse par défaut de récupérer le code d'une pull request de fork dans un workflow déclenché par pull_request_target ou workflow_run ; il faut une option explicite (allow-unsafe-pr-checkout) pour passer outre. Les versions plus anciennes n'ont pas ce garde-fou, raison de plus pour laisser Dependabot tenir tes actions à jour. La règle reste simple : pour lancer les tests d'une pull request, utilise pull_request, jamais pull_request_target.

Avec GitLab

GitLab fournit des modèles de jobs de sécurité, que l'on inclut en une ligne :

yaml
include:
  - template: Jobs/SAST.gitlab-ci.yml
  - template: Jobs/Secret-Detection.gitlab-ci.yml

stages:
  - test

Les jobs ajoutés détectent les langages du projet et produisent un rapport, téléchargeable comme artefact. L'analyse des dépendances, l'analyse des images et le DAST existent aussi sous forme de modèles ; l'affichage des résultats dans la merge request et le tableau de bord des vulnérabilités sont réservés à l'offre la plus haute de GitLab. Les mêmes outils libres (Semgrep, Trivy, Gitleaks, ZAP) se lancent aussi par de simples commandes dans un script.

Le lien avec l'E7 SLAM

La compétence du bloc 3 SLAM « Assurer la cybersécurité d'une solution applicative et de son développement » a deux indicateurs que la CI prouve directement : « les composants utilisés sont certifiés, sécurisés et actualisés » (Dependabot, npm audit, revue des dépendances) et « des tests de sécurité sont prévus et mis en œuvre » (SAST, DAST, tests d'autorisation automatisés). Dans une copie d'E7, quand on te demande comment éviter qu'une faille de dépendance revienne, la réponse attendue est un contrôle automatique dans le pipeline, avec son seuil et son moment (les compétences du bloc 3 évaluées à l'E7, tests de sécurité à l'E7, méthode de l'E7 SLAM).

Exercices

Exercice 1 sur 4 SAST, SCA, DAST ou secrets ? Réussi À revoir

Classe chaque contrôle.

  1. Un outil signale que la ligne 42 de notes.controller.js construit une requête SQL en concaténant req.query.tri.
  2. Un outil signale que la version de la bibliothèque de génération de PDF a une faille connue de gravité haute.
  3. Un outil, lancé contre https://recette.sorbier.example, signale que l'en-tête Content-Security-Policy est absent.
  4. GitHub refuse un push qui contient une clé d'API d'un service de paiement.
  5. Un outil signale que l'image Docker contient une version d'OpenSSL vulnérable.
Voir la réponseMasquer la réponse de l'exercice 1
  1. SAST : analyse du code source, sans l'exécuter.
  2. SCA : analyse des dépendances.
  3. DAST : test de l'application en fonctionnement.
  4. Détection de secrets, ici la push protection.
  5. Analyse d'image de conteneur : c'est une forme de SCA appliquée aux paquets du système de l'image.

Tu avais trouvé ?

Exercice 2 sur 4 Lire un audit de dépendances Réussi À revoir

L'étape npm audit --audit-level=high échoue. Voici la fin de sa sortie (extrait simplifié, paquets fictifs) :

text
# npm audit report

pdf-generateur  <3.2.1
Severity: high
Prototype Pollution in pdf-generateur
fix available via `npm audit fix`
node_modules/pdf-generateur

date-format-lite  <1.4.0
Severity: moderate
Regular Expression Denial of Service in date-format-lite
fix available via `npm audit fix --force`
Will install date-format-lite@2.0.0, which is a breaking change
node_modules/date-format-lite

2 vulnerabilities (1 moderate, 1 high)
  1. Pourquoi l'étape échoue-t-elle ? Échouerait-elle avec --audit-level=critical ?
  2. Que proposes-tu pour chaque paquet ?
  3. Pourquoi ne pas ajouter simplement npm audit fix --force dans le pipeline ?
Voir la réponseMasquer la réponse de l'exercice 2
  1. Une faille de gravité haute (pdf-generateur) atteint le seuil high. Avec --audit-level=critical, aucune faille n'atteint le seuil : l'étape réussirait.
  2. pdf-generateur : npm audit fix installe une version corrigée compatible (3.2.1 ou plus, même version majeure) ; on le fait dans une branche, on vérifie que les tests passent, et la pull request est testée par la CI. date-format-lite : le correctif impose la version majeure 2.0.0, qui peut casser l'API ; on lit son journal des modifications, on adapte le code si besoin, on teste. En attendant, la faille est modérée : on la note dans un ticket avec une échéance.
  3. Le pipeline modifierait les dépendances sans revue, en installant des versions majeures non testées, et ces modifications ne seraient même pas commitées : le code livré ne correspondrait plus au dépôt. La CI détecte ; la correction passe par une pull request.

Tu avais trouvé ?

Exercice 3 sur 4 Un workflow dangereux Réussi À revoir

Ce workflow a été ajouté pour « tester les contributions externes avec la base de recette » :

yaml
on: pull_request_target
jobs:
  tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ github.event.pull_request.head.sha }}
          allow-unsafe-pr-checkout: true
      - run: npm ci
      - run: npm test
        env:
          DB_PASSWORD: ${{ secrets.RECETTE_DB_PASSWORD }}
  1. Explique le risque, sans décrire d'attaque précise.
  2. Propose une correction.
Voir la réponseMasquer la réponse de l'exercice 3
  1. pull_request_target s'exécute avec les secrets du dépôt et un jeton en écriture. Le workflow récupère le code proposé par l'auteur de la pull request, qui peut être n'importe qui, puis l'exécute (npm ci exécute les scripts d'installation des paquets, npm test le code des tests). Un contributeur malveillant ferait donc exécuter son propre code avec le mot de passe de la base de recette dans l'environnement. L'option allow-unsafe-pr-checkout: true désactive justement la protection ajoutée par actions/checkout.
  2. Utiliser on: pull_request : les pull requests de forks s'exécutent alors sans secrets. Les tests utilisent une base jetable créée dans le job (services: mysql), pas la base de recette. Si des tests ont vraiment besoin d'un secret, ils ne tournent qu'après la fusion, sur main, ou après validation par un mainteneur.

Tu avais trouvé ?

Exercice 4 sur 4 Configurer Dependabot Réussi À revoir

Écris le fichier .github/dependabot.yml d'une application Symfony dont le dépôt contient aussi des workflows GitHub Actions et un Dockerfile. Les dépendances PHP doivent être vérifiées chaque semaine, le reste chaque mois.

Voir la réponseMasquer la réponse de l'exercice 4
yaml
version: 2
updates:
  - package-ecosystem: composer
    directory: /
    schedule:
      interval: weekly
  - package-ecosystem: github-actions
    directory: /
    schedule:
      interval: monthly
  - package-ecosystem: docker
    directory: /
    schedule:
      interval: monthly

Pour github-actions, directory: / suffit : Dependabot cherche lui-même dans .github/workflows/. Les alertes et les correctifs de sécurité, eux, s'activent dans les réglages et n'attendent pas le calendrier.

Tu avais trouvé ?

Chapitre terminé !

Fais les 4 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Secrets et variables dans un pipelineRevoir le sommaire du cours